RBAC权限设计思想
为了达成不同账号(员工、总裁)登录系统后看到不同页面,执行不同功能,RBAC(Role-Based Access control)权限模型,就是根据角色的权限,分配可视页面。
三个关键点:
用户:使用系统的人
角色:使用系统的人是什么职位(员工、经理、总裁)
权限点:职位可以做的事情(左侧菜单栏中的功能模块——>增删改查)
测试流程:
①在员工管理页新增员工这是三要素中的用户
②为新增的员工分配角色
③在公司设置里为角色分配权限
💢系统中的权限不能随意添加,必须是以开发出来的权限(左侧菜单栏里可实现的页面)
💢用户和角色之间是一对多的关系,一个人身兼数职。
具体实现
1.实现分配角色
点击分配角色、弹出框,框里含有已有角色列表,点击分配角色时将id传过去,根据id显示当前用户已有的角色。
分配角色父组件src/employees/employee.vue
<template slot-scope="scope"><el-button type="text" size="smell" @click="assignFn(scope.row)">分配角色</el-button></template><el-dialog
title="分配角色":visible.sync="showDialogRole":close-on-press-escape="false":close-on-click-modal="false"
@close="showDialogRole=false"><assign-role:id="curId" ref="assignRole" @close="showDialogRole=false"/></el-dialog>//import 导入复原框子组件// -------------------------------------------分配角色----------------------------------assignFn(row){this.showDialogRole=truethis.curId= row.idthis.$nextTick(()=>{this.$refs.assignRole.getRoleListFn()})}
分配角色的子组件:employees/assignRole.vue
<template><div><el-checkbox-group v-model="rolesList"><el-checkbox v-for="item in checkList":key="item.id":label="item.id">{{ item.name}}</el-checkbox></el-checkbox-group><div style="margin-top: 20px; text-align: right"><el-button type="primary" @click="submitFn">确定</el-button><el-button @click="closeDialog">取消</el-button></div></div></template><script>import{ getAllRoleAPI}from'@/api/settings'import{ getDetailInfo}from'@/api/user.js'import{ assignRolesAPI}from'@/api/employees.js'exportdefault{
name:'AssignRole',
props:{
id:{ type: String, required:true}},data(){return{
checkList:[],// 角色列表
rolesList:[]// 用户已有角色}},created(){},
methods:{// ------------------------------------提交角色-----------------------------------asyncsubmitFn(){const resp=awaitassignRolesAPI({ id:this.id, roleIds:this.rolesList})
console.log(resp)this.$emit('close')},// ----------------------------------获取角色列表----------------------------------asyncgetRoleListFn(){const resp=awaitgetAllRoleAPI({ page:1, pagesize:100})
console.log(resp)this.checkList= resp.data.rowsconst res=awaitgetDetailInfo(this.id)
console.log(res)this.rolesList= res.data.roleIds},// -------------------------------------取消按钮------------------------------------closeDialog(){this.$emit('close')}}}</script>
💢《el-checkbox-group v-model=“rolesList”》中v-model绑定的值是数组表示可多选。
💢在模板中渲染数据时
{{ item.name }}
其中label决定当前选中的值,{{要展示的角色名称}}
2.实现分配权限
父组件中(views/setings/setings.vue):准备弹框 -> 注册事件 -> 提供数据方法
<template><divclass="settings-container"><divclass="app-container"><el-card><!-- 具体页面结构--><el-tabs><!-- 放置页签--><el-tab-pane label="角色管理"><!-- 表格--><el-table:data="tableList"><el-table-column label="操作"><!-- scope只是插槽占位置的名字而已,重要的是里面的.row这是每一行的对象,是固定写法--><template slot-scope="scope"><el-button size="small" type="success" @click="hAssign(scope.row.id)">分配权限</el-button></template></el-table-column></el-table><el-row type="flex" justify="center" align="middle" style="height: 60px"></el-row></el-tab-pane></el-tabs></el-card><!-- 分配权限的弹层--><el-dialog
title="分配权限(一级为路由页面查看权限-二级为按钮操作权限)":visible.sync="showDialogAssign"><assign-permission ref="assignPermission":role-id="roleId" @close="showDialogAssign=false"/></el-dialog></div></div></template><script>exportdefault{
name:'Setting',
components:{
assignPermission},data(){return{
showDialogAssign:false,// 分配权限对话框
methods:{// -----------------------------------------分配权限-------------------------------------hAssign(id){this.roleId= idthis.showDialogAssign=truethis.$nextTick(()=>{this.$refs.assignPermission.getRoleDetail()})}}}</script>
子组件中(settings/assignPermission.vue):
<template><div><!-- 权限点数据展示:check-strictly 设置true,可以关闭父子关联--><el-tree
ref="tree":data="permissionData":props="{ label: 'name' }"
node-key="id"default-expand-all:show-checkbox="true":check-strictly="true"/><div style="text-align:right;"><el-button @click="hCancel">取消</el-button><el-button type="primary" @click="getAssignRoleFn">确定</el-button></div></div></template><script>import{ getPermissionListAPI}from'@/api/permissions.js'import{ tranListToTreeData}from'@/utils/index.js'import{ getRoleDetail, getAssignRoleAPI}from'@/api/settings.js'exportdefault{
props:{
roleId:{
type: String,
required:true}},data(){return{
permissionData:[]// 存储权限数据}},created(){this.getPermissionListFn()},
methods:{// -------------------------------------------获取权限列表-------------------------------------asyncgetPermissionListFn(){const resp=awaitgetPermissionListAPI()
console.log(resp)this.permissionData=tranListToTreeData(resp.data)
console.log('数组转树',this.permissionData)},// --------------------------------------------获取角色详情------------------------------------asyncgetRoleDetail(){const resp=awaitgetRoleDetail(this.roleId)
console.log(resp)// 回填到树上this.$refs.tree.setCheckedKeys(resp.data.permIds)},// ---------------------------------------------关闭弹层------------------------------------------hCancel(){// 通过父组件去关闭弹层this.$emit('close')// 下次根据id获取角色权限数组时,将容器清空,以免影响下次保存this.$refs.tree.setCheckedKeys([])},// ------------------------------------------给角色分配权限--------------------------------------asyncgetAssignRoleFn(){const pid=this.$refs.tree.getCheckedKeys()const resp=awaitgetAssignRoleAPI({ id:this.roleId, permIds: pid})
console.log(resp)this.hCancel()// 通知父组件关闭弹层this.$message.success('分配成功')}}}</script><style></style>
3.页面权限控制
1. 左侧菜单权限控制(不同的用户进来系统之后,看到的菜单是不同的)
2. 操作按钮权限控制 (页面上的按钮,不同的人也有不同权限)
3. 权限数据所在位置:下图是管理员登录时,可以看到的权限。3.1修改权限数据
只有管理员才可修改权限数据,所以要先新增用户——>分配角色——>分配权限、重新等新用户账号观察权限数据(data.roles.menus,
points)
3.2 动态生成左侧菜单
新用户登录成功页面跳转、进入导航守卫
3.3 在router/index.js中的路由配置中删除动态路由的部分改为:routes: […constantRoutes]
3.4 在permission.js中引入动态路由,并使用addRoutes动态添加,此时左侧动态路由只剩下静态首页了,可在地址栏输入地址实现跳转(addRoutes的作用)
3.5 从actions中返回菜单项
asyncgetUserInfo(context){// 1. ajax获取基本信息,包含用户idconst rs=awaitgetUserInfoApi()
console.log('用来获取用户信息的,', rs)// 2. 根据用户id(rs.data.userId)再发请求,获取详情(包含头像)const info=awaitgetUserDetailById(rs.data.userId)
console.log('获取详情', info.data)// 把上边获取的两份合并在一起,保存到vuex中
context.commit('setUserInfo',{...info.data,...rs.data})// 当前用户可以看到的菜单 res.data.roles.menus+return rs.data.roles.menus},
3.6 在permission.js中获取action的返回值并过滤
/引入所有的动态路由表(未经过筛选)+import router,{ asyncRoutes}from'@/router'const whiteList=['/login','/404']
router.beforeEach(async(to,from, next)=>{// 开启进度条
NProgress.start()// 获取本地token 全局getterconst token= store.getters.tokenif(token){// 有tokenif(to.path==='/login'){next('/')}else{if(!store.getters.userId){+const menus=await store.dispatch('user/getUserInfo')//根据权限过滤动态数组+const filterRoutes= asyncRoutes.filter(route=>{+const routeName= route.children[0].name+return menus.includes(routeName)+})// 1.改写成动态添加的方式+ router.addRoutes(filterRoutes)//2. 生成左侧菜单时,也应该去vuex中拿+ store.commit('menu/setMenuList', filterRoutes)+//3.解决刷新时出现的白屏bugnext({...to,// 保证路由添加完了再进入页面(可理解为重新进一次)
replace:true// 重新进一次,不保留重复历史})}else{next()}}else{// 没有tokenif(whiteList.includes(to.path)){next()}else{next('/login')}}// 结束进度条
NProgress.done()})
💢当前的菜单(src\layout\components\Sidebar\index.vue)使用的数据:this.$router.options.routes **可以拿到当前路由配置,设置的路由表数据**但是这个数据是固定的,所以将此数据换为 this.$router.options.routes就可以动态拿到路由表的数据。
💢如果想调用addRoutes方法之后,路由表数据立刻在左侧菜单栏中显示,那就将动态路由菜单保存在vuex中
3.7修复bug
3.7.1解决刷新出现的白屏(路由守卫中的 //3…)
3.7.2退出后,再次登陆,发现菜单异常 (控制台有输出说路由重复)
原因:路由设置是通过router.addRoutes(filterRoutes)来添加的,退出时,并没有清空,再次登陆,又加了一次,所以有重复。
需要将路由权限重置 (恢复默认) 将来登录后再次追加才可以,不然的话,就会重复添加
解决:
router/index.js文件,有一个重置路由方法
// 重置路由exportfunctionresetRouter(){const newRouter=createRouter()
router.matcher= newRouter.matcher// 重新设置路由的可匹配路径}
在登出的时候, 调用一下即可store/modules/user.js
import{ resetRouter}from'@/router'// 退出的action操作logout(context){// 1. 移除vuex个人信息
context.commit('removeUserInfo')// 2. 移除token信息
context.commit('removeToken')// 3. 重置路由+resetRouter()}
4.按钮级控制
4.1 自定义指令:自己定义的指令,因为本身指令不够用,所以我们需要自已去定义。
4.2 解决按钮级别的权限验证 ——在main.js中,定义全局指令
// 注册一个全局自定义指令 `v-allow`
Vue.directive('allow',{inserted:function(el, binding){// 从vuex中取出points,const points= store.state.user.userInfo.roles.points// 如果points有binding.value则显示if(points.includes(binding.value)){// console.log('判断这个元素是否会显示', el, binding.value)}else{// el.style.display = 'none',这个只是隐藏了,懂业务的通过检查还可以显示,所以要销毁
el.parentNode.removeChild(el)}}})
使用
<el-button+ v-allow="'import_employee'"
type="warning"
size="small"
@click="$router.push('/import')">导入excel</el-button>
※这里的:'import_employee’是从标识符来的