新版官网模板
This commit is contained in:
310
app/composables/useAppPermission.ts
Normal file
310
app/composables/useAppPermission.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* 应用级权限管理 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)
|
||||
|
||||
// 用户信息(供其他组件复用,避免重复请求)
|
||||
const user = ref<{
|
||||
userId?: number
|
||||
username?: string
|
||||
nickname?: string
|
||||
phone?: string
|
||||
mobile?: string
|
||||
type?: number
|
||||
} | null>(null)
|
||||
|
||||
/**
|
||||
* 设置当前用户信息(布局中调用)
|
||||
*/
|
||||
function setCurrentUser(userInfo: typeof user.value) {
|
||||
user.value = userInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
function getCurrentUser() {
|
||||
return user.value
|
||||
}
|
||||
|
||||
// ============ 核心方法 ============
|
||||
|
||||
/**
|
||||
* 检查用户是否有开发者中心访问权限
|
||||
* 并自动加载可访问的应用列表
|
||||
*/
|
||||
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 {
|
||||
// 状态
|
||||
user,
|
||||
appPermissions: appPermissionsMap,
|
||||
isPlatformDeveloper: readonly(isPlatformDeveloper),
|
||||
isChecked: readonly(isChecked),
|
||||
isLoading: readonly(isLoading),
|
||||
|
||||
// 方法
|
||||
setCurrentUser,
|
||||
getCurrentUser,
|
||||
checkDeveloperAccess,
|
||||
loadAppPermissions,
|
||||
setAppPermission,
|
||||
getAppPermission,
|
||||
hasPermission,
|
||||
hasRole,
|
||||
clearPermissions,
|
||||
getNoPermissionTip,
|
||||
|
||||
// 工具
|
||||
accessibleAppIds,
|
||||
maskSensitiveValue,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user