這一篇主要是指令、過濾器、路由、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
})
}
好的今天主要配置就這麼多。下一篇主要是配置路由攔截器,以及微信登錄的設計邏輯,其中包含微信測試授權登錄的小技巧