权限设计-RBAC的权限设计思想
首先,我们先了解下什么是传统的权限设计

从上面的图中,我们发现,传统的权限设计是对每个人进行单独的权限设置,但这种方式已经不适合目前企业的高效管控权限的发展需求,因为每个人都要单独去设置权限
  基于此,RBAC的权限模型就应运而生了,RBAC(Role-Based Access control) ,也就是基于角色的权限分配解决方案,相对于传统方案,RBAC提供了中间层Role(角色),其权限模式如下

RBAC实现了用户和权限点的分离,想对某个用户设置权限,只需要对该用户设置相应的角色即可,而该角色就拥有了对应的权限,这样一来,权限的分配和设计就做到了极简,高效,当想对用户收回权限时,只需要收回角色即可,接下来,我们就在该项目中实施这一设想
给分配员工角色
**目标**在员工管理页面,分配角色
新建分配角色窗体
在上一节章节中,员工管理的角色功能,我们并没有实现,此章节我们实现给员工分配角色

从上图中,可以看出,用户和角色是**1对多**的关系,即一个用户可以拥有多个角色,比如公司的董事长可以拥有总经理和系统管理员一样的角色
首先,新建分配角色窗体 assign-role.vue     

<template>   <el-dialog title="分配角色" :visible="showRoleDialog">     <!-- el-checkbox-group选中的是 当前用户所拥有的角色  需要绑定 当前用户拥有的角色-->     <el-checkbox-group>       <!-- 选项 -->     </el-checkbox-group>     <el-row slot="footer" type="flex" justify="center">       <el-col :span="6">         <el-button type="primary" size="small">确定</el-button>         <el-button size="small">取消</el-button>       </el-col>     </el-row>   </el-dialog> </template>
  <script> export default {   props: {     showRoleDialog: {       type: Boolean,       default: false     },     // 用户的id 用来查询当前用户的角色信息     userId: {       type: String,       default: null     }   } } </script>
 
 
   | 
 
获取角色列表和当前用户角色
获取所有角色列表
<!-- 分配角色 -->   <el-checkbox-group v-model="roleIds">     <el-checkbox v-for="item in list" :key="item.id" :label="item.id">       {{         item.name       }}     </el-checkbox>   </el-checkbox-group>
 
   | 
 
获取角色列表
import { getRoleList } from '@/api/setting'
  export default {   props: {     showRoleDialog: {       type: Boolean,       default: false     },     userId: {       type: String,       default: null     }   },   data() {     return {       list: [],        roleIds: []     }   },   created() {     this.getRoleList()   },   methods: {          async getRoleList() {       const { rows } = await getRoleList()       this.list = rows     }   } }
  | 
 
获取用户的当前角色  
import { getUserDetailById } from '@/api/user'
   async getUserDetailById(id) {       const { roleIds } = await getUserDetailById(id)       this.roleIds = roleIds    }
  | 
 
点击角色弹出层
// 编辑角色  async  editRole(id) {       this.userId = id // props传值 是异步的       await this.$refs.assignRole.getUserDetailById(id) // 父组件调用子组件方法       this.showRoleDialog = true     },   <!-- 放置角色分配组件 -->  <assign-role ref="assignRole" :show-role-dialog.sync="showRoleDialog" :user-id="userId" />
   | 
 
给员工分配角色
分配角色接口  api/employees.js
 
  export function assignRoles(data) {   return request({     url: '/sys/user/assignRoles',     data,     method: 'put'   }) }
 
  | 
 
确定保存  assign-role
async btnOK() {       await assignRoles({ id: this.userId, roleIds: this.roleIds })              this.$emit('update:showRoleDialog', false)     },
  | 
 
**取消或者关闭 **  assign-role 
btnCancel() {       this.roleIds = []        this.$emit('update:showRoleDialog', false)     }
  | 
 
提交代码
本节任务 分配员工权限
权限点管理页面开发
**目标**: 完成权限点页面的开发和管理
新建权限点管理页面
人已经有了角色, 那么权限是什么
在企业服务中,权限一般分割为 页面访问权限,按钮操作权限,API访问权限
API权限多见于在后端进行拦截,所以我们这一版本只做**页面访问和按钮操作授权/**
由此,我们可以根据业务需求设计权限管理页面

完成权限页面结构 src/views/permission/index.vue
<template>   <div class="dashboard-container">     <div class="app-container">       <!-- 靠右的按钮 -->       <page-tools>         <template v-slot:after>           <el-button type="primary" size="small">添加权限</el-button>         </template>       </page-tools>       <!-- 表格 -->       <el-table border>         <el-table-column align="center" label="名称" />         <el-table-column align="center" label="标识" />         <el-table-column align="center" label="描述" />         <el-table-column align="center" label="操作">           <template>             <el-button type="text">添加</el-button>             <el-button type="text">编辑</el-button>             <el-button type="text">删除</el-button>           </template>         </el-table-column>
        </el-table>     </div>   </div> </template>
   | 
 
封装权限管理的增删改查请求  src/api/permisson.js
 export function getPermissionList(params) {   return request({     url: '/sys/permission',     params   }) }
  export function addPermission(data) {   return request({     url: '/sys/permission',     method: 'post',     data   }) }
 
  export function updatePermission(data) {   return request({     url: `/sys/permission/${data.id}`,     method: 'put',     data   }) }
 
  export function delPermission(id) {   return request({     url: `/sys/permission/${id}`,     method: 'delete'   }) }
  export function getPermissionDetail(id) {   return request({     url: `/sys/permission/${id}`   }) }
 
 
  | 
 
获取权限数据并转化树形
这里,我们通过树形操作方法,将列表转化成层级数据
<script> import { getPermissionList } from '@/api/permission' import { transListToTreeData } from '@/utils' export default { data() {     return {       list: [],       formData: {         name: '', // 名称         code: '', // 标识         description: '', // 描述         type: '', // 类型 该类型 不需要显示 因为点击添加的时候已经知道类型了         pid: '', // 因为做的是树 需要知道添加到哪个节点下了         enVisible: '0' // 开启       },       rules: {         name: [{ required: true, message: '权限名称不能为空', trigger: 'blur' }],         code: [{ required: true, message: '权限标识不能为空', trigger: 'blur' }]       },       showDialog: false     }   },   created() {     this.getPermissionList()   },   computed: {     showText() {       return this.formData.id ? '编辑' : '新增'     }   },   methods: {     async  getPermissionList() {       this.list = transListToTreeData(await getPermissionList(), '0')     }   }
  } </script>
   | 
 
绑定表格数据
<el-table :data="list" border="" row-key="id">         <el-table-column label="名称" prop="name" />         <el-table-column label="标识" prop="code" />         <el-table-column label="描述" prop="description" />         <el-table-column label="操作">           <template slot-scope="{ row }">             <el-button v-if="row.type === 1" type="text" @click="addPermission(row.id, 2)">添加</el-button>             <el-button type="text" @click="editPermission(row.id)">编辑</el-button>             <el-button type="text" @click="delPermission(row.id)"> 删除</el-button>           </template>         </el-table-column>       </el-table>
   | 
 
需要注意的是, 如果需要树表, 需要给el-table配置row-key属性  id
当type为1时为访问权限,type为2时为功能权限
和前面内容一样,我们需要完成 新增权限 / 删除权限 / 编辑权限
新增编辑权限的弹层
新增权限/编辑权限弹层
<!-- 放置一个弹层 用来编辑新增节点 -->  <el-dialog :title="`${showText}权限点`" :visible="showDialog" @close="btnCancel">     <!-- 表单 -->     <el-form ref="perForm" :model="formData" :rules="rules" label-width="120px">       <el-form-item label="权限名称" prop="name">         <el-input v-model="formData.name" style="width:90%" />       </el-form-item>       <el-form-item label="权限标识" prop="code">         <el-input v-model="formData.code" style="width:90%" />       </el-form-item>       <el-form-item label="权限描述">         <el-input v-model="formData.description" style="width:90%" />       </el-form-item>       <el-form-item label="开启">         <el-switch           v-model="formData.enVisible"           active-value="1"           inactive-value="0"         />       </el-form-item>     </el-form>     <el-row slot="footer" type="flex" justify="center">       <el-col :span="6">         <el-button size="small" type="primary" @click="btnOK">确定</el-button>         <el-button size="small" @click="btnCancel">取消</el-button>       </el-col>     </el-row>   </el-dialog>
   | 
 
新增,编辑,删除权限点
新增/删除/编辑逻辑
import { updatePermission, addPermission, getPermissionDetail, delPermission, getPermissionList } from '@/api/permission'   methods: {           async delPermission(id) {       try {         await this.$confirm('确定要删除该数据吗')         await delPermission(id)         this.getPermissionList()         this.$message.success('删除成功')       } catch (error) {         console.log(error)       }     },     btnOK() {       this.$refs.perForm.validate().then(() => {         if (this.formData.id) {           return updatePermission(this.formData)         }         return addPermission(this.formData)       }).then(() => {                  this.$message.success('新增成功')         this.getPermissionList()         this.showDialog = false       })     },     btnCancel() {       this.formData = {         name: '',          code: '',          description: '',          type: '',          pid: '',          enVisible: '0'        }       this.$refs.perForm.resetFields()       this.showDialog = false     },     addPermission(pid, type) {       this.formData.pid = pid       this.formData.type = type       this.showDialog = true     },     async editPermission(id) {              this.formData = await getPermissionDetail(id)       this.showDialog = true     }   }  
  | 
 
提交代码
本节任务: 权限点管理页面开发
给角色分配权限
**目标**: 完成给角色分配权限的业务
新建分配权限弹出层
在公司设置的章节中,我们没有实现分配权限的功能,在这里我们来实现一下
封装分配权限的api  src/api/setting.js
 export function assignPerm(data) {   return request({     url: '/sys/role/assignPrem',     method: 'put',     data   }) }
 
 
  | 
 
给角色分配权限弹出层
<el-dialog title="分配权限" :visible="showPermDialog" @close="btnPermCancel">       <!-- 权限是一颗树 -->       <!-- 将数据绑定到组件上 -->       <!-- check-strictly 如果为true 那表示父子勾选时  不互相关联 如果为false就互相关联 -->       <!-- id作为唯一标识 -->       <el-tree         ref="permTree"         :data="permData"         :props="defaultProps"         :show-checkbox="true"         :check-strictly="true"         :default-expand-all="true"         :default-checked-keys="selectCheck"         node-key="id"       />       <!-- 确定 取消 -->       <el-row slot="footer" type="flex" justify="center">         <el-col :span="6">           <el-button type="primary" size="small" @click="btnPermOK">确定</el-button>           <el-button size="small" @click="btnPermCancel">取消</el-button>         </el-col>       </el-row>     </el-dialog>
   | 
 
定义数据
showPermDialog: false,  defaultProps: {   label: 'name' },  permData: [],  selectCheck: [],  roleId: null 
   | 
 
点击分配权限
<el-button size="small" type="success" @click="assignPerm(row.id)">分配权限</el-button>
   | 
 
给角色分配权限
分配权限/树形数据
 import { transListToTreeData } from '@/utils' import { getPermissionList } from '@/api/permission' methods: {             async assignPerm(id) {       this.permData = tranListToTreeData(await getPermissionList(), '0')        this.roleId = id                     const { permIds } = await getRoleDetail(id)        this.selectCheck = permIds        this.showPermDialog = true     },     async  btnPermOK() {                     await assignPerm({ permIds: this.$refs.permTree.getCheckedKeys(), id: this.roleId })       this.$message.success('分配权限成功')       this.showPermDialog = false     },     btnPermCancel() {       this.selectCheck = []        this.showPermDialog = false     } }
 
  | 
 
提交代码
本节任务 给角色分配权限
前端权限应用-页面访问和菜单
**目标**: 在当前项目应用用户的页面访问权限
权限受控的主体思路
到了最关键的环节,我们设置的权限如何应用?
在上面的几个小节中,我们已经把给用户分配了角色, 给角色分配了权限,那么在用户登录获取资料的时候,会自动查出该用户拥有哪些权限,这个权限需要和我们的菜单还有路由有效结合起来
我们在路由和页面章节中,已经介绍过,动态权限其实就是根据用户的实际权限来访问的,接下来我们操作一下

在权限管理页面中,我们设置了一个标识, 这个标识可以和我们的路由模块进行关联,也就是说,如果用户拥有这个标识,那么用户就可以拥有这个路由模块,如果没有这个标识,就不能访问路由模块
用什么来实现?
vue-router提供了一个叫做addRoutes的API方法,这个方法的含义是动态添加路由规则
思路如下

新建Vuex中管理权限的模块
 在主页模块章节中,我们将用户的资料设置到vuex中,其中便有权限数据,我们可以就此进行操作
我们可以在vuex中新增一个permission模块
src/store/modules/permission.js
 import { constantRoutes } from '@/router'
  const state = {   routes: constantRoutes  } const mutations = {      setRoutes(state, newRoutes) {                         state.routes = [...constantRoutes, ...newRoutes]   } } const actions = {} export default {   namespaced: true,   state,   mutations,   actions }
 
 
  | 
 
在Vuex管理模块中引入permisson模块
import permission from './modules/permission' 	 const store = new Vuex.Store({   modules: {               app,     settings,     user,     permission   },   getters })
   | 
 
Vuex筛选权限路由
 OK, 那么我们在哪将用户的标识和权限进行关联呢 ?

我们可以在这张图中,进一步的进行操作

访问权限的数据在用户属性**menus中,menus**中的标识该怎么和路由对应呢?

  可以将路由模块的根节点**name**属性命名和权限标识一致,这样只要标识能对上,就说明用户拥有了该权限
这一步,在我们命名路由的时候已经操作过了

接下来, vuex的permission中提供一个action,进行关联
import { asyncRoutes, constantRoutes } from '@/router'
  const actions = {                  filterRoutes(context, menus) {     const routes = []          menus.forEach(key => {                     routes.push(...asyncRoutes.filter(item => item.name === key))      })               context.commit('setRoutes', routes)      return routes    }
  | 
 
权限拦截出调用筛选权限Action
在拦截的位置,调用关联action, 获取新增routes,并且addRoutes
  import router from '@/router' import store from '@/store'  import nprogress from 'nprogress'  import 'nprogress/nprogress.css' 
 
 
 
 
 
  const whiteList = ['/login', '/404']  router.beforeEach(async(to, from, next) => {   nprogress.start()    if (store.getters.token) {               if (to.path === '/login') {              next('/')      } else {                            if (!store.getters.userId) {                           const { roles } = await store.dispatch('user/getUserInfo')                                             const routes = await store.dispatch('permission/filterRoutes', roles.menus)                                    router.addRoutes(routes)                   next(to.path)                 } else {         next()       }     }   } else {          if (whiteList.indexOf(to.path) > -1) {              next()     } else {       next('/login')     }   }   nprogress.done()  })
  router.afterEach(() => {   nprogress.done()  })
 
 
  | 
 
静态路由动态路由解除合并
注意: 这里有个非常容易出问题的位置,当我们判断用户是否已经添加路由的前后,不能都是用**next()**,
在添加路由之后应该使用 next(to.path), 否则会使刷新页面之后 权限消失,这属于一个vue-router的已知缺陷
同时,不要忘记,我们将原来的静态路由 + 动态路由合体的模式 改成 只有静态路由  src/router/index.js

  此时,我们已经完成了权限设置的一半, 此时我们发现左侧菜单失去了内容,这是因为左侧菜单读取的是固定的路由,我们要把它换成实时的最新路由
在**src/store/getters.js**配置导出routes
const getters = {   sidebar: state => state.app.sidebar,   device: state => state.app.device,   token: state => state.user.token,   name: state => state.user.userInfo.username,    userId: state => state.user.userInfo.userId,    companyId: state => state.user.userInfo.companyId,    routes: state => state.permission.routes  } export default getters
 
  | 
 
在左侧菜单组件中, 引入routes
computed: {   ...mapGetters([     'sidebar', 'routes'   ]),
  | 
 
 OK,到现在为止,我们已经可以实现不同用户登录的时候,菜单是动态的了
提交代码
本节任务 前端权限应用-页面访问和菜单
登出时,重置路由权限和 404问题
目标: 处理当登出页面时,路由不正确的问题
 上一小节,我们看似完成了访问权限的功能,实则不然,因为当我们登出操作之后,虽然看不到菜单,但是用户实际上可以访问页面,直接在地址栏输入地址就能访问
这是怎么回事?
 这是因为我们前面在addRoutes的时候,一直都是在加,登出的时候,我们并没有删,也没有重置,也就是说,我们之前加的路由在登出之后一直在,这怎么处理?
大家留意我们的router/index.js文件,发现一个重置路由方法
 export function resetRouter() {   const newRouter = createRouter()   router.matcher = newRouter.matcher  }
 
  | 
 
没错,这个方法就是将路由重新实例化,相当于换了一个新的路由,之前**加的路由**自然不存在了,只需要在登出的时候, 处理一下即可
 lgout(context) {      context.commit('removeToken')       context.commit('removeUserInfo')       resetRouter()                  context.commit('permission/setRoutes', [], { root: true })    }
 
  | 
 
除此之外,我们发现在页面刷新的时候,本来应该拥有权限的页面出现了404,这是因为404的匹配权限放在了静态路由,而动态路由在没有addRoutes之前,找不到对应的地址,就会显示404,所以我们需要将404放置到动态路由的最后
src/permission.js
router.addRoutes([...routes, { path: '*', redirect: '/404', hidden: true }]) 
  | 
 
提交代码
功能权限应用
目标: 实现功能权限的应用
功能权限的受控思路
 上小节中,当我们拥有了一个模块,一个页面的访问权限之后,页面中的某些功能,用户可能有,也可能没有,这就是功能权限
这就是上小节,查询出来的数据中的**points**
 比如,我们想对员工管理的删除功能做个权限怎么做?
首先需要在员工管理的权限点下, 新增一个删除权限点,启用

  我们要做的就是看看用户,是否拥有point-user-delete这个point,有就可以让删除能用,没有就隐藏或者禁用
使用Mixin技术将检查方法注入
所以,我们可以采用一个新的技术 mixin(混入)来让所有的组件可以拥有一个公共的方法
src/mixin/checkPermission.js
import store from '@/store' export default {   methods: {     checkPermission(key) {       const { userInfo } = store.state.user       if (userInfo.roles.points && userInfo.roles.points.length) {         return userInfo.roles.points.some(item => item === key)       }       return false     }   } }
 
   | 
 
在员工组件中检查权限点
<el-button :disabled="!checkPermission('POINT-USER-UPDATE')" type="text" size="small" @click="$router.push(`/employees/detail/${obj.row.id}`)">查看</el-button>
 
  | 
 
此时,可以通过配置权限的方式,检查权限的可用性了
提交代码