From 20d12c1b94faa95cbfe4d9c01bbdd07c03d9747b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Wed, 8 Apr 2026 07:04:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20v-permission=20?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E5=AE=9E=E7=8E=B0=E6=8C=89=E9=92=AE=E7=BA=A7?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/directives/permission.test.ts | 124 ++++++++++++++++++ .../src/directives/permission.ts | 33 +++++ novalon-manage-web/src/main.ts | 3 + 3 files changed, 160 insertions(+) create mode 100644 novalon-manage-web/src/__tests__/directives/permission.test.ts create mode 100644 novalon-manage-web/src/directives/permission.ts 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')