这一篇主要是指令、过滤器、路由、Store的配置、axios的二次封装以及使用
1.过滤器的配置
1)在filters目录新建filters/index.js,目录结构如下:
└─src
│ filters
│ index.js
filters/index.js
/**
* @description 过滤时间格式,传入时间戳, 根据参数返回不同格式
*/
// 过滤日期格式,传入时间戳,根据参数返回不同格式
const formatTimer = function(val, hours) {
if (val) {
var dateTimer = new Date(val * 1000)
var y = dateTimer.getFullYear()
var M = dateTimer.getMonth() + 1
var d = dateTimer.getDate()
var h = dateTimer.getHours()
var m = dateTimer.getMinutes()
M = M >= 10 ? M : '0' + M
d = d >= 10 ? d : '0' + d
h = h >= 10 ? h : '0' + h
m = m >= 10 ? m : '0' + m
if (hours) {
return y + '-' + M + '-' + d + ' ' + h + ':' + m
} else {
return y + '-' + M + '-' + d
}
}
}
/**
*@description 格式化支付方式
* */
const formatPayWay = function(val) {
switch (val) {
case 1:
return '微信'
break
case 2:
return '支付宝'
break
case 3:
return 'apple pay'
break
case 4:
return '银联支付'
break
default:
break
}
}
/**
* 根据key过滤值 returnkey要返回的值的key
*/
const findValue = function(val, key, filterArr, rerunkey) {
let findItem = filterArr.find(item => {
return item[key] === val
})
if (findItem) {
return findItem[rerunkey]
}
}
/**
* 文字超出就省略
* @param {String} text 文本
* @param {number} length 截取长度
*/
const textEllipsis = function(text, length) {
return text.length > length ? text.slice(0, length) + '...' : text
}
export default {
formatTimer,
formatPayWay,
findValue,
textEllipsis
}
- 在main.js中引入
src/main.js
import filters from '@/filters'
// 注入全局过滤器
Object.keys(filters).forEach(item => {
Vue.filter(item, filters[item])
})
- 文件中使用
那findValue 方法举例
<div class="class-hour-tag">
{{coursewareItem.type | findValue('value', coursewareStateArr, 'text')}}
</div>
2.指令的配置
1)在directives目录新建directives/index.js,目录结构如下:
└─src
│ directives
│ index.js
directives/index.js
/**
* @description fitIphoneX 主要是为了适配iphoneX自适配的问题,可以设置padding,maring,bottom
* @params setValue 需要设置的值 | type 设置的类型,比如说padding
* @useage v-fitIphoneX="{ type: 'padding', pxNum: 10 }"
*/
function judgeIPhoneX() {
// 判断是否是iphoneX
let ua = window.navigator.userAgent
let isIos = !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
return isIos && window.screen.height === 812 && window.screen.width === 375
? true
: false
}
export const fitIphoneX = {
bind(el, binding) {
let isIPhoneX = judgeIPhoneX()
let designWidth = 375 // 设计稿高度
let pxNum = binding.value.pxNum
let iphoneXNum = (binding.value.pxNum || 30) + 34
let setValue = isIPhoneX
? (100 / designWidth) * iphoneXNum
: (100 / designWidth) * pxNum // 转化成vw
switch (binding.value.type) {
case 'padding':
el.style.paddingBottom = `${setValue}vw`
break
case 'margin':
el.style.marginBottom = `${setValue}vw`
break
default:
el.style.bottom = `${setValue}vw`
break
}
}
}
/**
* @description 修复ios手机失去焦点页面未还原问题
* @params
* @useage v-reset-page
*/
export const resetPage = {
inserted(el) {
// 监听键盘收起事件
document.body.addEventListener('focusout', () => {
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
// 软键盘收起处理
setTimeout(() => {
const scrollHeight =
document.documentElement.scrollTop || document.body.scrollTop || 0
window.scrollTo({
left: 0,
top: Math.max(scrollHeight - 1, 0),
behavior: 'smooth'
})
}, 100)
}
})
}
}
/**
* @description input输入框只能输入数字
* @params
* @useage v-number-only
*/
export const numberOnly = {
bind(el, binding) {
el.handler = function() {
let val = el.value
val = val.replace(/[^\d]/g, '')
if (el.value.length > binding.value) {
el.value = val.slice(0, binding.value)
console.log('el.value---', el.value)
}
}
el.addEventListener('input', el.handler, false)
},
unbind(el) {
el.removeEventListener('input', el.handler)
}
}
- 在main.js中引入
src/main.js
import * as directives from './directives'
// 注入全局指令
Object.keys(directives).forEach(item => {
Vue.directive(item, directives[item])
})
3)在文件中使用
拿resetPage 方法举例
<input
type="number"
class="phone"
placeholder="请输入手机号"
v-model.trim="phoneNumber"
oninput="if (value.length > 11) value = value.slice(0, 11).replace(/[^\d]/g, '')"
v-reset-page
/>
3.路由的配置
1)src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
/* 解决vue项目路由出现message: "Navigating to current location (XXX) is not allowed"的问题*/
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error => error)
}
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home'),
meta: {
title: '首页',
keepAlive: true
}
},
{
path: '/404',
name: '404',
component: () => import('@/views/404'),
meta: {
title: '404',
keepAlive: true
}
},
{ path: '*', redirect: '/404', hidden: true }
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
scrollBehavior: () => ({ y: 0 }),
routes
})
export default router
4.Store的配置
1)store的文件目录结构如下:
└─src
├─store // vuex
│ │ getters.js // vuex中的getters 相当于computed
│ │ index.js // store的入口文件
│ │
│ └─modules // 将soter分为多个模块
│ user.js
同时在utils目录下新建constant.js,这里主要是是放置一些常量
└─src
├─utils
│ constant.js
以下是主要文件的示例代码
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
// 自动化引入modules下的所有js文件 https://webpack.docschina.org/guides/dependency-management/#require-context
const modulesFiles = require.context('./modules', true, /\.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// 设置 user.js => user
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
//将数据集成为 modules: {a: moduleA,b: moduleB}的格式
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
export default new Vuex.Store({
getters,
modules
})
src/store/getters.js
const getters = {
token: state => state.user.token,
userInfo: state => state.user.userInfo,
inviteUserInfo: state => state.study.inviteUserInfo,
openId: state => state.user.openId,
device: state => state.page.device,
subjects: state => state.practice.examInfo.subjectList,
subjectSum: state => state.practice.examInfo.subjectSum,
practiceModel: state => state.practice.examInfo.model
}
export default getters
src/store/modules/user.js
import {
LOGIN,
LOGOUT,
USERINFO,
SET_USERINFO,
SET_OPENID,
OPENID,
USER_TOKEN,
SET_TOKEN
} from '@/utils/constant'
import { loginByPhone, fetchUserInfo, logout } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
// import { Toast } from 'vant'
const state = {
token: getToken(USER_TOKEN) || '', // 权限验证
userInfo: JSON.parse(localStorage.getItem(USERINFO)),
openId: getToken(OPENID) || '' // openId
}
const mutations = {
[LOGIN](state, token) {
state.token = token
setToken(USER_TOKEN, token)
},
[LOGOUT](state) {
state.userInfo = null
state.token = ''
removeToken(USER_TOKEN)
sessionStorage.removeItem(USERINFO)
resetRouter()
},
[SET_TOKEN](state, token) {
state.token = token
setToken(USER_TOKEN, token)
},
[SET_USERINFO](state, userInfo = {}) {
state.userInfo = { ...userInfo }
localStorage.setItem(USERINFO, JSON.stringify(userInfo))
},
[SET_OPENID](state, openId) {
state.openId = openId
setToken(OPENID, openId)
}
}
const actions = {
async loginByPhone({ commit }, data) {
// 登录,登出会根据业务场景实现
try {
let res = await login({
phoneNumber: data.phoneNumber,
password: data.password
})
commit(LOGIN, res)
Toast({
message: '登录成功',
position: 'middle',
duration: 1500
})
setTimeout(() => {
const redirect = data.$route.query.redirect || '/'
data.$router.replace({
path: redirect
})
}, 1500)
} catch (error) {
return error
}
},
getUserInfo({ commit, state }) {
return new Promise((resolve, reject) => {
fetchUserInfo()
.then(data => {
if (!data) {
reject('Verification failed, please Login again.')
}
commit(SET_USERINFO, data)
resolve(data)
})
.catch(err => {
reject(err)
})
})
},
resetToken({ commit, state }) {
return new Promise(resolve => {
commit(SET_TOKEN, '')
removeToken(USER_TOKEN)
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
5.axios的二次封装以及使用
1)安装axios
yarn add axios -D
2)在src/utils 目录新建request.js
request.js
/* 对axios根据业务需求再次封装 */
import axios from 'axios'
import { Toast, Dialog } from 'vant'
import { getToken } from './auth'
import store from '@/store'
import { USER_TOKEN } from '@/utils/constant'
import Router from '@/router'
import defaultSettings from '@/settings'
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
// 让请求携带token
config.headers['token'] = getToken(USER_TOKEN)
}
return config
},
error => {
// console.log('request-error:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
// 拦截文件流
const headers = response.headers
if (headers['content-type'] === 'application/octet-stream') {
return response.data
}
const res = response.data
if (res.code === 200) {
//响应成功
return res.data
} else {
// 2004: token 无效; 2005: token 过期;
if (res.code === 2004 || res.code === 2005) {
// to re-login 不在白名单中就提示重新登录并且刷新当前页面
if (!defaultSettings.whiteList.includes(Router.history.current.path)) {
Dialog.alert({
message: '您必须重新登录!'
}).then(() => {
console.log('重新登录确定')
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
} else if (res.code === 2001 || res.code === 2003) {
// 不需要弹窗的情况
// 微信没有绑定手机号的情况下
return Promise.reject(res)
} else {
/* 其他的情况 */
Toast({
message: res.msg || 'response error',
duration: 5 * 1000
})
return Promise.reject(res)
}
}
},
error => {
if (error.response.status > 500 && error.response.status < 506) {
Toast('服务器错误')
} else {
Toast(error.msg)
}
return Promise.reject(error)
}
)
export default service
请求的response拦截器那部分需要根据自身业务来,自己稍微改写下就可以用。
- 在src下新建api目录, 并且新建user.js,目录结构如下
src/api/user.js
/* 用户相关 */
import request from '@/utils/request'
/**
* @export
* @param {*}
* @returns
*/
export function test() {
return request({
url: '/test',
method: 'get'
})
}
/**
* @description 手机验证码登录
* @export
* @param {*} data
* @returns
*/
export function loginByPhone(data) {
return request({
url: '/loginByPhone',
method: 'post',
data
})
}
好的今天主要配置就这么多。下一篇主要是配置路由拦截器,以及微信登录的设计逻辑,其中包含微信测试授权登录的小技巧