| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- import { nextTick, type App, type Directive, type DirectiveBinding } from 'vue'
- import router from '@/router'
- import { pinia, usePermissionStore } from '@/store'
- import type { PermissionDirectiveValue } from '@/types/permission'
- type PermissionHTMLElement = HTMLElement & {
- __permissionDisplay?: string
- __permissionDividerDisplay?: string
- __permissionLabel?: string
- __permissionTextNode?: Text
- __permissionObserver?: MutationObserver
- __permissionLabelConfig?: {
- menuCode: string
- buttonCode: string
- label?: string
- usePermissionName?: boolean
- }
- }
- const resolveVisibilityElement = (el: PermissionHTMLElement) =>
- (el.closest('.el-dropdown-menu__item') as PermissionHTMLElement | null) || el
- const resolveDropdownDivider = (el: PermissionHTMLElement) => {
- const visibilityElement = resolveVisibilityElement(el)
- const previousElement = visibilityElement.previousElementSibling as PermissionHTMLElement | null
- return previousElement?.getAttribute('role') === 'separator' ? previousElement : undefined
- }
- const updateVisibility = (el: PermissionHTMLElement, visible: boolean) => {
- const visibilityElement = resolveVisibilityElement(el)
- const dropdownDivider = resolveDropdownDivider(el)
- if (visibilityElement.__permissionDisplay === undefined) {
- visibilityElement.__permissionDisplay = visibilityElement.style.display
- }
- if (dropdownDivider && dropdownDivider.__permissionDividerDisplay === undefined) {
- dropdownDivider.__permissionDividerDisplay = dropdownDivider.style.display
- }
- visibilityElement.style.display = visible ? visibilityElement.__permissionDisplay || '' : 'none'
- if (dropdownDivider) {
- dropdownDivider.style.display = visible
- ? dropdownDivider.__permissionDividerDisplay || ''
- : 'none'
- }
- }
- // 支持三种写法:v-permission="'add'"、v-permission="['menu', 'add']"、对象形式。
- const resolvePermissionBinding = (binding: DirectiveBinding<PermissionDirectiveValue>) => {
- const currentMenuCode =
- typeof router.currentRoute.value.meta.menuCode === 'string'
- ? router.currentRoute.value.meta.menuCode
- : undefined
- if (typeof binding.value === 'string') {
- return {
- menuCode: currentMenuCode,
- buttonCode: binding.value
- }
- }
- if (Array.isArray(binding.value)) {
- return {
- menuCode: binding.value[0],
- buttonCode: binding.value[1]
- }
- }
- return {
- menuCode: binding.value?.menuCode || currentMenuCode,
- buttonCode: binding.value?.buttonCode,
- label: binding.value?.label,
- usePermissionName: binding.value?.usePermissionName
- }
- }
- const resolveTextNode = (el: PermissionHTMLElement) => {
- if (el.__permissionTextNode && el.contains(el.__permissionTextNode)) {
- return el.__permissionTextNode
- }
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT)
- let node = walker.nextNode()
- while (node) {
- if (node.textContent?.trim()) {
- el.__permissionTextNode = node as Text
- return el.__permissionTextNode
- }
- node = walker.nextNode()
- }
- return undefined
- }
- const updateButtonLabel = (
- el: PermissionHTMLElement,
- menuCode: string,
- buttonCode: string,
- label?: string,
- usePermissionName?: boolean
- ) => {
- const permissionStore = usePermissionStore(pinia)
- const shouldUsePermissionName = usePermissionName ?? label === undefined
- const resolvedLabel = shouldUsePermissionName
- ? permissionStore.getButtonPermissionName(menuCode, buttonCode, label || buttonCode)
- : label || buttonCode
- const textNode = resolveTextNode(el)
- if (!textNode) return
- if (el.__permissionLabel === resolvedLabel && textNode.textContent === resolvedLabel) return
- // 只替换按钮的首个文本节点,保留前置图标和其他插槽内容不变。
- textNode.textContent = resolvedLabel
- el.__permissionLabel = resolvedLabel
- }
- const ensureButtonLabelObserver = (el: PermissionHTMLElement) => {
- if (el.__permissionObserver) return
- el.__permissionObserver = new MutationObserver(() => {
- const config = el.__permissionLabelConfig
- if (!config) return
- updateButtonLabel(
- el,
- config.menuCode,
- config.buttonCode,
- config.label,
- config.usePermissionName
- )
- })
- el.__permissionObserver.observe(el, {
- childList: true,
- characterData: true,
- subtree: true
- })
- }
- const scheduleButtonLabelUpdate = async (
- el: PermissionHTMLElement,
- menuCode: string,
- buttonCode: string,
- label?: string,
- usePermissionName?: boolean
- ) => {
- el.__permissionLabelConfig = { menuCode, buttonCode, label, usePermissionName }
- ensureButtonLabelObserver(el)
- updateButtonLabel(el, menuCode, buttonCode, label, usePermissionName)
- await nextTick()
- updateButtonLabel(el, menuCode, buttonCode, label, usePermissionName)
- requestAnimationFrame(() => {
- updateButtonLabel(el, menuCode, buttonCode, label, usePermissionName)
- })
- }
- const updatePermissionVisibility = async (
- el: PermissionHTMLElement,
- binding: DirectiveBinding<PermissionDirectiveValue>
- ) => {
- const { menuCode, buttonCode, label, usePermissionName } = resolvePermissionBinding(binding)
- if (!menuCode || !buttonCode) {
- updateVisibility(el, false)
- return
- }
- const permissionStore = usePermissionStore(pinia)
- await permissionStore.ensureButtonPermissions(menuCode)
- const allowed = permissionStore.hasButtonAccess(menuCode, buttonCode)
- // 自定义指令只负责隐藏无权限按钮,不改变按钮原有业务逻辑。
- updateVisibility(el, allowed)
- if (allowed) {
- void scheduleButtonLabelUpdate(el, menuCode, buttonCode, label, usePermissionName)
- }
- }
- const permissionDirective: Directive<PermissionHTMLElement, PermissionDirectiveValue> = {
- mounted(el, binding) {
- void updatePermissionVisibility(el, binding)
- },
- updated(el, binding) {
- void updatePermissionVisibility(el, binding)
- },
- unmounted(el) {
- el.__permissionObserver?.disconnect()
- }
- }
- export const installPermissionDirective = (app: App) => {
- app.directive('permission', permissionDirective)
- }
|