feat(权限): 新增 v-permission 按钮级权限指令并移除前端权限别名
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import type { App } from 'vue'
|
||||
|
||||
import permission from './permission'
|
||||
|
||||
export function registerDirectives(app: App) {
|
||||
app.directive('permission', permission)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { effectScope, ref, watchEffect, type Directive, type EffectScope, type Ref } from 'vue'
|
||||
|
||||
import { useLoginStore } from '@/stores/login'
|
||||
|
||||
/**
|
||||
* 指令值:单个权限码,或需要同时具备的一组权限码。
|
||||
* 数组按「且」判断,与 stores/login 的 hasPermission 口径保持一致。
|
||||
*/
|
||||
export type PermissionBindingValue = string | string[]
|
||||
|
||||
interface PermissionBindingState {
|
||||
value: Ref<PermissionBindingValue | undefined>
|
||||
scope: EffectScope
|
||||
}
|
||||
|
||||
const bindingStates = new WeakMap<HTMLElement, PermissionBindingState>()
|
||||
|
||||
function isAllowed(value: PermissionBindingValue | undefined): boolean {
|
||||
if (!value || (Array.isArray(value) && value.length === 0)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return useLoginStore().hasPermission(value)
|
||||
}
|
||||
|
||||
function applyPermission(el: HTMLElement, allowed: boolean, disableOnly: boolean) {
|
||||
if (disableOnly) {
|
||||
el.toggleAttribute('disabled', !allowed)
|
||||
el.setAttribute('aria-disabled', String(!allowed))
|
||||
return
|
||||
}
|
||||
|
||||
el.style.display = allowed ? '' : 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* 按钮级权限指令。
|
||||
*
|
||||
* - `v-permission="'doc:template:edit'"`:没有权限时隐藏元素;
|
||||
* - `v-permission.disable="'doc:template:edit'"`:没有权限时置灰,保留位置,
|
||||
* 用于「必须让操作人看到、但当前不可执行」的弹窗主按钮;
|
||||
* - 传数组时按「且」判断,与 hasPermission 一致。
|
||||
*
|
||||
* 使用约定:
|
||||
* - 权限码必须与菜单资源树里 BUTTON 节点的 code 完全一致,指令内部不做任何别名兜底;
|
||||
* - 隐藏或置灰只影响体验,后端校验仍是最终边界,因此提交接口的处理函数仍需自行校验;
|
||||
* - 与 v-if 可同时使用:隐藏走 display,不删除 DOM,不会和 v-if 的节点复用打架。
|
||||
*/
|
||||
const permission: Directive<HTMLElement, PermissionBindingValue> = {
|
||||
mounted(el, binding) {
|
||||
const value = ref<PermissionBindingValue | undefined>(binding.value)
|
||||
const disableOnly = Boolean(binding.modifiers.disable)
|
||||
const scope = effectScope()
|
||||
|
||||
// 用 watchEffect 追踪授权数据:角色、菜单资源或员工角色变更后,
|
||||
// refreshAuthorization 会让按钮自动恢复或隐藏,不必刷新页面。
|
||||
scope.run(() => {
|
||||
watchEffect(() => {
|
||||
applyPermission(el, isAllowed(value.value), disableOnly)
|
||||
})
|
||||
})
|
||||
|
||||
bindingStates.set(el, { value, scope })
|
||||
},
|
||||
updated(el, binding) {
|
||||
const state = bindingStates.get(el)
|
||||
if (state) {
|
||||
state.value.value = binding.value
|
||||
}
|
||||
},
|
||||
unmounted(el) {
|
||||
bindingStates.get(el)?.scope.stop()
|
||||
bindingStates.delete(el)
|
||||
},
|
||||
}
|
||||
|
||||
declare module 'vue' {
|
||||
interface GlobalDirectives {
|
||||
vPermission: typeof permission
|
||||
}
|
||||
}
|
||||
|
||||
export default permission
|
||||
Reference in New Issue
Block a user