本示例基於vue-admin-template基礎模板進行二次開發
在線演示:http://hymhub.gitee.io/vue-element-admin-role
源碼地址:https://gitee.com/hymhub/vue-element-admin-rolecode
效果圖:
在 前端后台項目 中通常會根據用戶登錄成功后 根據用戶權限動態添加相應路由 及渲染功能菜單,在vue-element-admin官方文檔中的實現是前端提前寫好異步掛載的總路由表,規定好每個路由能進入的角色,登錄成功后端返回角色權限,再比對權限過濾路由動態添加,官方介紹:
再來看vue-element-admin官方主分支的異步路由表代碼:
如官方文檔所述,官方示例是將權限寫死在前端的,主要告訴我們一個實現的思路,當然官方也提到后期可能會增加權限控制面板,期待...
本示例權限驗證實現思路
用戶登錄成功后,我們去獲取用戶權限,后端通常會返回一個可訪問的路由表,前端根據返回的路由表與提前設定的總路由表進行匹配過濾出最終可訪問的路由,再使用router.addRoutes 動態掛載,后台返回的路由表,大概長這樣:第一種后端返回的路由表格式 ,可以大概看一下長什么樣,后面會提到
[
{
path: '/',
children: [
{
path: 'dashboard'
}
]
},
{
path: '/example',
children: [
{
path: 'table'
},
{
path: 'tree'
}
]
},
{
path: '/form',
children: [
{
path: 'index'
}
]
},
{
path: '/nested',
children: [
{
path: 'menu1',
children: [
{
path: 'menu1-1'
},
{
path: 'menu1-2',
children: [
{
path: 'menu1-2-1'
},
{
path: 'menu1-2-2'
}
]
},
{
path: 'menu1-3'
}
]
},
{
path: 'menu2'
}
]
},
{
path: 'external-link',
children: [
{
path: 'https://panjiachen.github.io/vue-element-admin-site/#/'
}
]
}
]
也可能是這樣(第二種后端返回的路由表格式 ,可以大概看一下長什么樣,后面會提到):
[
{ path: "/dashboard" },
{ path: "/table" },
{ path: "/example" },
{ path: "/tree" },
{ path: "/editStu/index/:id" },
{ path: "/form" },
{ path: "/menu1" },
{ path: "/menu2" },
{ path: "/menu1-1" },
{ path: "/menu1-2" },
{ path: "/menu1-3" },
{ path: "/menu1-2-1" },
{ path: "/menu1-2-2" },
{ path: "/external" },
]
具體實現
在src/router/index.js中導出asyncRoutes總路由表,里面正常添加路由:constantRoutes中只保留登錄頁和404頁面,因為登錄后會比對總路由表動態掛載路由:
一切從登錄開始,在登錄按鈕點擊后觸發store倉庫中的登錄方法,登錄成功后設置token並跳轉到首頁
下面配置路由守衛,修改模板中src目錄下的permission.js文件,這里最為關鍵:
// permission.js
import router from './router'
import store from './store'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
import { asyncRoutes } from '@/router'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.routes.length === 0) { // 判斷當前用戶是否已拉取過路由表信息
store.dispatch('user/getInfo').then(routesMap => { // 拉取路由表
store.dispatch('permission/generateRoutes', { asyncRoutes, routesMap }).then(addRouters => { // 生成可訪問的路由表,asyncRoutes總路由表,routesMap后端返回的路由表,addRouters 過濾后需要動態掛載的路由表
router.addRoutes(addRouters)
next({ ...to, replace: true }) // hack方法 確保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
})
}).catch(err => {
console.log(err);
});
} else {
next() //當有用戶權限的時候,說明所有可訪問路由已生成 如訪問沒權限的全面會自動進入404頁面
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
上面代碼主要修改模板中的導航守衛,判斷是否從后台拉取了當前用戶可訪問的路由表,如果拉取了,則路由放行,如果沒拉取,則拉取並進行路由比對過濾出可訪問的路由表,最后掛載路由放行
路由比對處理,也就是上面代碼中的store.dispatch('permission/generateRoutes'.....
// store/modules/modules/permission.js
import { constantRoutes } from '@/router' //初始路由表,里面包含登錄和404頁面路由
// 后端返回的嵌套路由表(也就是最開始說的"第一種后端返回的路由表格式")轉一維數組(也就是最開始說的"第二種后端返回的路由表格式"),
// 轉換后用於與總路由表進行比較,后端若返回一維數組則直接交由下面filterAsyncRoutes處理
function MultidimensionalToOnedimensional(routesMap) {
const filterRoutesMap = []
!function fn(routesMap) {
routesMap.forEach(route => {
const tmp = {}
for (const key in route) {
if (Object.hasOwnProperty.call(route, key)) {
if (key !== 'children') {
tmp[key] = route[key]
} else if (key === 'children') {
fn(route[key])
}
}
}
filterRoutesMap.push(tmp)
})
}(routesMap)
return filterRoutesMap
}
// 比對過濾路由表,生成最終可訪問的路由表
export function filterAsyncRoutes(routes, filterRoutesMap) {
const accessedRoutes = [];
routes.forEach(route => {
const tmp = {};
if (filterRoutesMap.some(a => a.path == route.path)) {
for (const key in route) {
if (Object.hasOwnProperty.call(route, key)) {
if (key !== 'children') {
tmp[key] = route[key];
} else if (key === 'children') {
const tmpC = filterAsyncRoutes(route[key], filterRoutesMap);
(tmpC.length > 0) && (tmp.children = tmpC);
}
}
}
}
tmp.path && accessedRoutes.push(tmp)
});
return accessedRoutes
}
const getDefaultState = () => {
return {
routes: [],
addRoutes: []
}
}
const state = getDefaultState;
const mutations = {
// 設置路由,過濾后的路由表存倉庫
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
state.routes.push({ path: '*', redirect: '/404', hidden: true }); // 找不到路由或訪問無權限路由跳轉404,必須放在最后,因為只有找不到才會進入這個條件
},
// 重置路由,用於退出登錄操作
RESET_STATE: state => {
Object.assign(state, getDefaultState())
}
}
const actions = {
// 生成可訪問路由表
generateRoutes({ commit }, { asyncRoutes, routesMap }) {
return new Promise(resolve => {
const filterRoutesMap = MultidimensionalToOnedimensional(routesMap)
let accessedRoutes = filterAsyncRoutes(asyncRoutes, filterRoutesMap)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
},
// 重置路由,用於退出登錄操作
resetState({ commit }) {
return new Promise(resolve => {
commit('RESET_STATE');
resolve();
});
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
上面代碼主要就是做遞歸遍歷,將后端返回的嵌套路由表(也就是最開始說的"第一種后端返回的路由表格式")轉一維數組(也就是最開始說的"第二種后端返回的路由表格式"),
然后再遞歸遍歷總路由表比對后端返回的路由表,生成最終可訪問的路由表。
掛載路由后,還需要在src/layout/components/Sidebar/index.vue中的routes方法返回的路由表改成上面store/modules/modules/permission.js中配置的routes用以渲染側邊欄菜單:
src/store/getters.js:
示例完整源碼地址:https://gitee.com/hymhub/vue-element-admin-rolecode