一:RBAC权限控制的概念
RBAC:基于角色的权限控制,用户+角色+权限,给角色分配权限,给用户分配角色
分为两部分:1.权限数据管理:管理后台数据,就是用户和角色对应的权限数据
2.权限控制:分菜单权限控制和按钮权限控制,在前台控制用户的路由和界面 显示(主要是这部分,上面的就是搭页面增删改查)
权限控制只能在前端做吗?都可以,主要看用户的动态路由在哪生成
二:菜单权限
1. 这里是要控制用户界面能看到菜单的哪些部分,这些部分是由路由控制的。
2. 用户信息中包含一个routes属性,这个值是个数组,数组里是当前用户被允许的路由名称 这个数据是什么都行,path或者meta中的属性都可以,只要能代表这个路由就行
3.首先要把路由进行拆分为3部分
①静态 (常量) 路由:所有用户都可以访问的路由,不做筛选
②动态 (异步) 路由:代表所有需要进行权限筛选的路由
③任意路由:一般指的是兜底的404
4.所以现在的目的就是:拿到动态路由在用户信息的路由权限数组中筛选,留下有权限的, 把最后筛选完成的路由对象动态添加到路由中
5.那么在哪完成?获取到用户信息的时候就开始。这里就是store/modules/user.js
6.首先我们在仓库中定义一个routes : [ ] ,用来供菜单组件根据路由生成对应菜单,同时定 义存入数据的一个commit方法
7.我们要在获取用户信息的action方法中拿到用户信息中的权限点并进行筛选,为了防止代码 过多影响维护,我们选择在外部定义一个函数来完成筛选的操作
function filterAsyncRoutes(allAsyncRoutes, routeNames) {
const res= allAsyncRoutes.filter((item) => {
if (routeNames.inclues(item.name)) {
if (item.children && item.children.length !== 0) {
item.children = filterAsyncRoutes(item.children, routeNames);
}
return true;
}
});
return res;
}
然后在获取用户信息的action中调用函数+动态添加路由+组织菜单生成需要的路由列表
async getUserInfo({ commit }) {
try {
const result = await reqUserInfo();
if (result.code === 200 || result.code === 20000) {
commit("SET_USERINFO", result.data);
// 拿到用户信息的routes,从所有的动态路由过滤自己的
let userAsyncRoutes = filterAsyncRoutesForUser(
cloneDeep(allAsyncRoutes),
result.data.routes
);
// 动态添加路由到路由器,后期点击可以跳转
router.addRoutes([...userAsyncRoutes, anyRoute]);
// 构造遍历产生菜单的路由
commit(
"SET_MENUROUTES",
constantRoutes.concat(userAsyncRoutes, anyRoute)
);
return "ok";
} else {
return Promise.reject("获取用户信息失败");
}
} catch (error) {
return Promise.reject("请求获取用户信息失败");
}
},
注意:1.上面这段代码中给函数传参的时候用了lodash的cloneDeep方法,因为如果不这样做的话,函数在对子路由进行校验的时候会直接修改源数据,这样allAsyncRoutes异步路由就直接被修改了,切换用户的时候菜单显示会出现问题
2.记得要用router.addRoutes()方法把这两个路由对象动态添加进路由中,因为我们在创建路由的时候只放了常量路由进去
8.现在用户权限范围里的路由表单已经有了,可以去菜单生成组件中循环生成菜单了
9.还有个问题,需要在路由守卫中,获取用户信息后next()中加to,next(to),否则会有白屏,出现在高权限账号退出后低权限登录
三:按钮权限
1.筛选的思路和菜单的思路差不多,但按钮权限要在每个按钮上做判断
2.那问题就变成了,怎么在按钮上判断,在vue2中有两个方向
①写一个自定义指令,在这个自定义指令中判断并决定这个按钮的存在与否
②写一个判断函数放在Vue原型上,结合v-if决定 这个按钮加载情况
方法①
// 写一个自定义指令
Vue.directive("has",{
// 这里是个配置对象,官网有不同属性钩子的含义
inserted(el,bindings){
// el是当前使用这个指令的真实dom,bindings.value是指令后面绑定的数据
if(!store.getters.buttons.includes(bindings.value)){
// 如果没有这个按钮的名字 删除这个按钮
el.parentNode.removeChild(el)
}
}
})
<button v-has="xxx">
方法②:
export function hasBtn(str) {
return store.getters.buttons?.includes(str);
}
/main.js
import {hasBtn} from "/xxx"
new Vue({
beforeCreate(){
Vue.prototype.$has=hasBtn
}
})
<button v-if=" $has("xxx") " />