0


Vue如何实现动态路由以及动态路由详解

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

** ,模板内容如下

<template>
  <!-- // 分配角色 -->
  <div>
    这里将来会放置多选列表

    <div style="margin-top: 20px; text-align: right">
      <el-button type="primary">确定</el-button>
      <el-button @click="closeDialog">取消</el-button>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      roleIds: []
    }
  },
  methods: {
    closeDialog() {

    }
  }
}
</script>

注册并使用组件

在员工管理的主页employee.vue中,引入上面添加的组件

import AssignRole from './assignRole'

components: {
  // 省略其他....
  AssignRole // 注册组件
},
  
  // 使用
<el-dialog :visiable.sync="showDialogRole">
  <assign-role/>
</el-dialog>  

补充数据项控制弹层的显示隐藏

data(){return{// 省略其它showDialogRole:false}}

员工分配角色-基本交互

目标

完成显示关闭弹层的效果

交互效果-显示弹层

点击分配角色按钮,记录id,显示弹层.

模板

<el-button type="text" size="small" @click="hAssignRole(scope.row)">分配角色</el-button>

代码

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>

子组件

<template>
  <div>
    <el-checkbox-group v-model="roleIds">
      <el-checkbox label="110">管理员</el-checkbox>
      <el-checkbox label="113">开发者</el-checkbox>
      <el-checkbox label="115">人事</el-checkbox>
    </el-checkbox-group>

    <div style="margin-top: 20px; text-align: right">
      <el-button type="primary">确定</el-button>
+      <el-button @click="closeDialog">取消</el-button>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      roleIds: []
    }
  },
  methods: {
    closeDialog() {
+      this.$emit('close')
    }
  }
}
</script>

员工分配角色-获取角色列表并用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="roleIds">
  <el-checkbox label="110">管理员</el-checkbox>
  <el-checkbox label="113">开发者</el-checkbox>
  <el-checkbox label="115">人事</el-checkbox>
</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
 */exportfunctiongetRoles(params){returnrequest({url:'/sys/role',method:'GET',params: params
  })}

在业务组件中调用

src\views\employees\assignRole.vue

<script>import{ getRoles }from'@/api/setting'exportdefault{data(){return{roleIds:[],+     list:[]}},created(){this.loadRoles()},methods:{asyncloadRoles(){const{ data }=awaitgetRoles({page:1,pagesize:100})+this.list = data.rows
    },closeDialog(){this.$emit('close')}}}</script>

在模板中渲染数据

<el-checkbox-group v-model="roleIds">
  <!-- 注意:label决定当前选中的值 -->
  <el-checkbox v-for="item in list" :key="item.id" :label="item.id">
    {{ item.name }}
  </el-checkbox>
</el-checkbox-group>

注意:label决定当前选中的值

小结

员工分配角色-获取数据并回填

目标

​ 如果当前用户已经配置过一些角色数据,应该先把已经配置过的角色数据回显出来: 有些checkbox是选中的!

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

思路

父组件中传入用户id

在打开弹层后,根据用户id去获取当前的角色信息,再回显

父传子-父

定义数据项

data(){return{// 省略其他 ...curEmployeId:'',// 当前的员工编号}}

在点击分配角色时,给它赋值

// 用户点击分配角色hAssignRole(id){this.showDialogRole =truethis.curEmployeId = id
}

模板

给子组件传递props

<el-dialog :visible.sync="showDialogRole" title="权限">
  <assign-role
+        :employee-id="curEmployeId"
        @close="showDialogRole=false"
      />
</el-dialog>

父传子-子接收

<script>import{ getUserDetailById }from'@/api/user'exportdefault{props:{// 用户的id 用来查询当前用户的角色信息employeeId:{type: String,required:true}},created(){this.loadRoles()},methods:{asyncloadRoles(){const res =awaitgetRoles({page:1,pagesize:100})// 保存所有的角色this.list = res.data.rows
      // console.log('loadRoles...........', res)const info =awaitgetUserDetailById(this.employeeId)
      console.log('getUserDetailById...........', info)// 保存这个员工当前的已经有的角色this.roleIds = info.data.roleIds
    },}</script>

员工分配角色-回填问题:created只执行一次

原因

由于子组件在dialog嵌套,所以,它只会创建一次:created只执行一次,后续的显示隐藏操作,都不会导致组件重建,所以:后面打开的内容与第一次是一样的。

解决

方案一: 让弹层隐藏时,把子组件销毁。

<el-dialog
      title="分配角色"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :visible.sync="showDialogRole"
    >
      <assign-role
+       v-if="showDialogRole"
        :employee-id="curEmployeId"
        @close="showDialogRole=false"
      />
    </el-dialog>

优点:简单;缺点:销毁组件,有一定性能问题

方案二:

思路:在父组件中点击分配角色时,直接调用子组件中方法获取数据

给子组件添加引用

<assign-role
        ref="assignRole"
        :employee-id="curEmployeId"
        @close="showDialogRole=false"
      />
// 用户点击分配角色hAssignRole(id){this.showDialogRole =truethis.curEmployeId = id
  console.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 {*}
 */exportfunctionassignRoles(data){returnrequest({url:'/sys/user/assignRoles',
    data,method:'put'})}

在业务代码中确定保存

导入上面定义的api

import{ assignRoles }from'@/api/employees'

给按钮添加点击事件

<template>
  <el-button type="primary" size="small" @click="hSave">确定</el-button>
</template>

补充保存的回调

// 保存当前角色信息asynchSubmit(){const res =awaitassignRoles({id:this.employeeId,roleIds:this.roleIds })
  console.log('保存角色', res)this.$emit('update-close')}

在父组件中,监听事件

<assign-role
        ref="assignRole"
        :employee-id="curEmployeId"
        @update-close="hUpdateClose"
        @close="showDialogRole=false"
      />

