/** * 按钮级权限控制 * * 参考 mp-vue/src/utils/permission.ts * 当前项目未引入 Pinia 的 user store,因此改为从 Nuxt state / localStorage 读取。 */ import type { App } from 'vue' import { useState } from '#imports' import type { User } from '@/api/system/user/model' import type { Role } from '@/api/system/role/model' import type { Menu } from '@/api/system/menu/model' type AuthzState = { roles: string[] authorities: string[] } const AUTHZ_STORAGE_KEY = 'Authz' function uniqNonEmpty(values: Array) { const seen = new Set() for (const v of values) { const s = typeof v === 'string' ? v.trim() : '' if (!s || s === 'null' || s === 'undefined') continue seen.add(s) } return Array.from(seen) } function safeJsonParse(value: string): unknown { try { return JSON.parse(value) } catch { return undefined } } function normalizeStringArray(value: unknown): string[] { if (Array.isArray(value)) { return uniqNonEmpty(value.filter((v): v is string => typeof v === 'string')) } if (typeof value === 'string') { return uniqNonEmpty( value .split(',') .map((v) => v.trim()) .filter(Boolean) ) } return [] } function readAuthzFromStorage(): AuthzState { if (!import.meta.client) return { roles: [], authorities: [] } try { const raw = localStorage.getItem(AUTHZ_STORAGE_KEY) if (!raw) return { roles: [], authorities: [] } const parsed = safeJsonParse(raw) if (!parsed || typeof parsed !== 'object') return { roles: [], authorities: [] } const obj = parsed as Record return { roles: normalizeStringArray(obj.roles), authorities: normalizeStringArray(obj.authorities) } } catch { return { roles: [], authorities: [] } } } function writeAuthzToStorage(next: AuthzState) { if (!import.meta.client) return try { localStorage.setItem(AUTHZ_STORAGE_KEY, JSON.stringify(next)) } catch { // ignore } } function getAuthzStateRef() { try { return useState('authz', () => readAuthzFromStorage()) } catch { return null } } function getAuthzSnapshot(): AuthzState { const state = getAuthzStateRef() return state?.value ?? readAuthzFromStorage() } export function setAuthz(next: Partial) { const current = getAuthzSnapshot() const merged: AuthzState = { roles: normalizeStringArray(next.roles ?? current.roles), authorities: normalizeStringArray(next.authorities ?? current.authorities) } const state = getAuthzStateRef() if (state) state.value = merged writeAuthzToStorage(merged) } export function clearAuthz() { const state = getAuthzStateRef() if (state) state.value = { roles: [], authorities: [] } if (!import.meta.client) return try { localStorage.removeItem(AUTHZ_STORAGE_KEY) } catch { // ignore } } function getRoleCodesFromUser(user?: User | null) { const roleCodes: string[] = [] const fromUserRoleCode = normalizeStringArray(user?.roleCode) roleCodes.push(...fromUserRoleCode) const roles: Role[] | undefined = user?.roles if (Array.isArray(roles)) { for (const role of roles) { if (!role) continue roleCodes.push(role.roleCode) } } return uniqNonEmpty(roleCodes) } function getAuthoritiesFromUser(user?: User | null) { const authorities: string[] = [] const list: Menu[] | undefined = user?.authorities if (Array.isArray(list)) { for (const item of list) { if (!item) continue authorities.push(item.authority) } } return uniqNonEmpty(authorities) } export function setAuthzFromUser(user?: User | null) { setAuthz({ roles: getRoleCodesFromUser(user), authorities: getAuthoritiesFromUser(user) }) } /* 判断数组是否有某些值(全包含) */ function normalizeNeedles(value: string | string[]) { if (Array.isArray(value)) return uniqNonEmpty(value) const s = typeof value === 'string' ? value.trim() : '' return s ? [s] : [] } function normalizeHaystack(array: (string | undefined)[]) { return uniqNonEmpty(array) } function arrayHas(array: (string | undefined)[], value: string | string[]): boolean { if (!value) return true if (!array) return false const needles = normalizeNeedles(value) if (needles.length === 0) return true const haystack = new Set(normalizeHaystack(array)) for (let i = 0; i < needles.length; i++) { if (!haystack.has(needles[i])) return false } return true } /* 判断数组是否有任意值(任一包含) */ function arrayHasAny(array: (string | undefined)[], value: string | string[]): boolean { if (!value) return true if (!array) return false const needles = normalizeNeedles(value) if (needles.length === 0) return true const haystack = new Set(normalizeHaystack(array)) for (let i = 0; i < needles.length; i++) { if (haystack.has(needles[i])) return true } return false } /** * 是否有某些角色 * @param value 角色字符或字符数组 */ export function hasRole(value: string | string[]): boolean { const { roles } = getAuthzSnapshot() return arrayHas(roles, value) } /** * 是否有任意角色 * @param value 角色字符或字符数组 */ export function hasAnyRole(value: string | string[]): boolean { const { roles } = getAuthzSnapshot() return arrayHasAny(roles, value) } /** * 是否有某些权限 * @param value 权限字符或字符数组 */ export function hasPermission(value: string | string[]): boolean { const { authorities } = getAuthzSnapshot() return arrayHas(authorities, value) } /** * 是否有任意权限 * @param value 权限字符或字符数组 */ export function hasAnyPermission(value: string | string[]): boolean { const { authorities } = getAuthzSnapshot() return arrayHasAny(authorities, value) } export default { install(app: App) { // 添加自定义指令 app.directive('role', { mounted: (el, binding) => { if (!hasRole(binding.value)) el.parentNode?.removeChild(el) } }) app.directive('any-role', { mounted: (el, binding) => { if (!hasAnyRole(binding.value)) el.parentNode?.removeChild(el) } }) app.directive('permission', { mounted: (el, binding) => { if (!hasPermission(binding.value)) el.parentNode?.removeChild(el) } }) app.directive('any-permission', { mounted: (el, binding) => { if (!hasAnyPermission(binding.value)) el.parentNode?.removeChild(el) } }) } }