RBAC权限管理(详细)

RBAC权限设计思想

为了达成不同账号(员工、总裁)登录系统后看到不同页面,执行不同功能,RBAC(Role-Based Access control)权限模型,就是根据角色的权限,分配可视页面。

三个关键点:

用户:使用系统的人
角色:使用系统的人是什么职位(员工、经理、总裁)
权限点:职位可以做的事情(左侧菜单栏中的功能模块——>增删改查)
RBAC权限管理(详细)_第1张图片
测试流程
①在员工管理页新增员工这是三要素中的用户
②为新增的员工分配角色
③在公司设置里为角色分配权限

系统中的权限不能随意添加,必须是以开发出来的权限(左侧菜单栏里可实现的页面)
用户和角色之间是一对多的关系,一个人身兼数职。

具体实现

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 = true
      this.curId = row.id
      this.$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'
export default {
  name: 'AssignRole',
  props: {
    id: { type: String,	required: true }
  },
  data() {
    return {
      checkList: [], // 角色列表
      rolesList: []// 用户已有角色
    }
  },
  created() {
  },
  methods: {
    // ------------------------------------提交角色-----------------------------------
    async  submitFn() {
      const resp = await assignRolesAPI({ id: this.id, roleIds: this.rolesList })
      console.log(resp)
      this.$emit('close')
    },
    // ----------------------------------获取角色列表----------------------------------
    async  getRoleListFn() {
      const resp = await getAllRoleAPI({ page: 1, pagesize: 100 })
      console.log(resp)
      this.checkList = resp.data.rows
      const res = await getDetailInfo(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>
  <div class="settings-container">
    <div class="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>
export default {
  name: 'Setting',
  components: {
    assignPermission
  },
  data() {
    return {
      showDialogAssign: false, // 分配权限对话框
  methods: {
    // -----------------------------------------分配权限-------------------------------------
    hAssign(id) {
      this.roleId = id
      this.showDialogAssign = true
      this.$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'
export default {
  props: {
    roleId: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      permissionData: [] // 存储权限数据
    }
  },
  created() {
    this.getPermissionListFn()
  },
  methods: {
    // -------------------------------------------获取权限列表-------------------------------------
    async  getPermissionListFn() {
      const resp = await getPermissionListAPI()
      console.log(resp)
      this.permissionData = tranListToTreeData(resp.data)
      console.log('数组转树', this.permissionData)
    },
    // --------------------------------------------获取角色详情------------------------------------
    async  getRoleDetail() {
      const resp = await getRoleDetail(this.roleId)
      console.log(resp)
      // 回填到树上
      this.$refs.tree.setCheckedKeys(resp.data.permIds)
    },
    // ---------------------------------------------关闭弹层------------------------------------------
    hCancel() {
    // 通过父组件去关闭弹层
      this.$emit('close')
      // 下次根据id获取角色权限数组时,将容器清空,以免影响下次保存
      this.$refs.tree.setCheckedKeys([])
    },
    // ------------------------------------------给角色分配权限--------------------------------------
    async getAssignRoleFn() {
      const pid = this.$refs.tree.getCheckedKeys()
      const resp = await getAssignRoleAPI({ id: this.roleId, permIds: pid })
      console.log(resp)
      this.hCancel()// 通知父组件关闭弹层
      this.$message.success('分配成功')
    }
  }
}
</script>

<style>

</style>

3.页面权限控制

1. 左侧菜单权限控制(不同的用户进来系统之后,看到的菜单是不同的)
 2. 操作按钮权限控制 (页面上的按钮,不同的人也有不同权限)
 3. 权限数据所在位置:下图是管理员登录时,可以看到的权限。![在这里插入图片描述](https://img-blog.csdnimg.cn/5bb6a3a086d6498c911224f19bc2840e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5YuH5pWi54mb54mb77yM5Yay5Yay5Yay,size_11,color_FFFFFF,t_70,g_se,x_16)3.1修改权限数据

只有管理员才可修改权限数据,所以要先新增用户——>分配角色——>分配权限、重新等新用户账号观察权限数据(data.roles.menus,
points)

3.2 动态生成左侧菜单
新用户登录成功页面跳转、进入导航守卫
RBAC权限管理(详细)_第2张图片
3.3 在router/index.js中的路由配置中删除动态路由的部分改为:routes: […constantRoutes]
3.4 在permission.js中引入动态路由,并使用addRoutes动态添加,此时左侧动态路由只剩下静态首页了,可在地址栏输入地址实现跳转(addRoutes的作用)
3.5 从actions中返回菜单项

  async getUserInfo(context) {
      // 1. ajax获取基本信息,包含用户id
      const 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 })
      // 当前用户可以看到的菜单 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 全局getter
  const token = store.getters.token
  if (token) {
    // 有token
    if (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.解决刷新时出现的白屏bug
        next({ ...to, // 保证路由添加完了再进入页面(可理解为重新进一次)
          replace: true// 重新进一次,不保留重复历史
        })
      }else{
      next()
   }
  } else {
    // 没有token
    if (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文件,有一个重置路由方法

// 重置路由
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.按钮级控制

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’是从标识符来的
RBAC权限管理(详细)_第3张图片

你可能感兴趣的:(vue.js,javascript)