vue实现按钮权限控制史上最佳技术方案和落地实践

439 阅读1分钟

背景:
最近 vue3 项目要做按钮权限控制,于是综合利弊决定采用自定义指令方式实现。项目架构vue3 + pinia + pinia-plugin-persist(持久化)。

思路:
登录的时候请求按钮权限列表,同时用pinia做持久化存储,刷新页面时不再重复请求,退出登录时清除按钮权限缓存。

难点:
1.可能需要动态更改按钮DOM的权限值
2.动态更改当前用户的按钮权限数据,如重新请求按钮权限数据
解决方案:增加一个updated钩子,并用watchEffect收集按钮权限数据依赖。

具体代码:
src/stores/use-permission.ts

import { defineStore } from 'pinia'

interface usePermission {
  buttonPermission: string[]
}

export const usePermissionStore = defineStore('permission', {
  state: (): usePermission => {
    return {
      buttonPermission: []
    }
  },
  actions: {
    getButtonPermission() {
      // 请求按钮权限TODO
      this.buttonPermission = ['aaa', 'bbb', 'ccc']
    },
    clearButtonPermission() {
      this.buttonPermission = []
      globalThis.localStorage.removeItem('buttonPermission')
    },
    // 额外提供是否存在权限的方法(有些地方指令不能用,只能用if判断)
    hasPermission(code: string) {
      if (!code) return true
      return this.buttonPermission.includes(code)
    }
  },
  // 添加持久化选项
  persist: {
    enabled: true, // 启用持久化
    strategies: [
      {
        key: 'buttonPermission', // 本地存储的键
        storage: localStorage // 使用localStorage存储
      }
    ]
  }
})

src/directives/permission.ts

import { watchEffect, type DirectiveBinding } from 'vue'
import { usePermissionStore } from '~/stores/use-permission'

const hasPermission = (value: string) => {
  const permissionStore = usePermissionStore()
  return permissionStore.buttonPermission.includes(value)
}

const removeEl = (el: Record<string, any>) => {
  el._parentNode = el.parentNode
  el._placeholderNode = document.createComment('auth')
  el.parentNode?.replaceChild(el._placeholderNode, el)
}

const addEl = (el: Record<string, any>) => {
  el._parentNode?.replaceChild(el, el._placeholderNode)
}

function mounted(el: Record<string, any>, binding: DirectiveBinding<any>) {
  const value = binding.value
  const flag = hasPermission(value)
  el._oldHasPermission = !value ? true : flag;
  if (!value) return
  if (!flag) {
    removeEl(el)
  }
}

function updated(el: Record<string, any>, binding: DirectiveBinding<any>) {
  el._binding = binding
  const update = () => {
    const oldHasPermission = el._oldHasPermission
    const newHasPermission = hasPermission(el._binding.value)
    if (oldHasPermission === newHasPermission) return
    if (newHasPermission) {
      addEl(el)
    } else {
      removeEl(el)
    }
    el._oldHasPermission = newHasPermission
  }
  if (el._watchEffect) {
    update()
  } else {
    el._watchEffect = watchEffect(() => {
      update()
    })
  }
}

export default {
  mounted,
  updated
}

src/directives/index.ts

import type { Directive, App } from 'vue'
import permission from '~/directives/permission'


const directives = {
  permission
}

const registerDirectives = (app: App<Element>) => {
  Object.keys(directives).forEach((key) => {
    app.directive(key, (directives as Record<string, Directive>)[key])
  })
}

export default registerDirectives

然后再入口main.ts引入自定义指令:registerDirectives(app)

补充:
有些特殊权限控制是数组,请自行将入参code值兼容数组类型并判断是否有权限。

文献参考:blog.csdn.net/weixin_5379…