自定义指令 hasPermission:按钮级权限控制
概述
按钮级权限控制是后台管理系统的核心需求。传统方式使用 v-if + 响应式 flag 来控制按钮显示,但每个页面都要写权限判断逻辑,代码冗余。通过自定义指令 v-hasPermission,将权限判断逻辑封装为指令,在模板中一行代码即可实现按钮级权限控制。
传统方案的问题
<!-- 传统方案:每个按钮都要写权限判断逻辑 -->
<template>
<el-button v-if="hasAdminPermission" type="primary">编辑</el-button>
<el-button v-if="hasDeletePermission" type="danger">删除</el-button>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 每个页面都要写这些计算属性
const hasAdminPermission = computed(() =>
userStore.permissions.includes('admin')
)
const hasDeletePermission = computed(() =>
userStore.permissions.includes('delete')
)
</script>
vue
问题:权限判断逻辑分散在每个页面中,代码重复、维护困难。
指令方案
<!-- 指令方案:一行代码控制权限 -->
<template>
<el-button v-hasPermission="['admin']" type="primary">编辑</el-button>
<el-button v-hasPermission="['delete']" type="danger">删除</el-button>
<!-- 修饰符:取反,表示非 admin 可见 -->
<el-button v-hasPermission.not="['admin']">普通用户可见</el-button>
</template>
vue
指令实现
// directives/modules/hasPermission.ts
import type { Directive, DirectiveBinding } from 'vue'
import { useUserStore } from '@/stores/user'
/**
* v-hasPermission 指令
* 用法:v-hasPermission="['admin']"
* 修饰符:v-hasPermission.not="['admin']" 表示取反
*
* 无权限时从 DOM 中移除元素
*/
const hasPermission: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<string[]>) {
const { value, modifiers } = binding
const userStore = useUserStore()
// 获取当前用户的角色列表
const roles = userStore.roles || []
if (value && Array.isArray(value) && value.length > 0) {
const hasAuth = checkPermission(roles, value)
// modifiers.not 表示取反:有权限则隐藏,无权限则显示
const shouldShow = modifiers.not ? !hasAuth : hasAuth
if (!shouldShow) {
// 无权限:从 DOM 中移除元素
el.parentNode?.removeChild(el)
}
}
}
}
/**
* 检查用户是否拥有所需权限
* @param userRoles 用户当前角色
* @param requiredRoles 所需角色
*/
function checkPermission(userRoles: string[], requiredRoles: string[]): boolean {
// admin 角色拥有所有权限
if (userRoles.includes('admin')) return true
// 检查是否有交集
return requiredRoles.some(role => userRoles.includes(role))
}
export default hasPermission
typescript
修饰符机制
Vue 指令支持修饰符(modifiers),通过 . 语法添加:
<!-- 基础用法:有 admin 权限才显示 -->
<el-button v-hasPermission="['admin']">管理按钮</el-button>
<!-- .not 修饰符:没有 admin 权限才显示 -->
<el-button v-hasPermission.not="['admin']">普通用户按钮</el-button>
html
// 在指令中通过 binding.modifiers 获取修饰符
mounted(el, binding) {
const { modifiers } = binding
// modifiers.not === true 表示使用了 .not 修饰符
}
typescript
权限数据结构
// stores/user.ts
interface UserState {
roles: string[] // 用户角色列表 ['admin', 'editor']
permissions: string[] // 用户权限列表 ['system:user:add', 'system:menu:edit']
}
// 权限判断优先级:
// 1. admin 角色直接通过所有权限检查
// 2. 检查 roles 与 requiredRoles 是否有交集
typescript
使用场景
<template>
<!-- 单个权限 -->
<el-button v-hasPermission="['admin']">仅管理员可见</el-button>
<!-- 多个权限(满足其一即可) -->
<el-button v-hasPermission="['admin', 'editor']">管理员或编辑可见</el-button>
<!-- 取反:非管理员可见 -->
<el-button v-hasPermission.not="['admin']">普通用户可见</el-button>
<!-- 删除按钮 -->
<el-button v-hasPermission="['system:user:delete']" type="danger">
删除用户
</el-button>
</template>
vue
优化策略
// 优化:仅在角色为 roles 时才执行权限检查
// 避免每次 mounted 都读取 store
const hasPermission: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<string[]>) {
const { value } = binding
// 防御性检查
if (!value || !Array.isArray(value) || value.length === 0) {
return
}
const userStore = useUserStore()
const roles = userStore.roles
// 如果 roles 未加载(如异步获取),跳过检查
if (!roles || roles.length === 0) {
console.warn('v-hasPermission: user roles not loaded')
return
}
const hasAuth = checkPermission(roles, value)
const shouldShow = binding.modifiers.not ? !hasAuth : hasAuth
if (!shouldShow) {
el.parentNode?.removeChild(el)
}
}
}
typescript
实践要点
v-hasPermission通过从 DOM 移除元素实现权限控制,比display:none更安全- admin 角色默认拥有所有权限,无需逐个配置
.not修饰符实现权限取反,适合"非管理员可见"场景- 权限数据通过 Pinia store 管理,指令中直接读取
- 当权限数据异步加载时需防御性检查,避免 roles 为空时误判
↑