# 权限
项目中集成了三种权限处理方式:
1. 通过用户角色来过滤菜单(前端方式控制),菜单和路由分开配置
2. 通过用户角色来过滤菜单(前端方式控制),菜单由路由配置自动生成
3. 通过后台来动态生成路由表(后台方式控制)
## 前端角色权限
**实现原理:** 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登陆后或者其他方式获取用户角色后,通过角色去遍历路由表,获取该角色可以访问的路由表,生成路由表,再通过 `router.addRoutes` 添加到路由实例,实现权限的过滤。
**缺点:** 权限相对不自由,如果后台改动角色,前台也需要跟着改动。适合角色较固定的系统
### 实现
1. 在[项目配置](./settings.md#项目配置)将系统内权限模式改为 `ROLE` 模式
```ts
// ! 改动后需要清空浏览器缓存
const setting: ProjectConfig = {
// 权限模式
permissionMode: PermissionModeEnum.ROLE,
};
```
2. 在路由表配置路由所需的权限,如果不配置,默认可见(见注释)
```ts
import type { AppRouteModule } from '/@/router/types';
import { getParentLayout, LAYOUT } from '/@/router/constant';
import { RoleEnum } from '/@/enums/roleEnum';
import { t } from '/@/hooks/web/useI18n';
const permission: AppRouteModule = {
path: '/permission',
name: 'Permission',
component: LAYOUT,
redirect: '/permission/front/page',
meta: {
icon: 'ion:key-outline',
title: t('routes.demo.permission.permission'),
},
children: [
{
path: 'front',
name: 'PermissionFrontDemo',
component: getParentLayout('PermissionFrontDemo'),
meta: {
title: t('routes.demo.permission.front'),
},
children: [
{
path: 'auth-pageA',
name: 'FrontAuthPageA',
component: () => import('/@/views/demo/permission/front/AuthPageA.vue'),
meta: {
title: t('routes.demo.permission.frontTestA'),
roles: [RoleEnum.SUPER],
},
},
{
path: 'auth-pageB',
name: 'FrontAuthPageB',
component: () => import('/@/views/demo/permission/front/AuthPageB.vue'),
meta: {
title: t('routes.demo.permission.frontTestB'),
roles: [RoleEnum.TEST],
},
},
],
},
],
};
export default permission;
```
3. 在路由钩子内动态判断
```ts
// 这里只列举了主要代码
const routes = await permissionStore.buildRoutesAction();
routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw);
});
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
permissionStore.setDynamicAddedRoute(true);
next(nextData);
```
**permissionStore.buildRoutesAction** 用于**过滤动态路由**
```ts
// 主要代码
if (permissionMode === PermissionModeEnum.ROLE) {
const routeFilter = (route: AppRouteRecordRaw) => {
const { meta } = route;
const { roles } = meta || {};
if (!roles) return true;
return roleList.some((role) => roles.includes(role));
};
routes = filter(asyncRoutes, routeFilter);
routes = routes.filter(routeFilter);
// Convert multi-level routing to level 2 routing
routes = flatMultiLevelRoutes(routes);
}
```
### 动态更换角色
系统提供方便角色相关操作
```ts
import { usePermission } from '/@/hooks/web/usePermission';
import { RoleEnum } from '/@/enums/roleEnum';
export default defineComponent({
setup() {
const { changeRole } = usePermission();
// 更换为test角色
// 动态更改角色,传入角色名称,可以是数组
changeRole(RoleEnum.TEST);
return {};
},
});
```
### 细粒度权限
**函数方式**
[usePermission]还提供了按钮级别的权限控制。
```vue
<template>
<a-button v-if="hasPermission([RoleEnum.TEST, RoleEnum.SUPER])" color="error" class="mx-4">
拥有[test,super]角色权限可见
</a-button>
</template>
<script lang="ts">
import { usePermission } from '/@/hooks/web/usePermission';
import { RoleEnum } from '/@/enums/roleEnum';
export default defineComponent({
setup() {
const { hasPermission } = usePermission();
return { hasPermission };
},
});
</script>
```
**组件方式**
具体查看[权限组件使用](../components/auth.md)
**指令方式**
::: tip
指令方式不能动态更改权限
:::
```html
<a-button v-auth="RoleEnum.SUPER" type="primary" class="mx-4"> 拥有super角色权限可见</a-button>
```
## 后台动态获取
**实现原理:** 是通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构,再通过 `router.addRoutes` 添加到路由实例,实现权限的动态生成。
### 实现
1. 在[项目配置](./settings.md#项目配置)将系统内权限模式改为 `BACK` 模式
```ts
// ! 改动后需要清空浏览器缓存
const setting: ProjectConfig = {
// 权限模式
permissionMode: PermissionModeEnum.BACK,
};
```
2. 路由拦截,与角色权限模式一致
**permissionStore.buildRoutesAction** 用于**过滤动态路由**
```ts
// 主要代码
if (permissionMode === PermissionModeEnum.BACK) {
const { createMessage } = useMessage();
createMessage.loading({
content: t('sys.app.menuLoading'),
duration: 1,
});
// !Simulate to obtain permission codes from the background,
// this function may only need to be executed once, and the actual project can be put at the right time by itself
let routeList: AppRouteRecordRaw[] = [];
try {
this.changePermissionCode();
routeList = (await getMenuList()) as AppRouteRecordRaw[];
} catch (error) {
console.error(error);
}
// Dynamically introduce components
routeList = transformObjToRoute(routeList);
// Background routing to menu structure
const backMenuList = transformRouteToMenu(routeList);
this.setBackMenuList(backMenuList);
routeList = flatMultiLevelRoutes(routeList);
routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];
}
```
**getMenuList 返回值格式**
返回值由多个路由模块组成
::: warning 注意
后端接口返回的数据中必须包含`PageEnum.BASE_HOME`指定的路由(path定义于`src/enums/pageEnum.ts`)
:::
```ts
[
{
path: '/dashboard',
name: 'Dashboard',
component: '/dashboard/welcome/index',
meta: {
title: 'routes.dashboard.welcome',
affix: true,
icon: 'ant-design:home-outlined',
},
},
{
path: '/permission',
name: 'Permission',
component: 'LAYOUT',
redirect: '/permission/front/page',
meta: {
icon: 'carbon:user-role',
title: 'routes.demo.permission.permission',
},
children: [
{
path: 'back',
name: 'PermissionBackDemo',
meta: {
title: 'routes.demo.permission.back',
},
children: [
{
path: 'page',
name: 'BackAuthPage',
component: '/demo/permission/back/index',
meta: {
title: 'routes.demo.permission.backPage',
},
},
{
path: 'btn',
name: 'BackAuthBtn',
component: '/demo/permission/back/Btn',
meta: {
title: 'routes.demo.permission.backBtn',
},
},
],
},
],
},
];
```
### 动态更换菜单
```ts
import { usePermission } from '/@/hooks/web/usePermission';
import { RoleEnum } from '/@/enums/roleEnum';
export default defineComponent({
setup() {
const { changeMenu } = usePermission();
// 更改菜单的实现需要自行去修改
changeMenu();
return {};
},
});
```
### 细粒度权限
**函数方式**
```vue
<template>
<a-button v-if="hasPermission(['20000', '2000010'])" color="error" class="mx-4">
拥有[20000,2000010]code可见
</a-button>
</template>
<script lang="ts">
import { usePermission } from '/@/hooks/web/usePermission';
import { RoleEnum } from '/@/enums/roleEnum';
export default defineComponent({
setup() {
const { hasPermission } = usePermission();
return { hasPermission };
},
});
</script>
```
**组件方式**
具体查看[权限组件使用](../components/auth.md)
**指令方式**
::: tip
指令方式不能动态更改权限
:::
```html
<a-button v-auth="'1000'" type="primary" class="mx-4"> 拥有code ['1000']权限可见 </a-button>
```
### 组件禁用控制
~~~
const {disabled} = usePermission();
<a-checkbox :disabled="disabled('权限code)">{{ item.ruleName }}</a-checkbox>
dynamicDisabled: ({ values }) => {
return !disabled('user:add');
},
~~~
### 如何初始化 code
通常,如需做按钮级别权限,后台会提供相应的 code,或者类型的判断标识。这些编码只需要在登录后获取一次即可。
```ts
import { getPermCodeByUserId } from '/@/api/sys/user';
import { permissionStore } from '/@/store/modules/permission';
async function changePermissionCode(userId: string) {
// 从后台获取当前用户拥有的编码
const codeList = await getPermCodeByUserId({ userId });
permissionStore.commitPermCodeListState(codeList);
}
```
~~~
- 项目介绍
- 常见问题
- 开发环境准备
- 环境准备
- 启动项目
- 切换Vue3路由
- 项目配置详细说明
- 上线部署
- 快速构建&部署
- Docker镜像启动
- 项目配置
- 菜单配置
- 菜单缓存
- 积木报表菜单配置
- 首页配置
- 国际化
- 菜单国际化
- 组件注册
- 项目规范
- 跨域处理
- 样式库
- 图标生成
- package依赖介绍
- 菜单TAB风格
- 备份文档
- 详细构建和配置
- 构建部署1.0
- 切换Mock接口
- 原生路由(作废)
- 原生菜单(作废)
- 页面开启缓存(作废)
- 环境准备1.0
- 数据 mock&联调
- UI组件
- Form 表单组件
- Table 表格
- Modal 弹窗
- Drawer 抽屉组件
- Icon 图标组件
- Button 按钮
- 更多基础组件
- JSelectUser选择用户 ✔
- JSelectPosition岗位选择 ✔
- JSelectDept部门选择 ✔
- JCheckbox ✔
- JImportModal 列表导入弹窗组件
- JInput特殊查询组件 ✔
- JPopup弹窗选择组件 ✔
- JTreeSelect树形下拉框 (异步加载) ✔
- JAreaSelect 省市县级联组件
- JDictSelectTag 字典标签 ✔
- JEllipsis 超长截取显示组件 ✔
- JUpload 上传组件 ✔
- JEasyCron 定时表达式选择组件 ✔
- JInputPopup 多行输入窗口组件 ✔
- JSwitch 开关选择组件 ✔
- JTreeDict 分类字典树形下拉组件 ✔
- JSelectInput 可输入下拉框 ✔
- JEditor 富文本编辑器 ✔
- JMarkdownEditor Markdown编辑器 ✔
- JSearchSelect 字典表的搜索组件 ✔
- JSelectUserByDept 根据部门选择用户 ✔
- JVxeTable
- 组件配置文档
- 自定义组件
- 封装自定义组件
- 自定义组件增强
- 多级联动配置
- 使用示例
- 常见问题解答
- JAreaLinkage 省市县联动组件 ✔
- JCategorySelect 分类字典树 ✔
- JImageUpload 图片上传 ✔
- JSelectMultiple 下拉多选 ✔
- JSelectRole 选择角色 ✔
- JFormContainer 表单组件禁用 ✔
- SuperQuery 高级查询
- UserSelect 高级用户选择组件
- Basic
- Page
- Authority
- PopConfirmButton
- CollapseContainer
- ScrollContainer
- LazyContainer
- CodeEditor
- JsonPreview
- CountDown
- ClickOutSide
- CountTo
- Cropper
- Description
- FlowChart
- Upload
- Tree
- Excel
- Qrcode
- Markdown
- Loading
- Tinymce
- Time
- StrengthMeter
- Verify
- Transition
- VirtualScroll
- ContextMenu
- Preview
- Loading
- 前端权限
- 表单权限
- 显隐控制 ✔
- 禁用控制 ✔
- 列表权限
- 按钮权限控制
- 列字段显隐控制
- 行编辑组件权限
- 显隐控制
- 禁用控制
- 代码生成
- Online在线代码生成
- GUI代码生成
- 代码生成模板介绍
- vue3和vue3Native详细说明
- 深入开发
- 定义Form新组件
- 自定义列表查询
- 自定义表单布局
- 开发笔记
- 组件权限控制
- 使用Antd Vue原生Form
- 自定义图表组件
- 自定义渲染函数
- 如何编写mock接口
- 缓存用法
- 精简版代码制作
- 微前端(qiankun)集成
- 前端小技巧
- 表单整体禁用
- 弹框内下拉框错位
- 界面如何设置响应式
- 抽屉(Drawer)宽度自适应
- 生成菜单脚本
- Online表单
- Online常见问题
- Online表单配置
- 配置参数说明
- 系统标准字段
- 表单类型-主子表|树表
- 自定义查询配置
- Online表单风格
- Online表单删除说明
- Online联合查询配置
- online表单视图功能说明
- Online表单开启评论
- Online表单控件介绍
- 常用基础控件
- 高级关联记录
- Online表单控件配置
- 基本配置
- 控件扩展配置
- 默认值表达式
- 自定义查询配置
- 字段href
- 默认值(填值规则)
- 导入导出自定义规则
- Online表单权限配置
- 字段权限配置与授权
- 按钮权限配置与授权
- 数据权限配置与授权
- 联合查询数据权限规则说明
- 在线增强
- 自定义按钮
- SQL增强
- JS增强
- 按钮触发JS增强
- 列表Api
- 列表操作列前置事件
- 表单Api
- beforeSubmit事件
- loaded事件
- 表单值改变事件【单表/主表】
- 表单值改变事件【从表】
- 表单值改变事件【从改主】
- 控制字段显示与隐藏
- js增强实现下拉联动
- js增强控制下拉树数据
- JS增强 触发弹窗
- JS增强 http请求
- JS增强 方法定义
- 对接表单设计器后需注意
- JAVA增强
- 快速开始
- Online java增强 导入
- Online java增强 导出
- Online java增强 查询
- Online Java增强 http-api
- 表单类
- 列表类
- 其他功能示例
- 导入数据库表支持排除表
- 通过字段Href实现三级联动
- excel数据导入支持校验
- Online报表
- Online报表配置
- 配置成菜单
- 其他功能
- 推送消息
- ISO 8601书写格式
- 系统消息跳转至详情表单
- 菜单【批量申请(自定义)】功能说明
- Online自动化测试
- online AI自动化测试数据制作
- Online AI自动化测试数据制作
- Online AI模型测试用例功能详情
- JAVA后台功能
- saas多租户切换
- 新功能实现saas租户隔离
- 第三方集成
- 敲敲云集成钉钉