284 lines
7.9 KiB
TypeScript
284 lines
7.9 KiB
TypeScript
/**
|
||
* 应用级权限管理 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,
|
||
}
|
||
}
|