228 lines
7.0 KiB
Markdown
228 lines
7.0 KiB
Markdown
# 权限指令 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` 属性控制。权限节点在菜单管理中配置,需要保持前后端字符串完全一致。`*:*:*` 通配符表示超级管理员。
|