OfficeSystem/directives/permission.js
2025-11-26 17:59:11 +08:00

205 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file permission.js
* @description Vue权限指令实现用于根据用户权限控制DOM元素显示/隐藏
* 支持Vue 2和Vue 3提供v-permission指令进行灵活的权限控制
*/
import { useUserStore } from '@/store/user'
import { ADMIN_ROLES, normalizePermissions } from '@/utils/permission'
/**
* 解析所需权限列表
* 将指令值解析为标准权限数组
* @param {Array|string|null} value - 指令绑定的值
* @returns {Array} 解析后的权限数组
*/
const resolveRequiredPermissions = (value) => {
if (!value) return []
if (Array.isArray(value)) return value.filter(Boolean)
if (typeof value === 'string') {
return value
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
return []
}
/**
* 评估用户权限
* 检查用户是否拥有所需的权限
* @param {Object|null} userStore - 用户状态存储
* @param {Array|string} requiredPermissions - 需要验证的权限
* @param {Object} modifiers - 指令修饰符,用于控制权限验证策略
* @returns {boolean} 用户是否拥有所需权限
*/
const evaluatePermission = (userStore, requiredPermissions, modifiers = {}) => {
// 用户存储不可用时,默认为无权限
if (!userStore) return false
// 解析权限验证策略支持requireAll和all两种修饰符
const {requireAll = modifiers.all} = modifiers
// 解析所需权限
const permissionsToCheck = resolveRequiredPermissions(requiredPermissions)
// 空权限列表表示不需要权限直接返回true
if (permissionsToCheck.length === 0) return true
// 获取用户信息
const userInfo = userStore.userInfo
const userRoles = userInfo?.roles || []
// 检查用户是否为管理员,管理员拥有所有权限
const isAdmin = Array.isArray(userRoles) && userRoles.some((role) => ADMIN_ROLES.includes(role))
if (isAdmin) return true
// 获取并规范化用户权限
const userPermissions = normalizePermissions(userInfo?.permissions)
// 用户无权限时返回false
if (userPermissions.length === 0) return false
// 根据验证策略进行权限检查
if (requireAll) {
// all修饰符用户必须拥有所有指定权限
return permissionsToCheck.every((permission) => userPermissions.includes(permission))
}
// 默认策略:用户只需拥有任一指定权限即可
return permissionsToCheck.some((permission) => userPermissions.includes(permission))
}
/**
* 获取用户状态存储
* 通过多种途径尝试获取Pinia实例和用户存储
* @param {Object} binding - 指令绑定对象
* @param {Object} vnode - 虚拟节点对象
* @returns {Object|null} 用户状态存储或null
*/
const getUserStore = (binding, vnode) => {
// 尝试从多个可能的路径获取Pinia实例
// 这是为了兼容不同的使用场景和组件层次结构
const piniaInstance =
binding?.instance?.proxy?.$pinia ||
binding?.instance?.appContext?.app?.$pinia ||
vnode?.context?.$pinia ||
binding?.instance?.appContext?.appContext?.provides?.pinia
try {
// 如果获取到Pinia实例使用它来创建userStore
if (piniaInstance) {
return useUserStore(piniaInstance)
}
// 否则尝试使用全局Pinia实例
return useUserStore()
} catch (error) {
console.warn('[v-permission] failed to access pinia instance:', error)
return null
}
}
/**
* 切换元素可见性
* 根据权限验证结果控制元素的显示或隐藏
* 保留原始的display样式以便需要时恢复
* @param {HTMLElement} el - DOM元素
* @param {boolean} shouldShow - 是否显示元素
*/
const toggleElementVisibility = (el, shouldShow) => {
if (shouldShow) {
// 显示元素恢复原始display样式或使用默认值
el.style.display = el.__vOriginalDisplay || ''
} else {
// 隐藏元素先保存当前display样式然后设置为none
if (el.style.display !== 'none') {
el.__vOriginalDisplay = el.style.display
}
el.style.display = 'none'
}
// 记录权限控制状态,用于后续更新
el.__vPermissionVisible = shouldShow
}
/**
* 处理指令逻辑
* 指令的核心处理函数连接权限验证和DOM操作
* @param {HTMLElement} el - DOM元素
* @param {Object} binding - 指令绑定对象
* @param {Object} vnode - 虚拟节点
*/
const handleDirective = (el, binding, vnode) => {
// 获取用户状态存储
const userStore = getUserStore(binding, vnode)
// 评估用户是否有权限
const shouldShow = evaluatePermission(userStore, binding.value, binding.modifiers)
// 根据权限控制元素可见性
toggleElementVisibility(el, shouldShow)
}
/**
* 注册权限指令
* 在Vue应用中全局注册v-permission指令
* 支持Vue 2和Vue 3的不同API
* @param {Object} appOrVue - Vue应用实例或Vue构造函数
*/
export const registerPermissionDirective = (appOrVue) => {
// 参数验证
if (!appOrVue) return
// 定义Vue 3的指令配置
const directiveConfig = {
mounted: handleDirective, // 元素挂载时
updated: handleDirective, // 元素更新时
beforeMount: handleDirective, // 元素挂载前
beforeUpdate: handleDirective // 元素更新前
}
// 根据Vue版本进行兼容处理
if (typeof appOrVue.directive === 'function') {
// Vue 3 使用 app.directive 注册
appOrVue.directive('permission', directiveConfig)
} else if (typeof appOrVue.prototype?.directive === 'function' || typeof appOrVue.directive === 'function') {
// Vue 2 使用 Vue.directive 注册
const VueCtor = appOrVue.prototype?.constructor || appOrVue
VueCtor.directive('permission', {
inserted: handleDirective, // 元素插入父节点时
update: handleDirective, // 元素更新时
componentUpdated: handleDirective // 组件更新后
})
}
}
/**
* 使用示例:
*
* // 1. 注册指令在main.js中
* import { registerPermissionDirective } from '@/directives/permission'
* import { createApp } from 'vue'
* const app = createApp(App)
* registerPermissionDirective(app)
*
* // 2. 在组件中使用
*
* // 2.1 基础用法:用户需要任一权限
* <button v-permission="['user:view', 'user:edit']">编辑用户</button>
*
* // 2.2 使用all修饰符用户需要拥有所有权限
* <button v-permission.all="['user:view', 'user:edit']">高级编辑</button>
*
* // 2.3 使用requireAll修饰符效果同all
* <button v-permission.requireAll="['user:view', 'user:edit']">高级编辑</button>
*
* // 2.4 字符串权限
* <button v-permission="'user:view'">查看用户</button>
*
* // 2.5 逗号分隔的权限字符串
* <button v-permission="'user:view,user:edit'">编辑用户</button>
*/
// 默认导出
export default registerPermissionDirective