1.安裝axios坚芜,創(chuàng)建請求和響應(yīng)攔截器般哼,建立環(huán)境變量文件燥爷,模擬獲得token和userInfo接口。終端中運行 npm i axios 安裝axios谅辣。在/src目錄下創(chuàng)建utils/request.js 用來封裝請求修赞。同時在項目根目錄下創(chuàng)建.env環(huán)境變量文件( .env.development為開發(fā)時環(huán)境變量,.env.production為生產(chǎn)時環(huán)境變量 )桑阶。 axios.create的baseURL使用了環(huán)境變量柏副,因為每次修改環(huán)境變量都需要重新編譯才能生效,所以平時開發(fā)的時候為了省時都是直接在此添加一行實際用到的Url(切記提交代碼前改為使用環(huán)境變量的 (づ╥﹏╥)づ 之前忘記了2次導(dǎo)致生產(chǎn)環(huán)境請求了測試環(huán)境服務(wù)器蚣录,教訓(xùn)案钤瘛)。
request.js中主要做了創(chuàng)建axios實例并攔截請求和響應(yīng)萎河,做一些處理荔泳。實際項目中根據(jù)需要在請求攔截中附加token蕉饼,在響應(yīng)攔截中根據(jù)后端返回的響應(yīng)碼做對應(yīng)的處理。
request.js大概代碼:
import axios from 'axios'
import Store from '@/store'
const timeOut = 10000
const axiosInstance = axios.create({
baseURL: process.env.VUE_APP_BASE_URL,
withCredentials: true, // send cookies when cross-domain requests
timeout: timeOut // request timeout 1
})
axiosInstance.interceptors.request.use(
config => {
const token = Store.getters.token
if (token) {
config.headers['Authorization'] = 'Bearer ' + token
}
return config
},
error => {
return Promise.reject(error)
}
)
axiosInstance.interceptors.response.use(
response => {
// console.log(response)
const res = response.data
// console.log(res)
const code = res.code
if (code === 20000) {
return res
} else {
// TODO 根據(jù)實際項目中接口返回的狀態(tài)碼進(jìn)行處理(比如拿到?jīng)]有token的響應(yīng)跳轉(zhuǎn)login等操作)
return Promise.reject(res)
}
},
error => {
}
)
export default axiosInstance
2. 創(chuàng)建測試api玛歌。在src/下創(chuàng)建apis目錄昧港,生成login.js用來處理和登錄相關(guān)的api。這里測試只用了login登錄和getInfo獲得用戶信息兩個接口
然后在之前views/Login.vue測試頁面中簡單寫一個輸入用戶名+密碼以及一個登錄按鈕支子。點擊登錄按鈕dispatch登錄操作對應(yīng)的loginFn创肥,拿到token并存儲到state中;后繼續(xù)dispatch獲得用戶信息對應(yīng)的getUserInfo拿到roles和userInfo并存儲到state中值朋。這里獲得token后存到了本地叹侄,取的時候也從本地取,本地沒有時默認(rèn)為''昨登,防止刷新后store中數(shù)據(jù)丟失又需要重新登錄圈膏。
import Vue from 'vue'
import Vuex from 'vuex'
import { asyncRoutes, constantRoutes } from '@/router'
import { login, getInfo } from '@/apis/login'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
app: {
sideBarIsCollapse: false, // 左側(cè)菜單欄是否收起
},
routes: asyncRoutes.concat(constantRoutes),
token: localStorage.getItem('token') || '',
userInfo: null,
roles: [],
},
getters: {
token(state) {
return state.token
},
roles(state) {
return state.roles
},
routes(state) {
return state.routes
}
},
mutations: {
TOGGLE_SIDE_BAR(state) {
state.app.sideBarIsCollapse = !state.app.sideBarIsCollapse
},
SET_ROUTES(state, roles){
console.log(state, roles)
},
SET_TOKEN(state, token) {
state.token = token
localStorage.setItem('token', token)
},
SET_INFO(state, data) {
state.userInfo = data
},
SET_ROLES(state, roles) {
state.roles = roles
},
LOGOUT(state) {
state.token = ''
localStorage.removeItem('token')
}
},
actions: {
loginFn({ commit }, data) {
return new Promise((resolve) => {
login(data).then(res => {
if (res?.data) {
commit('SET_TOKEN', res.data)
resolve(res.data)
// dispatch('getUserInfo', res.data)
}
})
})
},
getUserInfo({ commit }, token) {
return new Promise((resolve, reject) => {
getInfo(token).then(res => {
// console.log(res)
if (res?.data) {
commit('SET_INFO', res.data)
commit('SET_ROLES', res.data.roles)
// commit('SET_ROUTES', res.data.roles)
resolve(res.data)
}
}).catch(e => {
reject(e)
})
})
}
},
})
Login.vue中dispatch獲得用戶信息后就可以跳轉(zhuǎn)到首頁了。
async handleLogin() {
const token = await this.loginFn(this.loginForm)
if (token) {
const info = await this.getUserInfo(token)
console.log(info)
if (info.id) {
this.$router.push('/')
}
}
}
3. 到這里就只需要根據(jù)用戶的role過濾出他有權(quán)限看的頁面的路由動態(tài)生成左側(cè)導(dǎo)航菜單篙骡,并且使用router.beforeEach路由守衛(wèi)進(jìn)行權(quán)限判斷攔截處理稽坤。
之前為了測試,router/index.js中new VueRouter時傳入的routes為所有路由糯俗,現(xiàn)在改為只傳入不需要權(quán)限就能訪問的constantRoutes尿褪;同時把store/index.js中的state中 routes 改為空數(shù)組;并新增2個方法得湘,調(diào)用getUserInfo后拿到role根據(jù)用戶角色從所有路由中過濾能訪問的路由數(shù)據(jù)賦值給state中的routes杖玲。
filterAsyncRoutes方法中,在有子路由即有children情況下使用遞歸處理:
function hasPermission(roles, route) {
if (route.meta && route.meta.rolesAuths) {
return roles.some(role => route.meta.rolesAuths.includes(role)) // rolesAuths只要包含roles中任意一個元素即滿足 true
} else {
return true // 沒寫權(quán)限的默認(rèn)允許 比如404
}
}
function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) { // 父級路由有權(quán)限才處理子頁面權(quán)限
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
// console.log(res)
return res
}
在/src下生成permission.js進(jìn)行路由守衛(wèi)淘正,并在main.js中引入摆马。代碼僅提供思路參考,實際根據(jù)項目完善異常處理
import store from '@/store'
import router from '@/router'
// 不需要鑒權(quán)的頁面
const whitePagePaths = ['/login']
router.beforeEach((to, from, next) => {
console.log(to, from)
const token = store.getters.token
if (token) {
if (to.path === '/login') {
next({ path: '/' }) // 如果是/login, 則跳轉(zhuǎn)到 /, 該跳轉(zhuǎn)動作依舊走一遍 router.beforeEach 邏輯
} else {
const roles = store.getters.roles
if (roles.length > 0) {
next()
} else {
// 如果只有token但是沒有拿到userInfo(比如說刷新了頁面)鸿吆,則重新獲取一次
store.dispatch('getUserInfo').then(res => {
if (res) {
const roles = res.roles
store.commit('SET_ROUTES', roles)
const routes = store.state.routes
routes.forEach(item => {
router.addRoute(item) // !!!! 生成動態(tài)路由的關(guān)鍵api
})
next(to.path)
}
}).catch(e => {
console.log(e)
localStorage.removeItem('token')
store.dispatch('SET_TOKEN', '')
router.push('/login')
})
}
}
} else {
if (whitePagePaths.includes(to.path)) {
next()
} else {
next(`/login?redirect=${to.fullPaht || ''}`)
}
}
})
到這里根據(jù)用戶角色生成動態(tài)路由全部實現(xiàn)思路走了一遍囤采。實現(xiàn)方式只是多種優(yōu)秀方式中的一種,這4篇關(guān)聯(lián)文章僅供自己記錄實現(xiàn)思路回顧用惩淳,如有見解歡迎交流