RBAC权限设计思想

目标

了解RBAC的权限模型

背景

为了达成不同的帐号登陆系统后能看到不同的页面,能执行不同的功能的目标,我们有很多种解决方案,RBAC(Role-Based Access control)权限模型 ,也就是基于角色的权限分配解决方案。

其权限模式如下:

三个关键点:

用户: 就是使用系统的人

权限点:这个系统中有多少个功能(例始:有3个页面,每个页面上的有不同的操作)

角色:不同的权限点的集合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CxmEMg8V-1673401451130)(asset/image-20210427155035187.png)]

  1. 给用户分配角色
  2. 给角色分配权限点

实际业务里面:

  1. 先给员工分配一个具体的角色

  2. 然后给角色分配具体的权限点 (工资页面 工资页面下的操作按钮)

    员工就拥有了权限点

员工分配角色-弹层组件

背景

目前系统中已经有一些角色,我们下面要将这些角色分配给不同的员工,让他们进入系统后,做不同的事情。

用户和角色是**1对多**的关系:一个用户可以拥有多个角色,这样他就会具体这多个角色的权限了。比如公司的董事长可以拥有财务主管和保安队长的角色: 董事长可以看财务报表,也可以查看监控。

目标

在员工管理页面中,点击分配角色时,以弹层的方式打开/关闭组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kH47ePb8-1673401451131)(asset/permissionUse/18.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aQ1JfSXk-1673401451131)(asset/permissionUse/03.png)]

思路

  1. 把具体的功能给拆分出去(角色的功能比较复杂,拆分组件会减轻工作量)

  2. 通过弹层控制显示

新建角色管理组件

建立文件**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}

交互效果-关闭弹层

有如下操作会导致弹层关闭:

  1. 用户点击了取消按钮
  2. 用户点击了确定按钮,且操作成功了
  3. 用户点击了弹层的右上角的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)]

思路

  1. 准备静态模板,学习el-checkbox-group
  2. 准备api接口
  3. 发请求获取后端数据,再渲染

学习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)]

思路

  1. 准备权限点接口

  2. 弹框展示之后:

    1. 调用api发请求获取数据;
    2. 对数据进行格式转换(数组转树)
    3. 模板绑定(把数据显示到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进一步设置:

  1. 显示选择框
  2. 默认全部展开
  3. 关闭父子关联

属性配置

https://element.eleme.io/#/zh-CN/component/tree

  1. show-checkbox 显示选择框
  2. default-expand-all 默认展开
  3. check-strictly 设置true,可以关闭父子关联

default-expand-all写法等价于:default-expand-all="true"

效果

角色分配权限-数据回填

目标

当前用户可能有一些已有的权限,需要我们回显出来

思路

  1. 准备api
  2. 组装 当前 参数 ,调用 api获取数据;
  3. 把数据回填显示到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

  1. 给tree补充属性node-key
  1. 调用setCheckedKeys
// 获取角色现有的权限async loadRoleDetail() {const res = await getRoleDetail(this.roleId)console.log('获取角色现有的权限', res.data.permIds)// 回填this.$refs.refTree.setCheckedKeys(res.data.permIds)},

小结

  1. 在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,并传入相关参数即可。

这里的参数有两个:

  1. 当前的角色id是什么? 在点击分配权限时,可以从表格中获取, 父传子
  2. 对应的权限列表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权限设计思想的各个环节,我们给员工分配了角色,给角色又分配了权限点,员工现在已经有了相对应的权限点,接下来我们就可以利用这些权限点做实际的权限控制,在人资项目里,权限的控制有两个地方:

  1. 左侧菜单权限控制(不同的用户进来系统之后,看到的菜单是不同的)
  2. 操作按钮权限控制 (页面上的按钮,不同的人也有不同权限)

权限数据在哪里

在员工管理中新建一个全新的员工数据,然后使用全新的员工账号登录(密码为123456),查看个人信息接口(/api/sys/profile)的返回数据,下图看到的是没有配置任何权限的返回状态,可以看到,roles下的menus和points都为空,此时员工没有任何权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WN9TIr43-1673401451134)(asset/permissionUse/13.png)]

如何修改权限数据

使用管理员账号登录,然后给刚才创建的新员工分配俩个菜单权限和一个操作按钮权限,然后我们再次登录员工账号查看个人信息返回数据

操作步骤:

  1. 权限点管理 > 给员工管理下增加导入,导出 按钮操作权限点

  2. 角色管理 > 新建角色人事总监 > 给角色分配权限 (员工管理,导入,导出)

  3. 员工管理 > 给员工分配人事总监角色

  4. 重新登录新员工账号,查看权限数据,观察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了。

改造代码

  1. 在router/index.js中的路由配置中删除动态路由的部分

    const createRouter = () => new Router({// mode: 'history', // require service supportscrollBehavior: () => ({ y: 0 }),// routes: constantRoutes// 合并动态和静态的路由, ...asyncRoutes- routes: [...constantRoutes, ...asyncRoutes]+ routes: [...constantRoutes]})
  2. 在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()})

验收效果

  1. 左侧的菜单只剩下静态的首页了(后续来解决)

  2. 浏览器手动输入某一个动态路由地址,依旧是可用的,这证明我们其实已经把动态路由添加到我们的路由系统了。

权限应用-动态生成左侧菜单-改写菜单保存位置

问题分析

当前的菜单渲染(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管理菜单数据

  1. 补充模块。在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页改到路由配置的最末尾就可以了

代码

  1. 从route/index.js中的静态路由中删除path:'*'这一项

  2. 在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)]

一种基于角色的设计思想

  1. 给员工配置角色 (一个员工可以拥有多个角色)
  2. 给角色配置权限点 (一个角色可以有多个权限点)

员工只要有了角色之后,就自动拥有了角色绑定的所有权限点

3. 根据权限设计思想对应业务模块

  1. 员工管理
  2. 角色管理
  3. 权限点管理

员工得到权限数据

​ 员工信息接口中有当前员工的所有权限数据

userInfo:{roles: {menus: [],// 菜单权限数据points: [] // 按钮权限数据}}

使用权限数据做具体的权限处理

  1. 菜单权限控制

    登录 > 菜单权限数据 > 和本地的所有的动态路由数据做匹配出具 > 得到根据权限筛选之后的动态路由数据

    1. 添加到路由系统中 (可以根据路径标识渲染组件 addRoutes
    2. 添加到左侧菜单渲染 (vuex管理 + v-for遍历)
  2. 按钮权限控制

    登录 > 按钮权限数据 > 使用按钮单独的权限标识 去权限数据里面查找

    自定义指令