Files
tiantian-system/app/composables/useAppPermission.ts
2026-04-08 17:10:58 +08:00

284 lines
7.9 KiB
TypeScript
Raw Permalink 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.

/**
* 应用级权限管理 composable
*
* 管理当前用户在各应用中的角色权限,提供:
* 1. 开发者中心访问检查
* 2. 可访问应用列表 + 角色映射
* 3. 按角色判断某应用的具体权限
*/
import type { AppRole, AppPermissionInfo } from '@/api/app/appUser/model'
// ============ 角色层级 ============
/** 角色层级:数字越大权限越高 */
export const ROLE_HIERARCHY: Record<AppRole, number> = {
viewer: 1,
developer: 2,
admin: 3,
owner: 4,
}
/** 角色中文名 */
export const ROLE_LABEL: Record<AppRole, string> = {
owner: '所有者',
admin: '管理员',
developer: '开发者',
viewer: '只读',
}
/** 角色颜色(用于 RoleTag */
export const ROLE_COLOR: Record<AppRole, string> = {
owner: 'gold',
admin: 'blue',
developer: 'green',
viewer: 'default',
}
// ============ 权限定义 ============
export interface AppPermission {
appId: number
productName: string
productCode?: string
icon?: string
role: AppRole
isOwner: boolean
canManageMembers: boolean // owner | admin
canEditApp: boolean // owner | admin
canDeleteApp: boolean // owner only
canSubmitReview: boolean // owner | admin
canEditResource: boolean // owner | admin | developer
canViewSensitive: boolean // owner | admin | developer
canCreateApiKey: boolean // owner | admin | developer
canEditConfig: boolean // owner | admin
canTriggerBuild: boolean // owner | admin | developer
}
/** 根据 role 计算权限字段 */
function buildPermission(info: AppPermissionInfo): AppPermission {
const { role } = info
const isOwner = role === 'owner'
const isAtLeastAdmin = isOwner || role === 'admin'
const isAtLeastDeveloper = isAtLeastAdmin || role === 'developer'
return {
appId: info.appId,
productName: info.productName,
productCode: info.productCode,
icon: info.icon,
role,
isOwner,
canManageMembers: isAtLeastAdmin,
canEditApp: isAtLeastAdmin,
canDeleteApp: isOwner,
canSubmitReview: isAtLeastAdmin,
canEditResource: isAtLeastDeveloper,
canViewSensitive: isAtLeastDeveloper,
canCreateApiKey: isAtLeastDeveloper,
canEditConfig: isAtLeastAdmin,
canTriggerBuild: isAtLeastDeveloper,
}
}
// ============ 全局状态(模块级单例) ============
const appPermissionsMap = ref<Map<number, AppPermission>>(new Map())
const isPlatformDeveloper = ref(false)
const hasCheckedAccess = ref(false)
const loading = ref(false)
// ============ 核心方法 ============
/**
* 检查用户是否有开发者中心访问权限
* 并自动加载可访问的应用列表
*/
async function checkDeveloperAccess(): Promise<{
accessible: boolean
isPlatformDeveloper: boolean
hasJoinedApps: boolean
}> {
if (loading.value) return { accessible: false, isPlatformDeveloper: false, hasJoinedApps: false }
loading.value = true
try {
// 尝试获取可访问应用列表后端接口GET /api/app/app-user/check-access
const { checkAppAccess } = await import('@/api/app/appUser')
const res = await checkAppAccess()
if (res?.accessible) {
isPlatformDeveloper.value = res.isPlatformDeveloper ?? false
hasCheckedAccess.value = true
// 如果返回了可访问应用列表,直接缓存
if (res.apps?.length) {
const newMap = new Map<number, AppPermission>()
for (const info of res.apps) {
newMap.set(info.appId, buildPermission(info))
}
appPermissionsMap.value = newMap
}
return {
accessible: true,
isPlatformDeveloper: res.isPlatformDeveloper ?? false,
hasJoinedApps: (res.apps?.length ?? 0) > 0,
}
}
return { accessible: false, isPlatformDeveloper: false, hasJoinedApps: false }
}
catch {
// 接口失败时降级:如果 localStorage 有 UserId 且是 type=2仍允许访问
if (import.meta.client) {
const userType = localStorage.getItem('UserType')
if (userType === '2') {
isPlatformDeveloper.value = true
hasCheckedAccess.value = true
return { accessible: true, isPlatformDeveloper: true, hasJoinedApps: false }
}
}
return { accessible: false, isPlatformDeveloper: false, hasJoinedApps: false }
}
finally {
loading.value = false
}
}
/**
* 加载用户所有可访问应用的权限
* 后端接口GET /api/app/product/accessible
*/
async function loadAppPermissions(): Promise<Map<number, AppPermission>> {
try {
const { getMyAccessibleApps } = await import('@/api/app/appProduct')
const apps = await getMyAccessibleApps()
const newMap = new Map<number, AppPermission>()
for (const app of apps) {
const role = (app.myRole as AppRole) || 'viewer'
newMap.set(app.productId!, {
appId: app.productId!,
productName: app.productName || '',
productCode: app.productCode,
icon: app.icon,
role,
...(buildPermission({ appId: app.productId!, productName: app.productName || '', role } as AppPermissionInfo)),
})
}
appPermissionsMap.value = newMap
return newMap
}
catch {
console.warn('[useAppPermission] 加载应用权限失败')
return appPermissionsMap.value
}
}
/**
* 设置单个应用的权限缓存
*/
function setAppPermission(info: AppPermissionInfo) {
const permission = buildPermission(info)
appPermissionsMap.value.set(info.appId, permission)
}
/**
* 获取指定应用的权限
*/
function getAppPermission(appId: number | undefined | null): AppPermission | null {
if (!appId) return null
return appPermissionsMap.value.get(appId) ?? null
}
/**
* 检查指定应用是否拥有某项权限
*/
function hasPermission(
appId: number | undefined | null,
permission: keyof AppPermission,
): boolean {
const perm = getAppPermission(appId)
if (!perm) return false
return !!perm[permission]
}
/**
* 检查当前用户在指定应用中的角色是否 >= 某个级别
*/
function hasRole(appId: number | undefined | null, minRole: AppRole): boolean {
const perm = getAppPermission(appId)
if (!perm) return false
return ROLE_HIERARCHY[perm.role] >= ROLE_HIERARCHY[minRole]
}
/**
* 清空权限缓存(登出时调用)
*/
function clearPermissions() {
appPermissionsMap.value.clear()
isPlatformDeveloper.value = false
hasCheckedAccess.value = false
}
// ============ Computed ============
/** 可访问的应用 ID 列表 */
const accessibleAppIds = computed(() => [...appPermissionsMap.value.keys()])
/** 是否已检查过访问权限 */
const isChecked = computed(() => hasCheckedAccess.value)
/** 是否正在加载 */
const isLoading = computed(() => loading.value)
/** 权限不足时的提示文字 */
function getNoPermissionTip(role?: AppRole): string {
const roleName = role ? ROLE_LABEL[role] : '当前角色'
return `当前身份为「${roleName}」,无法执行此操作。如需更多权限,请联系应用管理员。`
}
// ============ 敏感信息遮罩 ============
/**
* 遮罩敏感字段值
* @param value 原始值
* @param canView 是否有查看权限
* @param maskLength 遮罩长度,默认 6 个 *
*/
function maskSensitiveValue(value: string | null | undefined, canView: boolean, maskLength = 6): string {
if (canView) return value || ''
if (!value) return ''
const mask = '*'.repeat(maskLength)
// 对于较短的值直接全部遮罩,较长的值显示前几个字符
if (value.length <= maskLength) return mask
return value.slice(0, 4) + mask
}
// ============ 组合式函数导出 ============
export function useAppPermission() {
return {
// 状态
appPermissions: appPermissionsMap,
isPlatformDeveloper: readonly(isPlatformDeveloper),
isChecked: readonly(isChecked),
isLoading: readonly(isLoading),
// 方法
checkDeveloperAccess,
loadAppPermissions,
setAppPermission,
getAppPermission,
hasPermission,
hasRole,
clearPermissions,
getNoPermissionTip,
// 工具
accessibleAppIds,
maskSensitiveValue,
}
}