签入版本
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
# 权限指令 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` 属性控制。权限节点在菜单管理中配置,需要保持前后端字符串完全一致。`*:*:*` 通配符表示超级管理员。
|
||||
Reference in New Issue
Block a user