RBAC权限设计思想
目标
了解RBAC的权限模型
背景
为了达成不同的帐号登陆系统后能看到不同的页面,能执行不同的功能
的目标,我们有很多种解决方案,RBAC(Role-Based Access control)权限模型 ,也就是基于角色的权限分配解决方案。
其权限模式如下:
三个关键点:
用户: 就是使用系统的人
权限点:这个系统中有多少个功能(例始:有3个页面,每个页面上的有不同的操作)
角色:不同的权限点的集合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CxmEMg8V-1673401451130)(asset/image-20210427155035187.png)]
- 给用户分配角色
- 给角色分配权限点
实际业务里面:
先给员工分配一个具体的角色
然后给角色分配具体的权限点 (工资页面 工资页面下的操作按钮)
员工就拥有了权限点
员工分配角色-弹层组件
背景
目前系统中已经有一些角色,我们下面要将这些角色分配给不同的员工,让他们进入系统后,做不同的事情。
用户和角色是**1对多
**的关系:一个用户可以拥有多个角色,这样他就会具体这多个角色的权限了。比如公司的董事长可以拥有财务主管和保安队长的角色: 董事长可以看财务报表,也可以查看监控。
目标
在员工管理页面中,点击分配角色时,以弹层的方式打开/关闭组件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kH47ePb8-1673401451131)(asset/permissionUse/18.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aQ1JfSXk-1673401451131)(asset/permissionUse/03.png)]
思路
把具体的功能给拆分出去(角色的功能比较复杂,拆分组件会减轻工作量)
通过弹层控制显示
新建角色管理组件
建立文件**employees/assignRole.vue
** ,模板内容如下
这里将来会放置多选列表确定取消export default {data() {return {roleIds: []}},methods: {closeDialog() {}}}
注册并使用组件
在员工管理的主页employee.vue中,引入上面添加的组件
import AssignRole from './assignRole'components: {// 省略其他....AssignRole // 注册组件},// 使用
补充数据项控制弹层的显示隐藏
data () {return {// 省略其它showDialogRole: false}}
员工分配角色-基本交互
目标
完成显示关闭弹层的效果
交互效果-显示弹层
点击分配角色按钮,记录id,显示弹层.
模板
分配角色
代码
hEditRole({id}) {console.log('当前要分配角色id是', id)this.showRoleDialog = true}
交互效果-关闭弹层
有如下操作会导致弹层关闭:
- 用户点击了取消按钮
- 用户点击了确定按钮,且操作成功了
- 用户点击了弹层的右上角的X
<el-dialogtitle="分配角色":close-on-click-modal="false":close-on-press-escape="false":visible.sync="showDialogRole"> <assign-role @close="showDialogRole=false" /></el-dialog>
子组件
管理员开发者人事确定+取消export default {data() {return {roleIds: []}},methods: {closeDialog() {+this.$emit('close')}}}
员工分配角色-获取角色列表并用el-checkbox显示
组件:employees/assignRole.vue
目标
发请求获取本系统中所有的角色列表并显示在el-checkbox-group中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SHM57fGx-1673401451132)(asset/07-1619407883065.png)]
思路
- 准备静态模板,学习el-checkbox-group
- 准备api接口
- 发请求获取后端数据,再渲染
学习el-checkbox-group多选框
模板
管理员开发者人事
对于用来表示多选的el-checkbox-group来说:
- v-model的值是数组(表示多选)
- 它的子元素el-checkbox的label属性决定了选中这一项之后值
数据
data () {return {roleIds: [] // 保存当前选中的权限列表}}
准备api获取角色列表
目标是: 要获取所有的角色。但是后端并没有提供现成的接口可以直接获取所有的角色。
注意:我们没有专门用来做当前功能的角色列表,我们可以暂时使用pageSize为100(相当于取第一页,一页100条)获取数据。在文件src\api\setting.js
中,
/** * 获取所有角色信息 * @param {*} params{page, pagesize} * @returns */export function getRoles(params) {return request({url: '/sys/role',method: 'GET',params: params})}
在业务组件中调用
在src\views\employees\assignRole.vue
中
<script>import { getRoles } from '@/api/setting'export default {data() {return {roleIds: [],+ list: []}},created() {this.loadRoles()},methods: {async loadRoles() {const { data } = await getRoles({ page: 1, pagesize: 100 })+ this.list = data.rows},closeDialog() {this.$emit('close')}}}</script>
在模板中渲染数据
{{ item.name }}
注意:label决定当前选中的值
小结
员工分配角色-获取数据并回填
目标
如果当前用户已经配置过一些角色数据,应该先把已经配置过的角色数据回显出来: 有些checkbox是选中的!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YLTZ10fZ-1673401451132)(asset/08-1619407893456.png)]
思路
父组件中传入用户id
在打开弹层后,根据用户id去获取当前的角色信息,再回显
父传子-父
定义数据项
data() {return {// 省略其他 ...curEmployeId: '', // 当前的员工编号}}
在点击分配角色时,给它赋值
// 用户点击分配角色hAssignRole(id) {this.showDialogRole = truethis.curEmployeId = id}
模板
给子组件传递props
父传子-子接收
<script> import { getUserDetailById } from '@/api/user' export default {props: { // 用户的id 用来查询当前用户的角色信息employeeId: {type: String,required: true}},created() {this.loadRoles()},methods: {async loadRoles() {const res = await getRoles({ page: 1, pagesize: 100 })// 保存所有的角色this.list = res.data.rows// console.log('loadRoles...........', res)const info = await getUserDetailById(this.employeeId)console.log('getUserDetailById...........', info)// 保存这个员工当前的已经有的角色this.roleIds = info.data.roleIds}, }</script>
员工分配角色-回填问题:created只执行一次
原因
由于子组件在dialog嵌套,所以,它只会创建一次:created只执行一次,后续的显示隐藏操作,都不会导致组件重建,所以:后面打开的内容与第一次是一样的。
解决
方案一: 让弹层隐藏时,把子组件销毁。
优点:简单;缺点:销毁组件,有一定性能问题
方案二:
思路:在父组件中点击分配角色时,直接调用子组件中方法获取数据
给子组件添加引用
// 用户点击分配角色hAssignRole(id) {this.showDialogRole = truethis.curEmployeId = idconsole.log('父组件', this.curEmployeId)// this.$nextTick// 直接找到子组件,调用方法去获取最新的数据this.$nextTick(() => {this.$refs.assignRole.loadRoles()// console.log('子组件中的props', this.$refs.assignRole.employeeId)})}
把子组价中的created删除
// created() {// // 组件创建时执行一次// this.loadRoles()// },
员工分配角色-保存
目标
用户修改后的分配角色的具体功能保存
思路
封装接口 -> 调用接口
分配角色接口
在**api/employees.js
**文件中,补充一个名为assignRoles的方法
/** * @description: 为用户分配角色 * @param {*} data { id:当前用户id, roleIds:选中的角色id组成的数组 } * @return {*} */export function assignRoles(data) {return request({url: '/sys/user/assignRoles',data,method: 'put'})}
在业务代码中确定保存
导入上面定义的api
import { assignRoles } from '@/api/employees'
给按钮添加点击事件
确定
补充保存的回调
// 保存当前角色信息async hSubmit() {const res = await assignRoles({ id: this.employeeId, roleIds: this.roleIds })console.log('保存角色', res)this.$emit('update-close')}
在父组件中,监听事件
hUpdateClose:
// 用户分配角色成功hUpdateClose() {this.showDialogRole = falsethis.loadEmployeeList()}
角色分配权限-整体说明
为什么要给角色分配权限
用户是什么角色,他就具备某些功能
前面的代码中已经给用户加了角色了,那员工到底能做什么事,还是由角色中携带的具体的功能来定的。
权限管理功能比较多,需要封装组件。
角色分配权限-弹层空组件及基本交互
目标
在角色管理模块(views/setings/setings.vue)中,实现子组件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0zTwqJw2-1673401451132)(asset/分配权限.gif)]
思路
准备弹框 -> 注册事件 -> 提供数据方法
完成给角色分配权限点的业务
封装子组件
在settings下先封装一个assignPermission.vue组件,备用。
|--settings|---------settings.vue # 角色管理主页|---------assignPermission.vue#给角色分配权限
它将会在settings.vue中引用并使用。
在父组件添加弹层并引入子组件
在settings.vue中引入子组件
import assignPermission from './assignPermission'
注册
components: {assignPermission},
在模板中添加el-dialog组件并引入使用
补充数据
return {//... 省略其它showDialogAssign: false, // 分配权限对话框}
交互-显示弹层
显示弹层。在按钮在添加点击事件
分配权限
在回调中设置showDialogAssign为true
methods:{hAssign() {this.showDialogAssign = true} }
交互-隐藏弹层
自定义事件:子传父
在子组件中
methods: {hCancel() {// 通过父组件去关闭弹层this.$emit('close')}}
角色分配权限-获取权限点数据并显示
目标
在组件assignPermission.vue中,获取当前系统中所有的权限点数据,并以树状结构显示出来,目标效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYbV8FQO-1673401451133)(asset/image-20210428100556359.png)]
思路
准备权限点接口
弹框展示之后:
- 调用api发请求获取数据;
- 对数据进行格式转换(数组转树)
- 模板绑定(把数据显示到el-tree上)
准备api
在src\api\permission.js中准备api(这个api在权限点页面已经用过了)
import request from '@/utils/request'// 获取权限点列表export function getPermissionList(params) {return request({url: '/sys/permission',params})}
准备数据项
permissionData: [] // 存储权限数据
发请求获取数据
引入方法
import { getPermissionList } from '@/api/permission'import { tranListToTreeData } from '@/utils/index'
在created中调用
created() {this.loadPermissionList()},async loadPermissionList() {// 发送请求, 获取权限列表const { data } = await getPermissionList()console.log('权限列表的数据是', data)this.permissionData = tranListToTreeData(data)}
在el-tree中显示数据
注意:props
角色分配权限-设置el-tree的属性
目标
对el-tree进一步设置:
- 显示选择框
- 默认全部展开
- 关闭父子关联
属性配置
https://element.eleme.io/#/zh-CN/component/tree
- show-checkbox 显示选择框
- default-expand-all 默认展开
- check-strictly 设置true,可以关闭父子关联
default-expand-all写法等价于:default-expand-all="true"
效果
角色分配权限-数据回填
目标
当前用户可能有一些已有的权限,需要我们回显出来
思路
- 准备api
- 组装 当前 参数 ,调用 api获取数据;
- 把数据回填显示到tree中
准备api
文件: src\api\settings.js 中,补充一个getRoleDetail方法
/** * @description: 获取角色详情 * @param {*} id 角色id * @return {*} */export function getRoleDetail(id) {return request({url: `/sys/role/${id}`})}
将id从父传子
在父组件setting.vue中,定义数据项:
data () {return {// 省略其他...roleId: ''}}
在点击分配权限时,保存roleId
分配权限
对应的回调是:
hAssign(id) { // 记下来id this.roleId = id this.showDialogAssign = true},
在子级件中接收roleId
在assignPerimission.vue中,补充定义props接收roleId值
props: {roleId: {type: String,required: true}}
调用api获取数据
引入前面封装的api
import { assignPerm, + getRoleDetail } from '@/api/setting'
created() {// 调用接口,获取所有的权限点数据this.loadPermissionList()// 调用接口,获取当前这个角色已经具备的权限+ this.loadPermissionByRoleId()},async loadPermissionByRoleId() {// 根据roleId获取当前这个角色已经具备的权限const res = await getRoleDetail(this.roleId)+console.log('获取当前角色的已有的权限点数据', res.data.permIds)// 回填到树上this.$refs.tree.setCheckedKeys(res.data.permIds)},async loadPermissionList() {const res = await getPermissionList()console.log('获取所有的权限点数据', res)// 转成树状结构this.permissionData = tranListToTreeData(res.data)},
将数据回填到el-tree中
已经获取到了数据了,如何把它填充到el-tree中,让某些个复选框处于选中状态?
答: setCheckedKeys + node-key
官网: https://element.eleme.io/#/zh-CN/component/tree#fang-fa
- 给tree补充属性node-key
- 调用setCheckedKeys
// 获取角色现有的权限async loadRoleDetail() {const res = await getRoleDetail(this.roleId)console.log('获取角色现有的权限', res.data.permIds)// 回填this.$refs.refTree.setCheckedKeys(res.data.permIds)},
小结
- 在el-tree组件中通过setCheckedKeys方法将数据回显到el-tree组件中
角色分配权限-数据回填问题:created只执行一次
原因
由于子组件在dialog嵌套,所以,它只会创建一次:created只执行一次,后续的显示隐藏操作,都不会导致组件重建,所以:后面打开的内容与第一次是一样的。
解决
方案一: 让弹层隐藏时,把子组件销毁。
优点:简单;取到的是最新的数据;
缺点:销毁组件,有一定性能问题,
方案二:通过refs来引用子组件,直接调用它的方法来发请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JrmAm932-1673401451133)(asset/image-20210428105921353.png)]
// 用户点击了权限分配hAssign(id) {// alert(id)// 1. 保存角色编号//它会影响子组件中的props,但是,这个传递的过程是异步的this.roleId = id// 2. 弹层this.showDialogAssign = true// 3. 手动调用子组件的loadPermissionByRoleId, 去根据最新的roleId获取权限信息this.$nextTick(() => {this.$refs.permission.loadPermissionByRoleId()})}}
角色分配权限-保存设置
目标
完成权限分配的功能
思路
准备api, 在点击保存时调用
准备api
文件src\api\settings.js中,补充一个api用来分配权限
/** * 给角色分配权限 * @param {*} data {id:角色id, permIds:[] 所有选中的节点的id组成的数组} * @returns */export function assignPerm(data) {return request({url: '/sys/role/assignPrem',method: 'put',data})}
调用api分配权限-分析
只需要调用上面定义的api,并传入相关参数即可。
这里的参数有两个:
- 当前的角色id是什么?
在点击分配权限时,可以从表格中获取, 父传子
- 对应的权限列表id的是什么?
通过el-tree组件的getCheckedKeys来获取用户选中的id列表
调用api分配权限-功能实现
async hSave() {const permIds = this.$refs.tree.getCheckedKeys()// console.log('当前选中的节点数组是', permIds)const res = await assignPerm({id: this.roleId,permIds})console.log('保存角色的权限点的结果是', res)// 提示this.$message.success('保存角色的权限成功')// 关闭弹层this.hCancel()},hCancel() {// 通过父组件去关闭弹层this.$emit('close-dialog')// 清空当前的选择this.$refs.tree.setCheckedKeys([])}
最后,在弹层关闭时,去清空el-tree中用户选中的数据
小结
- el-tree 获取当前选中的节点的keys: getCheckedKeys
- 对于el-tree组件,清空当前的选择: this.$refs.tree.setCheckedKeys([])
认识用户的权限数据
到目前为止,我们实现了RBAC权限设计思想的各个环节,我们给员工分配了角色,给角色又分配了权限点,员工现在已经有了相对应的权限点,接下来我们就可以利用这些权限点做实际的权限控制,在人资项目里,权限的控制有两个地方:
- 左侧菜单权限控制(不同的用户进来系统之后,看到的菜单是不同的)
- 操作按钮权限控制 (页面上的按钮,不同的人也有不同权限)
权限数据在哪里
在员工管理中新建一个全新的员工数据,然后使用全新的员工账号登录(密码为123456),查看个人信息接口(/api/sys/profile)的返回数据,下图看到的是没有配置任何权限的返回状态,可以看到,roles下的menus和points都为空,此时员工没有任何权限
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WN9TIr43-1673401451134)(asset/permissionUse/13.png)]
如何修改权限数据
使用管理员账号登录,然后给刚才创建的新员工分配俩个菜单权限和一个操作按钮权限,然后我们再次登录员工账号查看个人信息返回数据
操作步骤:
权限点管理 > 给员工管理下增加
导入,导出
按钮操作权限点角色管理 > 新建角色人事总监 > 给角色分配权限 (员工管理,导入,导出)
员工管理 > 给员工分配人事总监角色
重新登录新员工账号,查看权限数据,观察data.roles.menus, points项目
权限应用-动态生成左侧菜单-整体分析
分析
登录成功,进入导航守卫:
- 获取个人权限信息
- 生成可以访问的动态路由
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMfe8si3-1673401451134)(asset/image-20210428122059657.png)]
示例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1oHNZ59-1673401451134)(asset/permissionUse/17.png)]
权限应用-动态生成左侧菜单-addRoutes方法
目标
学习vue-router对象中的addRoutes,用它来动态添加路由配置
思路
用户能访问到的页面(路由配置)必须是动态的, 所以要先掌握一个可以动态添加路由地址的API
addRoutes基本使用
格式
router.addRoutes([路由配置对象])或者:this.$router.addRoutes([路由配置对象])
作用:动态添加路由配置
示例
// 按钮// 回调hAddRoute() {this.$router.addRoutes([{path: '/abc',component: () => import('@/views/abc'),}])},
效果
点击了按钮之后,就可以在地址中访问/abc了。
改造代码
在router/index.js中的路由配置中删除动态路由的部分
const createRouter = () => new Router({// mode: 'history', // require service supportscrollBehavior: () => ({ y: 0 }),// routes: constantRoutes// 合并动态和静态的路由, ...asyncRoutes- routes: [...constantRoutes, ...asyncRoutes]+ routes: [...constantRoutes]})
在permission.js中引入,并使用addRoutes动态添加
把之前在router中直接静态写死的动态路由表改造成通过
addRoutes
方法调用添加的形式
// 引入所有的动态路由表(未经过筛选)+ 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) {await store.dispatch('user/getUserInfo')// 改写成动态添加的方式+ router.addRoutes(asyncRoutes)}next()}} else {// 没有tokenif (whiteList.includes(to.path)) {next()} else {next('/login')}}// 结束进度条NProgress.done()})
验收效果
左侧的菜单只剩下静态的首页了(后续来解决)
浏览器手动输入某一个动态路由地址,依旧是可用的,这证明我们其实已经把动态路由添加到我们的路由系统了。
权限应用-动态生成左侧菜单-改写菜单保存位置
问题分析
当前的菜单渲染(src\layout\components\Sidebar\index.vue)使用的数据:this.$router.options.routes
这个数据是固定,我们通过addRoutes添加的路由表只存在内存中,并不会改变this.$router.options.routes
如果我们希望在调用addRoutes方法之后,要路由数据立刻反映到菜单中,我们需要想一个额外的方法,思考一下,vue开发中,哪个技术可以保证响应式特性还可以动态修改? vuex!
目标
在vuex中保存菜单数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Q4i5Ywp-1673401451134)(asset/image-20210428122540218.png)]
定义vuex管理菜单数据
- 补充模块。在
src/store/modules
下补充menu.js模块:- 定义数据menuList
- 修改数据的方法setMenuList
// 导入静态路由import { constantRoutes } from '@/router'export default {namespaced: true,state: {// 先以静态路由作为菜单数据的初始值menuList: [...constantRoutes]},mutations: {setMenuList(state, asyncRoutes) {// 将动态路由和静态路由组合起来state.menuList = [...constantRoutes, ...asyncRoutes]}}}
当然,要在src/store/index.js中注册这个模块
+ import menu from './modules/menu'Vue.use(Vuex)const store = new Vuex.Store({modules: {app,settings,user,+ menu},getters})
2. 提交setMenuList生成完整的菜单数据
修改src/permission.js中的代码
if (!store.getters.userId) {await store.dispatch('user/getUserInfo')// 动态添加可以访问的路由设置router.addRoutes(asyncRoutes)// 根据用户实际能访问几个页面来决定从整体8个路由设置// 中,过滤中出来几个,然后保存到vuex中store.commit('menu/setMenuList', asyncRoutes)}
3. 菜单生成部分改写使用vuex中的数据
在src\layout\components\Sidebar\index.vue文件中,修改
routes() {// 拿到的是一个完整的包含了静态路由和动态路由的数据结构// return this.$router.options.routesreturn this.$store.state.menu.menuList}
小结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FQFaCeh7-1673401451135)(asset/image-20210525124226388.png)]
权限应用-使用权限数据做过滤处理
目标
上一步我们实现了:
- 把动态路由通过addRoutes动态添加到了路由系统里
- 把动态路由保存到vuex的menu中
但是我们没有和权限数据做搭配,接下来我们通过接口返回的权限数据对动态菜单做过滤处理,以确定完成菜单与用户权限相关。
过滤的思路
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQ1M2Yrk-1673401451135)(asset/image-20210525000107003.png)]
过滤使用name作为标识,对照下标检查路由name是否一致
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c8GBacDW-1673401451135)(asset/image-20210428153438963.png)]
后端的接口约定如下:
- 页面名字: 员工 标识: employees
- 页面名字: 权限 标识: permissions
- 页面名字: 组织架构 标识: departments
- 页面名字: 设置 标识: settings
- 页面名字: 工资 标识: salarys
- 页面名字: 审核 标识: approvals
- 页面名字: 考勤 标识: attendances
- 页面名字: 社保 标识: social_securitys
从actions中返回菜单项
用户能访问哪些页面是通过actions获取到的,只需要从action中返回即可。
修改 store/modules/user.js
,补充return语句。
// 用来获取用户信息的actionasync getUserInfo(context) {// 1. ajax获取基本信息,包含用户idconst rs = await getUserInfoApi()console.log('用来获取用户信息的,', rs)// 2. 根据用户id(rs.data.userId)再发请求,获取详情(包含头像)const info = await getUserDetailById(rs.data.userId)console.log('获取详情', info.data)// 把上边获取的两份合并在一起,保存到vuex中context.commit('setUserInfo', { ...info.data, ...rs.data })+ return rs.data.roles.menus},
在permission.js中获取action的返回值并过滤
在src/permission.js
中
if (!store.getters.userId) {// 有token,要去的不是login,就直接放行// 进一步获取用户信息// 发ajax---派发action来做const menus = await store.dispatch('user/getUserInfo')console.log('当前用户能访问的页面', menus)console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)// 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面const filterRoutes = asyncRoutes.filter(route => {const routeName = route.children[0].namereturn menus.includes(routeName)})// 一定要在进入主页之前去获取用户信息// addRoutes用来动态添加路由配置// 只有在这里设置了补充了路由配置,才可能去访问页面// 它们不会出现左侧router.addRoutes(filterRoutes)// 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue// 生成左侧菜单时,也应该去vuex中拿store.commit('menu/setMenuList', filterRoutes)}
效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EgzHy9V4-1673401451135)(asset/permissionUse/15.png)]
小结
- 从actions中获取返回值
asyncRoutes.filter
刷新页面时的bug修复
问题
如果我们刷新浏览器,会发现跳到了404页面
对于addRoute添加的路由,在刷新时会白屏
原因
现在我们的路由设置中的404页处在中间位置而不是所有路由的末尾了。
解决
把404页改到路由配置的最末尾就可以了
代码
从route/index.js中的静态路由中删除
path:'*'
这一项在permission.js中补充在最后
// if(没有userInfo) {if (!store.getters.userId) {// 有token,要去的不是login,就直接放行// 进一步获取用户信息// 发ajax---派发action来做const menus = await store.dispatch('user/getUserInfo')console.log('当前用户能访问的页面', menus)console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)// 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面const filterRoutes = asyncRoutes.filter(route => {const routeName = route.children[0].namereturn menus.includes(routeName)})// 一定要在进入主页之前去获取用户信息// 把404加到最后一条filterRoutes.push( // 404 page must be placed at the end !!!{ path: '*', redirect: '/404', hidden: true })// addRoutes用来动态添加路由配置// 只有在这里设置了补充了路由配置,才可能去访问页面// 它们不会出现左侧router.addRoutes(filterRoutes)// 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue// 生成左侧菜单时,也应该去vuex中拿store.commit('menu/setMenuList', filterRoutes)// 解决刷新出现的白屏bugnext({...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)replace: true // 重进一次, 不保留重复历史})} else {next()}
退出登录时重置路由
问题
退出后,再次登陆,发现菜单异常 (控制台有输出说路由重复);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xfeMibRM-1673401451136)(asset/image-20210525161618731.png)]
原因
路由设置是通过router.addRoutes(filterRoutes)
来添加的,退出时,并没有清空,再次登陆,又加了一次,所以有重复。
需要将路由权限重置 (恢复默认) 将来登录后再次追加才可以,不然的话,就会重复添加
解决
我们的**router/index.js
**文件,发现一个重置路由方法
// 重置路由export function resetRouter() {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. 重置 vuex 中的路由信息 只保留每个用户都一样的静态路由数据//在moudules中的一个module中去调用另一个modules中的mutation要加{root:true}// context.commit('setMenuList', [], { root: true })}
权限应用-按钮级控制-分析
目标
员工A和员工B都可以访问同一个页面(以员工管理为例),但是员工A可以导出excel,员工B就不可以导出excel
思路
用户登陆成功后,用户可以访问的按钮级别权限保存在points数组中。而这个数据我们是保存在vuex中的,所以,就可以在项目的任意地方来中访问。
- 如果某个按钮上的标识在points出现,则可以显示出来
权限应用-按钮级控制-自定义指令
指令: v-for, v-if…
自定义指令:自己定义的指令,因为本身指令不够用,所以我们需要自已去定义。
用它来做按钮级别权限控制
复习一下自定义指令
注册格式
// 注册一个全局自定义指令 `v-focus`Vue.directive('focus', {// 当被绑定的元素插入到 DOM 中时inserted会自动执行inserted: function(el, binding) {// v-focus="'abc'"===> binding.value = 'abc'console.log('focus.... binding', binding.value)// 聚焦元素el.focus()}})
使用格式
解决按钮级别的权限验证
在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.parentNode.removeChild(el)// el.style.display = 'none'}}})
使用
导入excel
这里的:'import_employee'
是从标识符来的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OHL0NZor-1673401451136)(asset/image-20210428165654008.png)]
权限控制流程重点梳理总结
业务场景
公司里有不同的职能部门,都在用同一套系统 ,不一样部门的人员进入系统里面需要操作的事情是不一样的
必定需要根据不同的员工角色配置不同的权限
RBAC权限设计思想
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zjgpc0W3-1673401451136)(asset/image-20210427155035187.png)]
一种基于角色的设计思想
- 给员工配置角色 (一个员工可以拥有多个角色)
- 给角色配置权限点 (一个角色可以有多个权限点)
员工只要有了角色之后,就自动拥有了角色绑定的所有权限点
3. 根据权限设计思想对应业务模块
- 员工管理
- 角色管理
- 权限点管理
员工得到权限数据
员工信息接口中有当前员工的所有权限数据
userInfo:{roles: {menus: [],// 菜单权限数据points: [] // 按钮权限数据}}
使用权限数据做具体的权限处理
菜单权限控制
登录 > 菜单权限数据 > 和本地的所有的动态路由数据做匹配出具 > 得到根据权限筛选之后的动态路由数据
- 添加到路由系统中 (可以根据路径标识渲染组件 addRoutes)
- 添加到左侧菜单渲染 (vuex管理 + v-for遍历)
按钮权限控制
登录 > 按钮权限数据 > 使用按钮单独的权限标识 去权限数据里面查找
自定义指令