从 0 实现一个 vue3 的权限指令 v-permission

在开发过程中会经常遇到一些权限控制,比如路由的权限控制、按钮的权限控制 🤔

这里利用 vue 的自定义指令功能,实现一个按钮级别的权限控制指令 💡

基础版本的权限控制

假设我们开发一个掘金,里面有文章编辑、发布、删除等功能,但不是每一个用户都可以有删除权限,除了用 v-if/v-show 外,可以用一个简单的权限控制指令实现

// permission.js
export const permission = {
  mounted(el, binding) {
    const { value } = binding
    const currentUser = getCurrentUser() // 获取当前用户的信息
    
    if (!currentUser.roles.includes(value)) {
      el.style.display = 'none'
    }
  }
}

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { permission } from './permission'

const app = createApp(App)
app.directive('permission', permission)
app.mount('#app')

使用:

<button v-permission="'admin'">删除文章</button>

上面的内容实现了一个基础版本的角色权限功能,比如 admin 用户才能进行删除文章操作

假设现在需求是需要多个角色权限控制,比如管理员及文章所属的用户?

多角色支持

上面实现的功能比较简单,遇到多角色的场景就不太行了,继续扩展下,让我们的指令支持多个角色👻

// permission.js
export const permission = {
  mounted(el, binding) {
    const { value } = binding
    const currentUser = getCurrentUser()
    
    if (Array.isArray(value)) {
      if (!value.some(role => currentUser.roles.includes(role))) {
        el.style.display = 'none'
      }
    } else if (typeof value === 'string') {
      if (!currentUser.roles.includes(value)) {
        el.style.display = 'none'
      }
    }
  }
}

现在可以这样用:

<button v-permission="['admin', 'editor']">编辑文章</button>

这样管理员跟文章所属的用户就可以对文章进行编辑处理

使用修饰符

继续扩展一下,使用一些修饰符实现更加灵活控制指令,比如删除 remove隐藏 hidden禁用 disable 按钮

// permission.js
export const permission = {
  mounted(el, binding) {
    const { value, modifiers } = binding
    const currentUser = getCurrentUser()
    
    let hasPermission = Array.isArray(value) 
      ? value.some(role => currentUser.roles.includes(role))
      : currentUser.roles.includes(value)

    if (!hasPermission) {
      if (modifiers.remove) {
        el.parentElement && el.parentElement.removeChild(el)
      } else if (modifiers.disable) {
        el.disabled = true
        el.classList.add('disabled')
      } else {
        el.style.display = 'none'
      }
    }
  }
}

现在默认还是隐藏元素,但添加了两个修饰符

  • .remove 直接从 DOM 中删除元素
  • .disable 禁用元素
<button v-permission.remove="'superadmin'">删除所有用户数据</button>
<button v-permission.disable="'editor'">发布文章</button>

假设是 superadmin 角色可以 删除所有用户数据,同时这个指令如果不是该角色的话,是直接从 DOM 中 删除 remove

对于 editor 角色,能发布文章,如果不是该角色的话,该按钮则处于 禁用 disable 状态

添加更多的权限控制

在上面的基础上,添加更多功能,比如复杂权限检查(如果是 Object 类型,这里待定)、使用函数进行动态权限检查、错误处理及日志记录

import { getCurrentUser } from './userService' // 获取当前用户的信息
// import { checkComplexPermission } from './permissionService' // 复杂的权限控制
import { logger } from './logger' // 记录日志

export const permission = {
  mounted(el, binding, vnode) {
    updateElementVisibility(el, binding, vnode)
  },
  updated(el, binding, vnode) {
    updateElementVisibility(el, binding, vnode)
  }
}

function updateElementVisibility(el, binding, vnode) {
  try {
    const { value, modifiers } = binding
    const currentUser = getCurrentUser()
    let hasPermission = false

    // 根据不同类型的权限值进行检查
    if (typeof value === 'function') {
      hasPermission = value(currentUser)
    } else if (Array.isArray(value)) {
      hasPermission = value.some(role => currentUser.roles.includes(role))
    } else if (typeof value === 'string') {
      hasPermission = currentUser.roles.includes(value)
    } 
    // else if (typeof value === 'object' && value !== null) {
    //   // 如果是对象,使用复杂权限检查服务
    //   hasPermission = checkComplexPermission(currentUser, value)
    // } 
    else {
      throw new Error(`Invalid value for v-permission directive: ${value}`)
    }

    // 根据权限检查结果和修饰符来处理元素
    if (!hasPermission) {
      if (modifiers.remove) {
        // 如果使用 remove 修饰符,则从 DOM 中移除元素
        el.parentElement && el.parentElement.removeChild(el)
      } else if (modifiers.disable) {
        // 如果使用 disable 修饰符,则禁用元素
        el.disabled = true
        el.classList.add('disabled')
      } else {
        // 默认行为是隐藏元素
        el.style.display = 'none'
      }
    } else {
      // 如果有权限,确保元素可见且启用
      el.style.display = ''
      el.disabled = false
      el.classList.remove('disabled')
    }

    // 记录权限检查结果
    logger.info(`Permission check for ${vnode.type.name || 'element'}: ${hasPermission ? 'granted' : 'denied'}`)
  } catch (error) {
    // 错误处理和日志记录
    console.error('Error in permission directive:', error)
    logger.error('Permission directive error', { error: error.message, element: vnode.type.name })
  }
}

使用:

<template>
  <!-- 基本角色检查 -->
  <button v-permission="'admin'">管理系统设置</button>

  <!-- 多角色检查 -->
  <button v-permission="['manager', 'hr']">查看员工报告</button>

  <!-- 使用修饰符 -->
  <button v-permission.remove="'owner'">删除公司数据</button>
  <button v-permission.disable="'editor'">发布文章</button>

  <!-- 复杂权限检查 -->
  <!-- <button v-permission="{ action: 'approve', resource: 'expense' }">批准报销</button> -->

  <!-- 使用函数进行动态权限检查 -->
  <button v-permission="(user) => user.experience > 5">访问高级功能</button>
</template>

上面的代码中实现了以下功能:

  • 使用 logger 进行日志记录(这里面需要实现一下)
  • mountedupdated 钩子中都调用 updateElementVisibility,确保在元素首次渲染和数据更新时都进行权限检查
  • 对字符串、数组、函数类型进行权限检测(这里面可以扩充一下对象类型,实现更复杂的权限校验)
  • 根据修饰符决定无权限的 DOM 的处理方式是 删除 remove禁用 disable 还是 隐藏 hidden

完结,撒花🎉🎉🎉

posted @ 2024-07-08 14:30  to人间值得  阅读(264)  评论(0编辑  收藏  举报