diff --git a/novalon-manage-web/src/__tests__/directives/permission.test.ts b/novalon-manage-web/src/__tests__/directives/permission.test.ts
new file mode 100644
index 0000000..9dfc020
--- /dev/null
+++ b/novalon-manage-web/src/__tests__/directives/permission.test.ts
@@ -0,0 +1,124 @@
+import { describe, it, expect, beforeEach } from 'vitest'
+import { mount } from '@vue/test-utils'
+import { createPinia, setActivePinia } from 'pinia'
+import { permissionDirective } from '@/directives/permission'
+import { usePermissionStore } from '@/stores/permission'
+
+describe('v-permission 指令', () => {
+ beforeEach(() => {
+ setActivePinia(createPinia())
+ localStorage.clear()
+ })
+
+ describe('角色检查', () => {
+ it('有角色时应该显示元素', () => {
+ const store = usePermissionStore()
+ store.setPermissionData({
+ roles: ['admin'],
+ permissions: [],
+ menus: []
+ })
+
+ const wrapper = mount({
+ template: '',
+ directives: {
+ permission: permissionDirective
+ }
+ })
+
+ expect(wrapper.find('button').isVisible()).toBe(true)
+ })
+
+ it('无角色时应该隐藏元素', () => {
+ const store = usePermissionStore()
+ store.setPermissionData({
+ roles: ['user'],
+ permissions: [],
+ menus: []
+ })
+
+ const wrapper = mount({
+ template: '',
+ directives: {
+ permission: permissionDirective
+ }
+ })
+
+ expect(wrapper.find('button').isVisible()).toBe(false)
+ })
+
+ it('支持数组参数(满足任一即可)', () => {
+ const store = usePermissionStore()
+ store.setPermissionData({
+ roles: ['user'],
+ permissions: [],
+ menus: []
+ })
+
+ const wrapper = mount({
+ template: '',
+ directives: {
+ permission: permissionDirective
+ }
+ })
+
+ expect(wrapper.find('button').isVisible()).toBe(true)
+ })
+ })
+
+ describe('权限检查', () => {
+ it('有权限时应该显示元素', () => {
+ const store = usePermissionStore()
+ store.setPermissionData({
+ roles: [],
+ permissions: ['user:delete'],
+ menus: []
+ })
+
+ const wrapper = mount({
+ template: '',
+ directives: {
+ permission: permissionDirective
+ }
+ })
+
+ expect(wrapper.find('button').isVisible()).toBe(true)
+ })
+
+ it('无权限时应该隐藏元素', () => {
+ const store = usePermissionStore()
+ store.setPermissionData({
+ roles: [],
+ permissions: ['user:read'],
+ menus: []
+ })
+
+ const wrapper = mount({
+ template: '',
+ directives: {
+ permission: permissionDirective
+ }
+ })
+
+ expect(wrapper.find('button').isVisible()).toBe(false)
+ })
+
+ it('支持简写形式(默认权限检查)', () => {
+ const store = usePermissionStore()
+ store.setPermissionData({
+ roles: [],
+ permissions: ['user:create'],
+ menus: []
+ })
+
+ const wrapper = mount({
+ template: '',
+ directives: {
+ permission: permissionDirective
+ }
+ })
+
+ expect(wrapper.find('button').isVisible()).toBe(true)
+ })
+ })
+})
diff --git a/novalon-manage-web/src/directives/permission.ts b/novalon-manage-web/src/directives/permission.ts
new file mode 100644
index 0000000..d2533e4
--- /dev/null
+++ b/novalon-manage-web/src/directives/permission.ts
@@ -0,0 +1,33 @@
+import type { Directive, DirectiveBinding } from 'vue'
+import { usePermissionStore } from '@/stores/permission'
+
+export const permissionDirective: Directive = {
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
+ const permissionStore = usePermissionStore()
+
+ const { arg, value } = binding
+ const checkType = arg || 'permission'
+
+ if (!value) {
+ console.warn('v-permission 指令需要提供权限值')
+ el.style.display = 'none'
+ return
+ }
+
+ let hasAccess = false
+
+ if (checkType === 'role') {
+ hasAccess = permissionStore.hasRole(value)
+ } else if (checkType === 'permission') {
+ hasAccess = permissionStore.hasPermission(value)
+ } else {
+ console.warn(`未知的权限检查类型: ${checkType}`)
+ el.style.display = 'none'
+ return
+ }
+
+ if (!hasAccess) {
+ el.style.display = 'none'
+ }
+ }
+}
diff --git a/novalon-manage-web/src/main.ts b/novalon-manage-web/src/main.ts
index 8774c23..d539656 100644
--- a/novalon-manage-web/src/main.ts
+++ b/novalon-manage-web/src/main.ts
@@ -6,6 +6,7 @@ import 'element-plus/dist/index.css'
import router from './router'
import App from './App.vue'
import './assets/styles.css'
+import { permissionDirective } from './directives/permission'
const app = createApp(App)
const pinia = createPinia()
@@ -16,4 +17,6 @@ app.use(ElementPlus, {
locale: zhCn,
})
+app.directive('permission', permissionDirective)
+
app.mount('#app')