Skip to content

权限指令 v-perm

说明

v-permv-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 的管理员跳过权限检查。

温馨提示

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

小蚂蚁云团队 · 提供技术支持