前面已经实现了用户与角色关联、角色与路由关联,这篇文章主要介绍后端根据用户角色返回角色绑定的路由后,前端如何实现动态生成路由。
首先我们前端保存了一份全部的异步路由配置表:
export const asyncRoutes = [
{
path: '/privilegeManage',
component: Layout,
redirect: '/privilegeManage/menuList',
name: 'privilegeManage',
alwaysShow: true,
meta: {
title: '权限管理',
icon: 'privilege-manage',
roles: ['admin', 'seeAdmin']
},
children: [
{
path: 'menuList',
component: () => import('@/views/privilegeManage/menuList'),
name: 'menuList',
meta: {
title: '菜单管理',
icon: 'menu',
noCache: true
}
},
{
path: 'createMenu',
component: () => import('@/views/privilegeManage/createMenu'),
name: 'createMenu',
meta: {
title: '添加菜单',
},
hidden: true
},
{
path: 'editMenu',
component: () => import('@/views/privilegeManage/editMenu'),
name: 'editMenu',
meta: {
title: '修改菜单',
},
hidden: true
},
{
path: 'roleList',
component: () => import('@/views/privilegeManage/roleList'),
name: 'roleList',
meta: {
title: '角色管理',
icon: 'role-list',
noCache: true
}
},
]
},
{
path: '/accounts',
component: Layout,
redirect: '/accounts/list',
name: 'accountManage',
alwaysShow: true,
meta: {
title: '账单管理',
icon: 'accounts'
},
children: [
{
path: 'accountClassify',
component: () => import('@/views/accounts/accountClassify'),
name: 'accountClassify',
meta: {
title: '分类管理',
icon: 'classify',
roles: ['admin', 'seeAdmin']
}
},
{
path: 'list',
component: () => import('@/views/accounts/list'),
name: 'accountsList',
meta: {
title: '账单列表',
icon: 'capitallist',
noCache: true
}
},
{
path: 'accountsEdit',
component: () => import('@/views/accounts/accountsEdit'),
name: 'accountsEdit',
meta: {
title: '修改账单',
activeMenu: '/accounts/list'
},
hidden: true
},
{
path: 'accountsAdd',
component: () => import('@/views/accounts/accountsAdd'),
name: 'accountsAdd',
meta: {
title: '添加账单',
activeMenu: '/accounts/list'
},
hidden: true
}
]
},
{path: '*', redirect: '/404', hidden: true}
]
目前就以两个一级菜单为例,为一个角色配置账单管理的权限。后端返回的数据如下:
let userRouter = [
{
menuId: 'f95523b631cbb1f9bc7892147f2e441f',
name: '账单管理',
pid: '',
url: '/accounts'
children: [
{
menuId: "8b0e14591bb0964817192401b3bfe934"
name: "账单列表"
pid: "f95523b631cbb1f9bc7892147f2e441f"
url: "list"
},
{
menuId: "aadc11cddff3e18e09d3291c92e17ce8"
name: "修改账单"
pid: "f95523b631cbb1f9bc7892147f2e441f"
url: "accountsEdit"
},
{
menuId: "f8548737bacd227bddbc7315a7f65fd0"
name: "添加账单"
pid: "f95523b631cbb1f9bc7892147f2e441f"
url: "accountsAdd"
}
]
}
]
一般都是在返回用户信息的接口里面同时返回用户的路由信息,你也可以在获取到用户信息后再获取路由信息也是可以的。
src/permision.js文件:
router.beforeEach(async (to, from, next) => {
// 根据token判断用户登录状态(有的项目不是根据token判断登录的,可根据实际情况进行改变)
const hasToken = getToken();
if(hasToken) {
if(to.path === '/login') {
next({path: '/'})
} else {
// 判断用户是否获取到路由信息
const hasUserRouter = store.state.permission.hasUserRouter;
if (hasUserRouter) {
next();
} else {
try {
// 重新获取用户信息并接收信息中的路由信息
const {routerList} = await stroe.dispatch('user/getInfo');
// 动态生成可访问的路由
const recursiveRouter = await store.dispatch('permission/recursiveRouter', routerList);
router.addRoutes(recursiveRouter);
next({...to, replace: true})
} catch (error) {
Message.error(error || error.message);
// 删除token并跳转到登录页重新登录
await store.dispatch('user/resetToken')
// Message.error(error || '出现一个未知错误')
next(`/login?redirect=${to.path}`)
}
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在白名单中,直接进入
next()
} else {
// 其他没有访问权限的页面将重定向到登录页面
next(`/login?redirect=${to.path}`)
}
}
})
store/modules/permission.js文件:
/**
* 权限生成路由模块
*/
import {asyncRoutes, constantRoutes} from "@/router/index";
/**
* 判断路由路径是否相等
* @param asyncRouterItem
* @param userRouteItem
*/
function hasPath(asyncRouterItem, userRouteItem) {
return asyncRouterItem.path === userRouteItem.path;
}
/**
* 通过循环递归出符合用户权限的路由表
* @param asyncRouterList
* @param userRouterList
* @returns {*[]}
*/
export function recursiveAsyncRoutes(asyncRouterList, userRouterList) {
let res = [];
asyncRouterList.forEach(route => {
const tmp = {...route};
if (tmp.path === '*' && !tmp.hasOwnProperty('children')) {
// 这一步主要是为了添加404路由
res.push(tmp)
} else {
userRouterList.forEach(uRoute => {
if (hasPath(tmp, uRoute)) {
if (tmp.children && uRoute.children) {
tmp.children = recursiveAsyncRoutes(tmp.children, uRoute.children)
}
res.push(tmp)
}
})
}
})
return res;
}
/**
* 数据
* @type {{routes: Array, addRoutes: Array}}
*/
const state = {
hasUserRouter: false,
routes: [],
addRoutes: []
}
const mutations = {
/**
* 设置路由表
* @param state 数据
* @param routes 路由
* @constructor
*/
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
},
/**
* 设置路由状态
* @param state
* @param states
* @constructor
*/
SET_ROUTER_STATE: (state, states) => {
state.hasUserRouter = states;
}
}
const actions = {
/**
* 根据用户路由权限递归路由表
* @param commit
* @param userRouterList 用户路由权限表
* @returns {Promise<any>}
*/
recursiveRouter({commit}, userRouterList) {
return new Promise(resolve => {
let accessedRoutes = recursiveAsyncRoutes(asyncRoutes, userRouterList);
commit('SET_ROUTES', accessedRoutes)
commit('SET_ROUTER_STATE', true)
resolve(accessedRoutes)
})
},
removeRouterState({commit}) {
commit('SET_ROUTER_STATE', false);
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
这个js文件的主要作用就是把后端返回的路由和前端配置的完整路由进行比较,取相同部分的路由。只拥有账单管理的用户如下图:
拥有权限管理和账单管理的用户如下图:
至此,完成了不同角色用户不同菜单(路由)的功能,我们只需要在后台管理系统给角色分配路由的权限就能实现。