项目背景:物业管理后台,不同角色拥有不同权限
采用技术:Vue.js + Vuex + Element UI
实现 RBAC 权限管理需要后端接口支持,这里仅提供前端解决方案。
因代码篇幅较大,对代码进行了删减,文中 “...” 即为省略的一部分代码。
大致思路:
首先登录成功后,从后台拉取用户当前可显示的菜单和可用权限列表,分别将其存入 store 的 nav(菜单导航) 和 auth(用户可用权限) 中,在用户切换路由时,判断是否存在 auth ,如果不存在,则重新获取,判断当前访问地址 to.Meta.alias 是否在用户可用权限列表中,如果不存在,则提示无权限,否则进入路由。1. 路由与侧边菜单分离
2. 路由切换前进行鉴权
路由定义的部分代码,对每个路由添加了 Meta 属性,用于鉴权。
这里 component 采用了异步引入的方式。
定义路由
import ('@/views/System/Organization.vue'),name: '组织结构',// requiresAuth 用于确认此地址是否需要验证
// alias 用于获取后端返回rbac权限对应的前端路由地址和导航菜单图标
Meta: {requiresAuth: true,alias: 'Pmsadmin/Oragnize/list'}
},{
path: '/system/user',component: () => import ('@/views/System/User.vue'),name: '人员管理',redirect: '/system/user/index',children: [
{
path: '/system/user/index',component: () => import ('@/views/System/UserList.vue'),name: '职员列表',Meta: {requiresAuth: true,alias: 'Pmsadmin/Admin/list'}
}
]
},{
path: '/system/auth',component: () => import ('@/views/System/Auth.vue'),name: '角色管理',alias: 'Pmsadmin/Role/list'}
}
]
}
// ...
路由钩子 beforeEach
{
document.title = `${configs.title} - ${to.name}`;
const {hasAuth,auth} = store.state.user;
// 未拿到权限,则获取
if (!hasAuth) {
store.dispatch('getUserAuth');
console.log('重新获取用户权限');
// next();
}
// 如果未登录,跳转
if (window.localStorage.getItem('IS_LOGIN') === null && to.path !== '/login') {
console.log('未登录状态');
next({
path: '/login',query: {redirect: to.fullPath}
// 将跳转的路由path作为参数,登录成功后跳转到该路由
})
} else {
// 需要鉴权的路由地址
console.log(to,auth.indexOf(to.Meta.alias),auth);
if (to.Meta.requiresAuth) {
if (auth.indexOf(to.Meta.alias) > -1) {
console.log('有权限进入');
next();
} else {
if(auth.length > 0) {
Message.error({
message: '当前用户权限不足,无法访问',showClose: true,});
} else {
next();
}
}
} else {
next();
}
}
});
在 Vuex 的 state 中,定义好 nav 对象
登录用户信息
const user = {
name: '',// 用户名
avatar: '',// 用户头像
auth: [],// 用户权限
hasAuth: false // 是否已经加载用户权限
};
// 导航菜单
const nav = [];
通过 action 异步获取数据
{
const res = await http.post('YOUR_URL',{});
if (res === null) return;
console.log('getUserAuth',res.param);
commit('SET_USER_AUTH',res.param.auth);
commit('SET_SIDE_NAV',res.param.nav);
};
Vuex 中的 mutation 的相关代码
{
state.user.auth = auth.concat('欢迎使用');
state.user.hasAuth = true;
};
// 设置导航菜单
const SET_SIDE_NAV = (state,nav) => {
// 导航菜单
let _nav = [{
name: '欢迎使用',url: "/main",iconCls: 'fa fa-bookmark'
}];
// 权限菜单对应的路由地址
const route = {
"系统管理": {iconCls: 'fa fa-archive',url: ''},"Pmsadmin/Oragnize/list": {iconCls: '',url: '/system/organization'},"Pmsadmin/Admin/list": {iconCls: '',url: '/system/user/index'},"Pmsadmin/Role/list": {iconCls: '',url: '/system/auth'},"Pmsadmin/Log/record": {iconCls: '',url: '/system/logs'},"项目管理": {iconCls: 'fa fa-unlock-alt',"Pmsadmin/Project/list": {iconCls: '',url: '/project/list/index'},"Pmsadmin/House/list": {iconCls: '',url: '/project/house'},"Pmsadmin/Pack/list": {iconCls: '',url: '/project/pack'},"广告位": {iconCls: 'fa fa-edit',"Pmsadmin/Place/list": {iconCls: '',url: '/adsplace/list'},"投诉建议": {iconCls: 'fa fa-tasks',"Pmsadmin/Scategory/list": {iconCls: '',url: '/complain/type'},"Pmsadmin/Complain/list": {iconCls: '',url: '/complain/list'},"Pmsadmin/Suggest/list": {iconCls: '',url: '/complain/suggestion'},"报事报修": {iconCls: 'fa fa-user',"Pmsadmin/Rcategory/list": {iconCls: '',url: '/rcategory/type'},"Pmsadmin/Rcategory/info": {iconCls: '',url: '/rcategory/public'},"Pmsadmin/Repair/list": {iconCls: '',url: '/rcategory/personal'},"便民服务": {iconCls: 'fa fa-external-link',"Pmsadmin/Bcategory/list": {iconCls: '',url: '/bcategory/type'},"Pmsadmin/Service/list": {iconCls: '',url: '/bcategory/list'},"首座推荐": {iconCls: 'fa fa-file-text',"Pmsadmin/stcategory/list": {iconCls: '',url: '/stcategory/type'},"Pmsadmin/Store/list": {iconCls: '',url: '/stcategory/list'},"招商租赁": {iconCls: 'fa fa-leaf',"Pmsadmin/Bussiness/list": {iconCls: '',url: '/bussiness/list'},"Pmsadmin/Company/list": {iconCls: '',url: '/bussiness/company'},"Pmsadmin/Question/list": {iconCls: '',url: '/bussiness/question'},"停车找车": {iconCls: 'fa fa-ra',"Pmsadmin/Cplace/list": {iconCls: '',url: '/cplace/cmanage'},"Pmsadmin/Clist/list": {iconCls: '',url: '/cplace/clist'},"Pmsadmin/Cquestion/list": {iconCls: '',url: '/cplace/cquestion'},};
for (let key in nav) {
let item = nav[key];
let _temp = {};
let subItems = []; // 二级菜单临时数组
if (item.children && item.children.length > 0) {
// 二级菜单
item.children.forEach(subItem => {
subItems.push(Object.assign({},{
name: subItem.name || '',url: route[subItem.url].url || '',iconCls: route[subItem.url].iconCls || '',}))
});
// 一级菜单
_temp = Object.assign({},{
name: item.name || '',url: item.url || '',iconCls: route[item.name].iconCls || '',children: subItems.slice(0)
});
_nav.push(_temp);
}
}
state.nav = _nav;
};
3. 后端接口返回内容
查询成功!","param": {
"nav": {
"1": {
"name": "系统管理","url": "","children": [
{
"name": "组织结构","url": "Pmsadmin/Oragnize/list"
},{
"name": "人员管理","url": "Pmsadmin/Admin/list"
},{
"name": "角色管理","url": "Pmsadmin/Role/list"
},{
"name": "日志管理","url": "Pmsadmin/Log/record"
}
]
},"61": {
"name": "广告位","children": [
{
"name": "广告位列表","url": "Pmsadmin/Place/list"
}
]
}
},"auth": [
"系统管理","Pmsadmin/Oragnize/list","Pmsadmin/Admin/list","Pmsadmin/Role/list","Pmsadmin/Log/record","广告位","Pmsadmin/Place/list"
]
}
}