Files
pc-10588/app/utils/permission.ts
2026-03-05 13:32:48 +08:00

248 lines
6.4 KiB
TypeScript
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.

/**
* 按钮级权限控制
*
* 参考 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<string | undefined | null>) {
const seen = new Set<string>()
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<string, unknown>
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<AuthzState>('authz', () => readAuthzFromStorage())
} catch {
return null
}
}
function getAuthzSnapshot(): AuthzState {
const state = getAuthzStateRef()
return state?.value ?? readAuthzFromStorage()
}
export function setAuthz(next: Partial<AuthzState>) {
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)
}
})
}
}