“人生是为心的修行而设立的道场。人生的目的就是在灾难和幸运才考验中磨炼自己的心志,磨炼灵魂,造就一颗美丽的心灵”
----来自《稻盛和夫给年轻人的忠告》
前言
在VueAdminWork框架中一直有一个功能至今还没实现,就是关于 "按钮级权限"也可以叫做 "功能点权限" 地实现。
一开始也想实现这个功能,随便从网上找几个类似的指令集成进去就好,但是我觉得这样千篇一律也没有什么意思,就想着能不能把这个功能做的尽量完善、方便、扩展性强点。最近正好有时间,就想着如何把这个功能实现一下。
总体功能概述
VueAdminWork的权限是基于RBAC权限模型设计而来。什么是RBAC大家可以网上查阅相关的资料,网上有很多这样的介绍。
不同角色的用户拥有不同的菜单权限。所以在这一模型下,我们得把按钮都依附于页面或者菜单下。
所以我们打算设计成两种都能控制的形式
- 基于后端的控制方式
- 基于前端的控制方式
基于后端的就是某个用户拥有不同的角色就相当于拥有不同的菜单权限,拥有不同的菜单那么就拥有不同的按钮。这样就可以实现了此功能
但是,我们还得考虑一点就是不是所有的页面都是受角色控制的,一些公共的页面,如个人中心,登录,工作台等,都是不受控制的,在这一环境下,我们就得使用 "基于前端的控制方式"
基于后端的控制方式具体实现思路
- 根据当前登录用户的角色获取菜单并且把所有的按钮查询出来放在菜单数据下,然后再通过一系列前端的处理,放入 `pinia` 状态中
- 根据按钮的不同展示位置属性进行分类,
有的按钮是要放在页面最顶部,如:新增;
有的按钮是放在 表格 中用来操作每一行的数据,如:编辑、删除等 - 在分类好之后,通过特定的组件容器把按钮展示出来。
说起来不算难,可是要真正实现这一功能,还是需要一点时间的。这里我们还是通过 mock 进行接口数据的模拟
先来看一下数据结构
{ menuUrl: '/system', menuName: '系统管理', iconPrefix: 'iconfont', icon: 'setting', parentPath: '', children: [ { parentPath: '/system', menuUrl: '/system/department', menuName: '部门管理', cacheable: true, buttonList: [ { name: '添加', code: 'add', // admin角色所能展示的按钮 roleCode: 'ROLE_admin', placement: 'top', type: 'primary', }, { name: '编辑', code: 'update', // editor角色所能展示的按钮 roleCode: 'ROLE_editor', placement: 'tableLine', type: 'warning', }, { name: '删除', code: 'delete', // 所有角色所能展示的按钮 roleCode: 'ROLE_all', placement: 'tableLine', type: 'error', }, ], }, }
拿到数据之后我们进行分类
/** * 根据当前用户的 roleCode 返回 某个 path 下所有的 button * @param key 路由 path * @returns buttons */ getButtonsListByRoleCode(key: string) { const userStore = useUserStore() const userRoleCode = userStore.userRoleCode const result = this.permissionButtonList.find((it) => it.key === key) if (result) { if (Array.isArray(result.buttonList) && result.buttonList.length > 0) { return result.buttonList.filter( (it) => userRoleCode.includes(it.roleCode) || it.roleCode === 'ROLE_all' ) } return [] } else { return [] } }, /** * 根据按钮的位置进行归类 * @param key 路由 path * @returns buttons */ getButtonListByPlacement(key: string) { const resultButtonList = this.getButtonsListByRoleCode(key) return resultButtonList.reduce((pre, cur) => { if (!(pre as any)[cur.placement]) { ;(pre as any)[cur.placement] = [] } ;(pre as any)[cur.placement].push(cur) return pre }, {} as ButtonPlacement) },
然后再在页面上进行展示
// 动态展示 tableColumns.push({ title: '操作', key: 'actions', align: 'center', render: (rowData) => { return useRenderAction( buttonModel.tableLine?.map((it) => { return { label: it.name, // onClick: eval(it.code + `.bind(null,rowData)`), onClick: () => { switch (it.code) { case 'update': onUpdateItem(rowData) break case 'delete': onDeleteItem(rowData) break } }, type: it.type, } as TableActionModel }) || [] ) }, })
// 通过组件容器进行展示 <PermissionButtons :buttons="topButtons" @click="onPermissionButtonClick" />
来看一下效果
ROLE_admin 所有的按钮
ROLE_editor 所有的按钮这样基本的功能算是实现了
基于前端的控制方式具体实现思路
这种方式下比较简单,就通过 v-permission 指令实现就好,用法也比较简单,和普通的指令用法一样
<template> // 指令接收的参数如果是多个的话可以是一个数组,如果只有一个就直接是一个字符串就好 <DeleteButton v-permission="['ROLE_admin', 'ROLE_editor']" /> <DeleteButton v-permission="'ROLE_admin'" /> </template>
写在最后
今天的内容比较长,希望大家可以认真的看一下,应该会有收获的。在这种方式下,如果以后对某个用户进行权限控制也是比较方便扩展的,根据当前登录用户的 id 和 角色查询出不同的按钮。当然这还需要前端进一步的处理。此功能我们以后再讲如何实现