在做後臺管理系統的時候,一般都會遇見路由權限的問題。
大家可以先體驗一下最終的例子 authority vue Router ,例子項目地址 authorityRouter。在例子的登錄頁面中,通過選擇不同的用戶類型來模擬不同的用戶賬號登錄的情況,通過不同的用戶類型登錄後臺的時候,可以看到左側的 menu 菜單是不同的,只有當有權限的時候纔可以進行查看,並且當手動輸入的時候都會直接到404頁面。
下面我就圍繞這個例子說一下實現原理:
路由導航守衛beforeEach
src/permission.js
import Vue from 'vue'
import router from './router'
import store from './store'
// 路由白名單,允許未登錄的時候可以查看的路由
const whiteList = ['/login']
router.beforeEach(async (to, from, next) => {
// 設置文章標題
if (to.meta && to.meta.title) {
document.title = to.meta.title
}
// 獲取cookies
// 有cookies的時候就默認登錄
let authorityRouterType = Vue.$cookies.get('authorityRouterType')
if (authorityRouterType && authorityRouterType * 1 >= 0) {
authorityRouterType = authorityRouterType * 1
store.commit('setLoginFlag', true)
store.commit('setAuthorityType', authorityRouterType)
// 處理的路由信息
const asyncRoutes = !!store.state.permission.asyncRoutes.length
if (asyncRoutes) {
next()
} else {
// 當路由信息不存在的時候進行請求
try {
// 根據權限得到可用的路由信息
const accessRoutes = await store.dispatch('permission/getRouter', authorityRouterType)
router.addRoutes(accessRoutes)
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
Vue.prototype.$message.error('獲取路由失敗')
if (!whiteList.includes(to.path)) {
next(`/login?redirect=${to.path}`)
}
}
}
} else {
// 當未登錄的時候,當前路由在白名單中直接 next ,否則跳轉回登錄頁面,並攜帶路由path
if (whiteList.includes(to.path)) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
此文件會被引入 main.js
進行執行。
store 信息處理
src/store/module/permission.js
import Vue from 'vue'
import { resetRouter } from '@/router/index'
import { formatAsyncRoute, asyncRoutesBase } from '@/router/dynamicRoutes/index'
/**
* Use meta.role to determine if the current user has permission
*/
function hasPermission (role, route) {
if (route.meta && route.meta.roles) {
return route.meta.roles.includes(role)
}
}
/**
* 對路由進行篩選
*/
export function filterAsyncRoutes (routes, role) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(role, tmp)) {
if (tmp.children && tmp.children.length > 0) {
tmp.children = filterAsyncRoutes(tmp.children, role)
} else {
tmp.children = null
}
res.push(tmp)
} else if (tmp.meta && route.meta.base) {
res.push(tmp)
}
})
return res
}
export default {
namespaced: true,
state: {
asyncRoutes: [],
accessedRoutes: []
},
mutations: {
setAsyncRoutes (state, asyncRoutes) {
asyncRoutes = [asyncRoutes]
formatAsyncRoute(asyncRoutes)
// 將基礎路由添加進去
state.asyncRoutes = asyncRoutes.concat(asyncRoutesBase)
},
setAccessedRoutes (state, accessedRoutes) {
state.accessedRoutes = accessedRoutes
},
resetAsyncRoutes (state) {
state.asyncRoutes = []
}
},
actions: {
async getRouter ({ commit, state }, type) {
// 根據用戶類型獲取路由信息
const res = await Vue.axios.post('/api/authorityRouter/getRouter', {
type
})
if (res.status && res.status === 200) {
if (res.data.isok) {
commit('setAsyncRoutes', res.data.data)
return new Promise(resolve => {
let accessedRoutes
// 這裏 type = 0 表示管理員
if (type === 0) {
accessedRoutes = state.asyncRoutes || {}
} else {
accessedRoutes = filterAsyncRoutes(state.asyncRoutes, type)
}
commit('setAccessedRoutes', accessedRoutes)
resolve(accessedRoutes)
})
} else {
Vue.prototype.$message.error('獲取路由失敗')
}
} else {
Vue.prototype.$message.error('網絡出錯,請重試')
}
},
logout ({ commit, state }) {
// 退出登錄
resetRouter()
commit('resetAsyncRoutes')
commit('setAccessedRoutes', [])
}
}
}
router 路由
src/router/dynamicRoutes/asyncRoutesMap.js
將所有路由 import 待用
/**
* 該組件集合會配合後臺返回的動態路由表,匹配有角色權限的 component
*/
export default {
Admin: () => import('@/views/Admin.vue'),
AdminBase: () => import('@/views/AdminBase.vue'),
AdminVip: () => import('@/views/AdminVip.vue'),
AdminAdmin: () => import('@/views/AdminAdmin.vue')
}
src/router/dynamicRoutes/index.js
import asyncRoutesMap from './asyncRoutesMap'
const Page404 = () => import('@/views/Page404.vue')
// 基礎的動態路由,帶有通配符 * 的路由應該始終放置在路由表的最後面,會拼接到驗權生成的動態路由後面
const asyncRoutesBase = [
{
path: '/404',
name: 'Page404',
component: Page404,
meta: {
base: true,
title: '404',
requiresAuth: false
}
},
{
path: '*',
redirect: '/404',
hidden: true,
meta: {
base: true,
title: '404',
requiresAuth: false
}
}
]
// 對後臺返回的動態路由 component 屬性實體化工具函數
const formatAsyncRoute = (asyncRoutes) => {
asyncRoutes.forEach(asyncRoute => {
if (asyncRoute.component) {
if (asyncRoutesMap[asyncRoute.component]) {
asyncRoute.component = asyncRoutesMap[asyncRoute.component]
} else {
delete asyncRoute.component
}
}
if (asyncRoute.children) {
formatAsyncRoute(asyncRoute.children)
}
})
}
export {
asyncRoutesMap,
formatAsyncRoute,
asyncRoutesBase
}
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
const Login = () => import('../views/Login.vue')
Vue.use(VueRouter)
// 公共路由,和路由白名單裏的路由信息一致
const publicRoutes = [
{
path: '/login',
name: 'Login',
component: Login,
meta: {
title: '登錄',
requiresAuth: false
}
}
]
// 生成信息的公共路由
const createRouter = () => new VueRouter({
routes: publicRoutes,
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
function resetRouter () {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
export {
publicRoutes,
resetRouter
}
登錄信息處理
src/view/Login.vue
async login () {
// 模擬登錄
const res = await this.$axios.post('/api/authorityRouter/login', this.form)
if (res.status && res.status === 200) {
if (res.data.isok) {
this.$store.commit('setLoginFlag', true)
this.$store.commit('setAuthorityType', this.form.type)
this.$cookies.set('authorityRouterType', this.form.type, { expires: 7, path: '' })
// 重置router
resetRouter()
// 根據權限得到可用的路由信息
const accessRoutes = await this.$store.dispatch('permission/getRouter', this.form.type)
router.addRoutes(accessRoutes)
this.$nextTick(() => {
// 處理從其他頁面頁面跳往登錄頁面的情況
if (this.$route.query.redirect) {
this.$router.push(this.$route.query.redirect)
} else {
this.$router.push('/')
}
})
} else {
this.$message.error('登錄失敗')
}
} else {
this.$message.error('網絡出錯,請重試')
}
}
menu 菜單信息處理
src/view/Admin.vue
...mapState('permission', ['asyncRoutes', 'accessedRoutes']),
menuRoutes () {
if (this.accessedRoutes.length) {
// 這裏請根據具體情況定
return this.accessedRoutes[0].children
} else {
return []
}
}
實際上路由權限也是將所有頁面組件都導入了,只是 router 路由中是否添加對應的路由信息罷了。