签入版本

This commit is contained in:
小蚂蚁云
2026-10-25 17:24:27 +08:00
parent 48911cc08f
commit fdbeae092a
1010 changed files with 30682 additions and 95274 deletions
@@ -1,91 +1,62 @@
# 9.7 权限控制
:::tip 概述
前端权限控制通过 `v-perm` / `v-perms` 指令实现按钮级权限控制。
前端权限三层:**路由层**(菜单按角色过滤生成)、**指令层**(v-perm / v-perms 控制按钮)、**逻辑层**(usePermission 组合式函数供 v-if 使用)。超管通配码 `*:*:*` 全通过。
:::
## 权限指令
## 指令
### v-perm(单权限)
```vue
<template>
<!-- 只有拥有 sys:user:add 权限的用户才能看到此按钮 -->
<el-button v-perm="'sys:user:add'" type="primary">新增</el-button>
</template>
```html
<!-- ui/src/views/tool/example/index.vue 实际用法 -->
<el-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">添加案例演示</el-button>
<el-button type="danger" @click="handleDelete()" v-perm="['sys:example:batchDelete']">批量删除</el-button>
```
### v-perms(多权限,满足任一即可)
| 指令 | 语义 | 实现 |
|---|---|---|
| v-perm | 包含**其中任一**权限(some 判定) | directives/permission.ts → hasSomePermission |
| v-perms | 包含**全部**权限(every 判定) | directives/permission.ts → hasEveryPermission |
```vue
<template>
<!-- 拥有 sys:user:add 或 sys:user:update 任一权限即可 -->
<el-button v-perms="['sys:user:add', 'sys:user:update']">操作</el-button>
</template>
```
判定基于 user store 的权限集合;命中通配码 `*:*:*` 直通(超管)。不满足时**移除 DOM 元素**(非隐藏)。
## 指令实现
```typescript
// src/directives/permission.ts
import { usePermission } from '@/hooks/web/usePermission';
export const perm: ObjectDirective = {
mounted(el: HTMLButtonElement, binding) {
const { hasSomePermission } = usePermission();
if (!hasSomePermission(binding.value)) {
el.parentNode.removeChild(el); // 无权限则移除元素
}
},
};
```
## usePermission Hook
:::tip 超级管理员
用户权限列表包含 `*:*:*` 时,表示超级管理员,拥有所有权限。
:::warning 入参必须是数组
`hasSomePermission` 对非数组入参按空权限处理(`Array.isArray(accesses) ? accesses : []`)——写成 `v-perm="'sys:user:add'"` 字符串形式会导致元素**一律被移除**。正确写法 `v-perm="['sys:user:add']"`。
:::
## 组合式函数(usePermission)
```typescript
// src/hooks/web/usePermission.ts
const { hasPermission, hasSomePermission, hasEveryPermission } = usePermission();
export function usePermission() {
const userStore = useUserStore();
/**
* 检查是否拥有指定权限
* 超级管理员(permissions 包含 '*:*:*')拥有所有权限
*/
function hasSomePermission(permission: string | string[]): boolean {
const perms = userStore.permissions;
if (perms.includes('*:*:*')) return true;
if (typeof permission === 'string') {
return perms.includes(permission);
}
return permission.some((p) => perms.includes(p));
}
return { hasSomePermission, hasEveryPermission };
}
// v-if 场景
v-if="hasPermission(['sys:user:add'])"
```
| 函数 | 语义 |
|---|---|
| hasPermission(accesses) | 包含任一(空数组视为通过) |
| hasSomePermission(accesses) | 包含任一(string/array 归一) |
| hasEveryPermission(accesses) | 包含全部 |
## 权限数据来源
用户登录后,`/index/getUser` 接口返回权限列表:
```json
{
"permissions": [
"sys:user:page",
"sys:user:add",
"sys:user:update",
"sys:user:delete",
"sys:role:page",
"..."
]
}
```text
登录后守卫 GetInfo → user store.getPermissions
(后端 LoadIdentity 装配的权限节点集合,含菜单下发的节点码)
```
超级管理员返回 `["*:*:*"]`,拥有所有权限。
## 安全边界提醒
前端权限**只是体验层**:隐藏按钮防误操作,越权请求仍会被后端 Authz 403 拦截。任何接口的安全性以 RouteSpec.Permission 为准,前端漏配指令不构成安全洞,但会造成用户困惑——两侧权限码保持一致是联调检查项(见 [3.9 权限节点配置](/zh/guide/permission.html))。
## 常见问题
| 现象 | 检查 |
|---|---|
| 按钮不显示 | 菜单节点码与 v-perm 值是否一致;角色是否勾选该节点 |
| 指令报错 | v-perm 需要字符串/数组,undefined 直接跳过 |
| 改授权后不生效 | 重新登录(权限集合在登录后装配) |
## 相关
- [5.4 权限管理(RBAC)](/zh/core/rbac.html)