Vue3 自定义指令,用来根据用户权限动态控制按钮/元素的显示/禁用,这是后台管理系统最常用的权限方案。
一套完整的完整可直接使用的实现,包含:指令注册、权限判断、使用方式、全局权限管理。
核心思路
- 定义全局自定义指令
v-permission - 指令接收权限标识(如
user:add、role:edit) - 从全局状态(Pinia/Vuex)获取用户拥有的权限列表
- 判断当前元素所需权限是否在用户权限内,不符合则移除元素/禁用按钮
完整实现步骤
1. 先创建权限状态管理(Pinia)
存储用户的权限列表,全局共享(如果用Vuex,逻辑完全一致)
// src/stores/permission.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
// 权限Store
export const usePermissionStore = defineStore('permission', () => {
// 用户拥有的权限标识列表(登录后从接口获取)
const permissions = ref<string[]>([])
// 设置权限
const setPermissions = (list: string[]) => {
permissions.value = list
}
return { permissions, setPermissions }
})
2. 注册自定义指令 v-permission
创建指令文件,支持两种模式:
disabled:权限不足时禁用按钮- 默认:权限不足时直接隐藏元素
// src/directives/permission.ts
import type { Directive } from 'vue'
import { usePermissionStore } from '@/stores/permission'
// 定义指令配置
const permissionDirective: Directive = {
mounted(el, binding) {
const permissionStore = usePermissionStore()
// 获取指令传入的权限值
const { value } = binding
if (!value || !Array.isArray(value)) {
console.warn('v-permission 必须传入权限标识数组,如 v-permission="[\'user:add\']"')
return
}
// 判断用户是否拥有该权限
const hasPermission = permissionStore.permissions.some(perm => value.includes(perm))
if (!hasPermission) {
// 支持两种模式:禁用 / 隐藏
if (binding.modifiers.disabled) {
// 禁用按钮
el.disabled = true
el.classList.add('is-disabled')
} else {
// 直接隐藏元素
el.parentNode?.removeChild(el)
}
}
}
}
export default permissionDirective
3. 全局注册指令
在 main.ts 中注册,全局可用:
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import permissionDirective from './directives/permission'
const app = createApp(App)
// 注册全局指令 v-permission
app.directive('permission', permissionDirective)
app.mount('#app')
使用方式(超简单)
① 隐藏按钮(默认)
<!-- 只有拥有 user:add 权限才显示 -->
<button v-permission="['user:add']">新增用户</button>
<!-- 满足任意一个权限就显示 -->
<button v-permission="['user:edit', 'user:delete']">编辑/删除</button>
② 禁用按钮(加 .disabled 修饰符)
<!-- 权限不足时禁用按钮,而不是隐藏 -->
<button v-permission.disabled="['role:edit']">编辑角色</button>
测试效果
在登录后/页面初始化时,给用户设置权限:
import { usePermissionStore } from '@/stores/permission'
const permissionStore = usePermissionStore()
// 模拟接口返回的用户权限
permissionStore.setPermissions(['user:list', 'user:add', 'role:list'])
此时:
v-permission="['user:add']"→ 显示v-permission="['user:delete']"→ 隐藏v-permission.disabled="['role:edit']"→ 禁用
扩展优化(可选)
支持单个字符串权限
修改指令,支持 v-permission="'user:add'":
// 处理权限值
const needPermissions = Array.isArray(value) ? value : [value]
const hasPermission = permissionStore.permissions.some(perm => needPermissions.includes(perm))
支持所有权限必须满足
加 .all 修饰符:
if (binding.modifiers.all) {
// 必须拥有所有权限
hasPermission = value.every(perm => permissionStore.permissions.includes(perm))
} else {
// 拥有任意一个即可
hasPermission = value.some(perm => permissionStore.permissions.includes(perm))
}
使用:
<button v-permission.all="['user:edit', 'user:delete']">删除</button>
总结
- 用 Pinia 全局存储用户权限列表
- 自定义指令
v-permission做权限判断 - 支持隐藏/禁用两种交互方式
- 全局注册后,所有页面直接使用,零业务代码侵入
