# 權限
項目中集成了三種權限處理方式:
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租戶隔離
- 第三方集成
- 敲敲云集成釘釘