Files
djangoadmin-wiki/DjangoAdmin_FastAPI_NaiveVue/zh/volume4/frontend/perm-directive.md
T
2026-09-12 23:12:15 +08:00

228 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 权限指令 v-perm
:::tip 说明
`v-perm` 和 `v-perms` 是项目自定义的 Vue 指令,用于按钮级别的权限控制,无权限时自动从 DOM 中移除元素。底层通过 `usePermission` Hook 检查用户权限列表。
:::
## 指令定义
权限指令定义在 `src/directives/permission.ts` 中:
```typescript
// src/directives/permission.ts
import { ObjectDirective } from 'vue';
import { usePermission } from '@/hooks/web/usePermission';
// v-perm:满足任一权限即可显示
export const perm: ObjectDirective = {
mounted(el: HTMLButtonElement, binding) {
if (binding.value == undefined) return;
const { hasSomePermission } = usePermission();
if (!hasSomePermission(binding.value)) {
el.parentNode.removeChild(el);
}
},
};
// v-perms:必须满足所有权限才显示
export const perms: ObjectDirective = {
mounted(el: HTMLButtonElement, binding) {
if (binding.value == undefined) return;
const { hasEveryPermission } = usePermission();
if (!hasEveryPermission(binding.value)) {
el.parentNode.removeChild(el);
}
},
};
```
指令在 `mounted` 钩子中检查当前用户的权限列表,如果不满足权限条件,直接从 DOM 中移除元素。
## usePermission Hook
权限检查的核心逻辑在 `src/hooks/web/usePermission.ts` 中:
```typescript
// src/hooks/web/usePermission.ts
import { useUserStore } from '@/store/modules/user';
export function usePermission() {
const userStore = useUserStore();
function _somePermissions(accesses: string[]) {
const permissionsList = userStore.getPermissions;
if (permissionsList[0] == '*:*:*') return true; // 超级管理员通配符
return accesses.some((item) => permissionsList.includes(item));
}
// 是否包含其中某个权限
function hasSomePermission(accesses?: string | string[]): boolean {
const permissionsList = userStore.getPermissions;
if (permissionsList[0] === '*:*:*') return true;
const list = Array.isArray(accesses) ? accesses : [];
return list.some((access) => permissionsList.includes(access));
}
// 是否包含指定的所有权限
function hasEveryPermission(accesses?: string | string[]): boolean {
const list = Array.isArray(accesses) ? accesses : [];
const permissionsList = userStore.getPermissions;
if (permissionsList[0] === '*:*:*') return true;
return list.every((access) => permissionsList.includes(access));
}
// 判断是否存在权限(可用于 v-if)
function hasPermission(accesses: string[]): boolean {
if (!accesses || !accesses.length) return true;
return _somePermissions(accesses);
}
return { hasPermission, hasEveryPermission, hasSomePermission };
}
```
权限列表在登录后通过 `userStore.GetInfo()` 从后端获取,存储在 Pinia 的 user store 中。
## 基本用法
### v-perm(满足任一权限)
```vue
<el-button v-perm="['sys:position:add']">添加岗位</el-button>
<el-button v-perm="['sys:position:batchDelete']">删除</el-button>
<el-button v-perm="['sys:position:export']">导出</el-button>
```
### v-perms(满足所有权限)
```vue
<el-button v-perms="['sys:user:add', 'sys:user:import']">高级操作</el-button>
```
### 与 v-if 结合
```typescript
import { usePermission } from '@/hooks/web/usePermission';
const { hasPermission } = usePermission();
// 在模板中使用
const showAddBtn = computed(() => hasPermission(['sys:position:add']));
```
```vue
<el-button v-if="showAddBtn">添加岗位</el-button>
```
## 实际用法(position 模块)
以岗位管理页面为例,实际代码如下:
```vue
<!-- src/views/system/position/index.vue -->
<template>
<BasicTable :columns="columns" :request="loadDataTable" ref="tableRef" :actionColumn="actionColumn">
<template #tableTitle>
<el-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
<template #icon><el-icon><PlusOutlined /></el-icon></template>
添加岗位
</el-button>
<el-button type="danger" @click="handleDelete()" :disabled="!selectionData.length"
v-perm="['sys:position:batchDelete']">
<template #icon><el-icon><Delete /></el-icon></template>
删除
</el-button>
</template>
</BasicTable>
</template>
```
操作列中的权限控制通过 `auth` 属性实现:
```typescript
const actionColumn = reactive({
width: 200,
label: '操作',
fixed: 'right',
render(record) {
return h(TableAction, {
style: 'button',
actions: [
{
label: '编辑',
icon: 'Edit',
type: 'warning',
onClick: handleEdit.bind(null, record),
auth: ['sys:position:update'], // 权限节点
},
{
label: '删除',
icon: 'Delete',
type: 'danger',
onClick: handleDelete.bind(null, record),
auth: ['sys:position:delete'], // 权限节点
},
],
});
},
});
```
## 权限节点命名规范
权限节点格式:`sys:{module}:{action}`
```
sys:position:page —— 分页查询
sys:position:detail —— 查询详情
sys:position:list —— 获取列表
sys:position:add —— 新增
sys:position:update —— 编辑
sys:position:delete —— 删除
sys:position:batchDelete —— 批量删除
sys:position:status —— 设置状态
sys:position:export —— 导出
sys:position:import —— 导入
```
## 与后端对应
前端 `v-perm` 指令的权限节点必须与后端 `@permission_required` 装饰器的权限字符串一致:
```python
# 后端
@router.post('/add')
@permission_required("sys:position:add")
@check_demo
def add(request: Request, data: PositionForm):
return position_service.add(request, data)
```
```vue
<!-- 前端 -->
<el-button v-perm="['sys:position:add']">添加岗位</el-button>
```
## 超级管理员
用户权限列表中包含 `*:*:*` 通配符时,所有权限检查直接返回 `true`,等同于后端用户 ID=1 的管理员跳过权限检查。
:::tip 温馨提示
`v-perm` 指令仅做 UI 层隐藏,真正的权限校验在后端 `@permission_required` 装饰器中。即使前端绕过指令限制直接调用接口,后端仍会校验权限。
:::
## 指令注册
指令在 `src/plugins/` 中通过 `setupDirectives` 全局注册:
```typescript
// 启动时注册(main.ts)
setupDirectives(app); // 注册 v-perm、v-perms 等指令
```
## 总结
`v-perm`(满足任一权限)和 `v-perms`(满足所有权限)指令通过 `usePermission` Hook 检查用户权限列表,无权限时自动从 DOM 移除元素。操作列通过 `TableAction` 组件的 `auth` 属性控制。权限节点在菜单管理中配置,需要保持前后端字符串完全一致。`*:*:*` 通配符表示超级管理员。