diff --git a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/auth.vue b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/auth.vue index 05858885e..9b398c041 100644 --- a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/auth.vue +++ b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/auth.vue @@ -4,8 +4,7 @@ diff --git a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/authAll.vue b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/authAll.vue index 54c8d5859..55404479b 100644 --- a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/authAll.vue +++ b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/authAll.vue @@ -4,9 +4,7 @@ diff --git a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/auths.vue b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/auths.vue index 41b8b2786..98a28f665 100644 --- a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/auths.vue +++ b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/components/auth/auths.vue @@ -4,8 +4,7 @@ diff --git a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/directive/authDirective.ts b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/directive/authDirective.ts index 30c39fd93..e864f488c 100644 --- a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/directive/authDirective.ts +++ b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/directive/authDirective.ts @@ -1,44 +1,82 @@ -import type { App } from 'vue'; -import { useUserInfo } from '/@/stores/userInfo'; -import { judementSameArr } from '/@/utils/arrayOperation'; - -/** - * 用户权限指令 - * @directive 单个权限验证(v-auth="xxx") - * @directive 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]") - * @directive 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]") - */ -export function authDirective(app: App) { - // 单个权限验证(v-auth="xxx") - app.directive('auth', { - mounted(el, binding) { - const stores = useUserInfo(); - if(stores.userInfos.isDebug == false){ - if (!stores.userInfos.authBtnList.some((v: string) => { - return v.toLowerCase() === binding.value.toLowerCase() - })) el.parentNode.removeChild(el); - } - }, - }); - // 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]") - app.directive('auths', { - mounted(el, binding) { - let flag = false; - const stores = useUserInfo(); - stores.userInfos.authBtnList.map((val: string) => { - binding.value.map((v: string) => { - if (val === v) flag = true; - }); - }); - if (!flag) el.parentNode.removeChild(el); - }, - }); - // 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]") - app.directive('auth-all', { - mounted(el, binding) { - const stores = useUserInfo(); - const flag = judementSameArr(binding.value, stores.userInfos.authBtnList); - if (!flag) el.parentNode.removeChild(el); - }, - }); -} +import { watch } from 'vue'; +import type { App, ObjectDirective } from 'vue'; +import { useUserInfo } from '/@/stores/userInfo'; +import { auth, authAll, auths } from '/@/utils/authFunction'; + +type PermissionCheck = (value: unknown) => boolean; + +interface PermissionElementState { + bindingValue: unknown; + checkPermission: PermissionCheck; + wasInitiallyHidden: HTMLElement['hidden']; +} + +// 注册所有使用权限指令的元素。这里只保存元素状态,不为每个元素创建 watcher。 +// Map 需要在 unmounted 中主动删除,避免继续持有已经卸载的 DOM。 +const permissionElements = new Map(); +let stopWatchingPermissions: (() => void) | undefined; + +function updateElementVisibility(el: HTMLElement, state: PermissionElementState) { + const shouldHide = state.wasInitiallyHidden || !state.checkPermission(state.bindingValue); + if (el.hidden !== shouldHide) el.hidden = shouldHide; +} + +function updateAllPermissionElements() { + permissionElements.forEach((state, el) => updateElementVisibility(el, state)); +} + +function startWatchingPermissions() { + if (stopWatchingPermissions) return; + + const stores = useUserInfo(); + stopWatchingPermissions = watch( + [() => stores.userInfos.isDebug, () => stores.authBtnSet], + updateAllPermissionElements + ); +} + +function createPermissionDirective(checkPermission: PermissionCheck): ObjectDirective { + return { + mounted(el, binding) { + const state = { + bindingValue: binding.value, + checkPermission, + wasInitiallyHidden: el.hidden, + }; + permissionElements.set(el, state); + updateElementVisibility(el, state); + startWatchingPermissions(); + }, + updated(el, binding) { + const state = permissionElements.get(el); + if (!state) return; + + state.bindingValue = binding.value; + updateElementVisibility(el, state); + }, + unmounted(el) { + permissionElements.delete(el); + + // 页面上已经没有权限指令时,连唯一的共享 watcher 也一起释放。 + if (permissionElements.size === 0) { + stopWatchingPermissions?.(); + stopWatchingPermissions = undefined; + } + }, + }; +} + +/** + * 用户权限指令 + * @directive 单个权限验证(v-auth="xxx") + * @directive 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]") + * @directive 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]") + */ +export function authDirective(app: App) { + // 单个权限验证(v-auth="xxx") + app.directive('auth', createPermissionDirective(auth)); + // 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]") + app.directive('auths', createPermissionDirective(auths)); + // 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]") + app.directive('auth-all', createPermissionDirective(authAll)); +} diff --git a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/stores/userInfo.ts b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/stores/userInfo.ts index 045f3ae6e..2899c1e38 100644 --- a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/stores/userInfo.ts +++ b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/stores/userInfo.ts @@ -22,8 +22,15 @@ export const useUserInfo = defineStore('userInfo', { menu:[], roles: [], authBtnList: [], + }, + }), + getters: { + // Pinia getter 会缓存结果,仅在 authBtnList 变化时重新生成。 + // 权限指令和组件可以复用同一个 Set,避免每个按钮重复遍历权限数组。 + authBtnSet: (state): Set => { + return new Set(state.userInfos.authBtnList.map((value) => value.trim().toLowerCase()).filter(Boolean)); }, - }), + }, actions: { async setUserInfos() { // 存储用户信息到浏览器缓存 diff --git a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/utils/authFunction.ts b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/utils/authFunction.ts index 84c0ab468..ee5df5ee8 100644 --- a/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/utils/authFunction.ts +++ b/demo/WalkingTec.Mvvm.Vue3Demo/ClientApp/src/utils/authFunction.ts @@ -1,14 +1,15 @@ import { useUserInfo } from '/@/stores/userInfo'; -import { judementSameArr } from '/@/utils/arrayOperation'; /** * 单个权限验证 * @param value 权限值 * @returns 有权限,返回 `true`,反之则反 */ -export function auth(value: string): boolean { +export function auth(value: unknown): boolean { + if (typeof value !== 'string') return false; const stores = useUserInfo(); - return stores.userInfos.authBtnList.some((v: string) => v === value); + if (stores.userInfos.isDebug === true) return true; + return stores.authBtnSet.has(value.trim().toLowerCase()); } /** @@ -16,15 +17,11 @@ export function auth(value: string): boolean { * @param value 权限值 * @returns 有权限,返回 `true`,反之则反 */ -export function auths(value: Array): boolean { - let flag = false; +export function auths(value: unknown): boolean { + if (!Array.isArray(value) || !value.every((item) => typeof item === 'string')) return false; const stores = useUserInfo(); - stores.userInfos.authBtnList.map((val: string) => { - value.map((v: string) => { - if (val === v) flag = true; - }); - }); - return flag; + if (stores.userInfos.isDebug === true) return true; + return value.some((permission) => stores.authBtnSet.has(permission.trim().toLowerCase())); } /** @@ -32,7 +29,9 @@ export function auths(value: Array): boolean { * @param value 权限值 * @returns 有权限,返回 `true`,反之则反 */ -export function authAll(value: Array): boolean { +export function authAll(value: unknown): boolean { + if (!Array.isArray(value) || !value.every((item) => typeof item === 'string')) return false; const stores = useUserInfo(); - return judementSameArr(value, stores.userInfos.authBtnList); + if (stores.userInfos.isDebug === true) return true; + return value.length > 0 && value.every((permission) => stores.authBtnSet.has(permission.trim().toLowerCase())); }