hUpdateClose:

// 用户分配角色成功hUpdateClose(){this.showDialogRole =falsethis.loadEmployeeList()}

角色分配权限-整体说明

为什么要给角色分配权限

用户是什么角色,他就具备某些功能

前面的代码中已经给用户加了角色了,那员工到底能做什么事,还是由角色中携带的具体的功能来定的。
image-20210428094452560
权限管理功能比较多,需要封装组件。

角色分配权限-弹层空组件及基本交互

目标

在角色管理模块(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组件并引入使用

<!-- 分配权限的弹层 -->
<el-dialog 
    title="分配权限(一级为路由页面查看权限-二级为按钮操作权限)" :visible.sync="showDialogAssign">
  <assign-permission />
</el-dialog>

补充数据

return{//... 省略其它showDialogAssign:false,// 分配权限对话框}

交互-显示弹层

显示弹层。在按钮在添加点击事件

<el-button size="small" type="success" @click="hAssign">
    分配权限
</el-button>

在回调中设置showDialogAssign为true

methods:{hAssign(){this.showDialogAssign =true}}

交互-隐藏弹层

自定义事件:子传父

<el-dialog
      title="分配权限(一级为路由页面查看权限-二级为按钮操作权限)"
      :visible.sync="showDialogAssign"
    >
      <assign-permission
+        @close="showDialogAssign=false"
      />
    </el-dialog>

在子组件中

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'// 获取权限点列表exportfunctiongetPermissionList(params){returnrequest({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中显示数据

<!-- 权限点数据展示 -->
<el-tree
    :data="permissionData"
    :props="{ label: 'name' }"
/>

注意:props

角色分配权限-设置el-tree的属性

目标

对el-tree进一步设置:

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

    属性配置

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

  1. show-checkbox 显示选择框
  2. default-expand-all 默认展开
  3. check-strictly 设置true,可以关闭父子关联
<!-- 权限点数据展示 -->
      <el-tree
        :data="permissionData"
        :props="{ label: 'name' }"
        default-expand-all
        :show-checkbox="true"
        :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 {*}
 */exportfunctiongetRoleDetail(id){returnrequest({url:`/sys/role/${id}`})}

将id从父传子

在父组件setting.vue中,定义数据项:

data(){return{// 省略其他...roleId:''}}

在点击分配权限时,保存roleId

<el-button size="mini" type="success" @click="hAssign(scope.row.id)">分配权限</el-button>

对应的回调是:

hAssign(id){// 记下来idthis.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()},asyncloadPermissionByRoleId(){// 根据roleId获取当前这个角色已经具备的权限const res =awaitgetRoleDetail(this.roleId)+      console.log('获取当前角色的已有的权限点数据', res.data.permIds)// 回填到树上this.$refs.tree.setCheckedKeys(res.data.permIds)},asyncloadPermissionList(){const res =awaitgetPermissionList()
      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
<!-- 权限点数据展示 -->
    <el-tree
      ref="refTree"
      :data="permissionData"
      :props="{ label: 'name' }"
      :default-expand-all="true"
      :show-checkbox="true"
      :check-strictly="true"
      node-key="id"
    />
  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只执行一次,后续的显示隐藏操作,都不会导致组件重建,所以:后面打开的内容与第一次是一样的。

解决

方案一: 让弹层隐藏时,把子组件销毁。

<el-dialog
      title="分配角色"
      :visible.sync="showDialogRole"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
    >
      <子组件
+       v-if="showDialogAssign"
      />
    </el-dialog>

优点:简单;取到的是最新的数据;

缺点:销毁组件,有一定性能问题,

方案二:通过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
 */exportfunctionassignPerm(data){returnrequest({url:'/sys/role/assignPrem',method:'put',
    data
  })}

调用api分配权限-分析

只需要调用上面定义的api,并传入相关参数即可。

这里的参数有两个:

  1. 当前的角色id是什么? 在点击分配权限时,可以从表格中获取, 父传子
  2. 对应的权限列表id的是什么?通过el-tree组件的getCheckedKeys来获取用户选中的id列表

调用api分配权限-功能实现

asynchSave(){const permIds =this.$refs.tree.getCheckedKeys()// console.log('当前选中的节点数组是', permIds)const res =awaitassignPerm({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([路由配置对象])

作用:动态添加路由配置

示例

// 按钮
<button @click="hAddRoute">addRoute</button>

// 回调
hAddRoute() {
    this.$router.addRoutes([{
        path: '/abc',
        component: () => import('@/views/abc'),
        }])
},

效果

点击了按钮之后,就可以在地址中访问/abc了。

改造代码

  1. 在router/index.js中的路由配置中删除动态路由的部分constcreateRouter=()=>newRouter({// 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.token
  if(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'exportdefault{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 =newVuex.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.routesreturnthis.$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语句。

// 用来获取用户信息的actionasyncgetUserInfo(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 })+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].name
          return 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
    image-20210525002110439

    刷新页面时的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].name
    return 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

**文件,发现一个重置路由方法

// 重置路由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. 重置 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()}})

使用格式

<input v-foucs="'xxxx'" />

解决按钮级别的权限验证

在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'}}})

使用

<el-button
+           v-allow="'import_employee'"
            type="warning"
            size="small"
            @click="$router.push('/import')"
          >导入excel</el-button>

这里的:

'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. 按钮权限控制登录 > 按钮权限数据 > 使用按钮单独的权限标识 去权限数据里面查找自定义指令

本文转载自: https://blog.csdn.net/weixin_45357661/article/details/128639638
版权归原作者 CV_CodeMan 所有, 如有侵权,请联系我们删除。

“Vue如何实现动态路由以及动态路由详解”的评论:

还没有评论