Files
leaudit-platform-frontend/app/hooks/usePermission.tsx
T
2026-04-29 22:25:06 +08:00

320 lines
9.0 KiB
TypeScript

/**
* 权限检查Hook
*
* 基于RBAC(基于角色的访问控制)模型,提供细粒度的权限检查功能。
*
* 权限键格式:module:resource:action
* 例如:prompt_template:create:write
*
* 使用示例:
* ```typescript
* const { hasPermission, canCreate, canEdit } = usePermission();
*
* // 检查单个权限
* if (hasPermission('prompt_template:create:write')) {
* // 显示创建按钮
* }
*
* // 使用便捷方法
* if (canCreate('prompt_template')) {
* // 显示创建按钮
* }
* ```
*/
import { useRouteLoaderData, useLocation } from "@remix-run/react";
import { normalizeRoutePathForPermission } from "~/utils/route-alias";
interface RootLoaderData {
permissions?: string[];
permissionMap?: Record<string, string[]>; // ✅ 新增:权限映射表
userRole: string;
userArea?: string;
userInfo?: {
role_id?: number;
role_key?: string;
role_name?: string;
};
}
/**
* 交叉评查模块默认权限配置
* 当 permissionMap 中没有配置时,使用此默认配置
*/
const CROSS_CHECKING_DEFAULT_PERMISSIONS: Record<string, string[]> = {
'/cross-checking': [
'cross_review:task:read',
// 'cross_review:task:create',
// 'cross_review:document:complete',
'cross_review:progress:view',
'cross_review:proposal:create',
'cross_review:proposal:delete',
'cross_review:proposal:read',
// 'cross_review:proposal:vote'
],
'/cross-checking/upload': [
// 'cross_review:task:create'
],
'/cross-checking/result': [
// 'cross_review:document:complete',
'cross_review:progress:view',
'cross_review:proposal:create',
'cross_review:proposal:delete',
'cross_review:proposal:read',
// 'cross_review:proposal:vote'
]
};
export function usePermission() {
const rootData = useRouteLoaderData("root") as RootLoaderData;
const location = useLocation();
// 从root loader获取权限映射表
const permissionMap = rootData?.permissionMap || {};
const userRole = rootData?.userRole || 'common';
const userArea = rootData?.userArea || '';
// 🔑 根据当前路由获取权限列表
const currentPath = normalizeRoutePathForPermission(location.pathname);
// console.log('currentPath', currentPath)
// 获取当前路由的权限:优先使用 permissionMap,否则使用交叉评查默认配置
const getCrossCheckingPermissions = (): string[] => {
// 检查是否是交叉评查相关路由
if (currentPath.startsWith('/cross-checking')) {
// 精确匹配
if (CROSS_CHECKING_DEFAULT_PERMISSIONS[currentPath]) {
return CROSS_CHECKING_DEFAULT_PERMISSIONS[currentPath];
}
// 处理带参数的路由,如 /cross-checking/result?id=xxx
const basePath = currentPath.split('?')[0];
if (CROSS_CHECKING_DEFAULT_PERMISSIONS[basePath]) {
return CROSS_CHECKING_DEFAULT_PERMISSIONS[basePath];
}
}
return [];
};
// 优先使用 permissionMap 中的权限,如果没有则使用交叉评查默认权限
const currentPermissions = permissionMap[currentPath]?.length > 0
? permissionMap[currentPath]
: getCrossCheckingPermissions();
// 向后兼容:如果存在旧的permissions数组,也要支持
const legacyPermissions = rootData?.permissions || [];
/**
* 检查是否有指定权限
* @param permissionKey 权限键,如 "prompt_template:create:write"
* @returns boolean
*/
const hasPermission = (permissionKey: string): boolean => {
// 优先使用当前路由的权限列表
if (currentPermissions.length > 0) {
return currentPermissions.includes(permissionKey);
}
// 向后兼容:支持旧的permissions数组
if (legacyPermissions.length > 0) {
return legacyPermissions.includes(permissionKey);
}
// 降级方案:没有权限映射时绝不默认放开写权限,只保留只读能力。
if (permissionKey.includes(':read')) {
return true;
}
return false;
};
/**
* 检查是否有指定路由的权限
* @param path 路由路径,如 "/prompts"
* @param permissionKey 权限键
* @returns boolean
*/
const hasRoutePermission = (path: string, permissionKey: string): boolean => {
const routePermissions = permissionMap[path] || [];
return routePermissions.includes(permissionKey);
};
/**
* 获取当前路由的所有权限
* @returns 权限列表
*/
const getCurrentPermissions = (): string[] => {
return currentPermissions;
};
/**
* 获取指定路由的所有权限
* @param path 路由路径
* @returns 权限列表
*/
const getRoutePermissions = (path: string): string[] => {
return permissionMap[path] || [];
};
/**
* 检查是否有指定模块的任意权限
* @param module 模块名,如 "prompt_template"
* @returns boolean
*/
const hasModulePermission = (module: string): boolean => {
if (currentPermissions.length > 0) {
return currentPermissions.some(p => p.startsWith(`${module}:`));
}
if (legacyPermissions.length > 0) {
return legacyPermissions.some(p => p.startsWith(`${module}:`));
}
// 降级方案只保留只读模块识别,避免 0/6 权限时被角色名放大。
return hasPermission(`${module}:list:read`) || hasPermission(`${module}:detail:read`);
};
/**
* 检查是否有指定资源和动作的权限
* @param module 模块名,如 "prompt_template"
* @param resource 资源名,如 "create", "list", "detail"
* @param action 动作,如 "read", "write", "delete"
* @returns boolean
*/
const hasResourcePermission = (module: string, resource: string, action: string): boolean => {
const permissionKey = `${module}:${resource}:${action}`;
return hasPermission(permissionKey);
};
/**
* 批量检查权限(需要全部满足)
* @param permissionKeys 权限键数组
* @returns boolean
*/
const hasAllPermissions = (permissionKeys: string[]): boolean => {
return permissionKeys.every(key => hasPermission(key));
};
/**
* 批量检查权限(满足任意一个即可)
* @param permissionKeys 权限键数组
* @returns boolean
*/
const hasAnyPermission = (permissionKeys: string[]): boolean => {
return permissionKeys.some(key => hasPermission(key));
};
// 便捷方法:检查常见操作权限
const canCreate = (module: string): boolean => {
return hasResourcePermission(module, 'create', 'write');
};
const canRead = (module: string, resource: string = 'list'): boolean => {
return hasResourcePermission(module, resource, 'read');
};
const canUpdate = (module: string): boolean => {
return hasResourcePermission(module, 'update', 'write');
};
const canDelete = (module: string): boolean => {
return hasResourcePermission(module, 'delete', 'delete');
};
const canList = (module: string): boolean => {
return hasResourcePermission(module, 'list', 'read');
};
const canView = (module: string): boolean => {
return hasResourcePermission(module, 'detail', 'read');
};
/**
* 检查是否有批量操作权限
* @param module 模块名,如 "evaluation_group"
* @returns boolean - 检查是否有 module:batch:write 权限
*/
const canBatch = (module: string): boolean => {
return hasResourcePermission(module, 'batch', 'write');
};
return {
// 原始权限数据
permissions: currentPermissions, // ✅ 返回当前路由的权限
permissionMap, // ✅ 返回完整的权限映射表
userRole,
userArea,
// 基础检查方法
hasPermission,
hasModulePermission,
hasResourcePermission,
hasAllPermissions,
hasAnyPermission,
// ✅ 新增:路由权限查询方法
hasRoutePermission,
getCurrentPermissions,
getRoutePermissions,
// 便捷方法
canCreate,
canRead,
canUpdate,
canDelete,
canList,
canView,
canBatch // ✅ 新增:批量操作权限检查
};
}
/**
* 权限组件包装器
*
* 根据权限控制子组件的显示/隐藏
*
* 使用示例:
* ```typescript
* <PermissionGuard permission="prompt_template:create:write">
* <Button>新增模板</Button>
* </PermissionGuard>
* ```
*/
interface PermissionGuardProps {
permission?: string;
permissions?: string[];
requireAll?: boolean; // true=需要全部权限,false=任意一个即可
fallback?: React.ReactNode; // 无权限时显示的内容
children: React.ReactNode;
}
export function PermissionGuard({
permission,
permissions: permissionList,
requireAll = false,
fallback = null,
children
}: PermissionGuardProps) {
const { hasPermission, hasAllPermissions, hasAnyPermission } = usePermission();
let hasAccess = false;
if (permission) {
// 单个权限检查
hasAccess = hasPermission(permission);
} else if (permissionList && permissionList.length > 0) {
// 多个权限检查
hasAccess = requireAll
? hasAllPermissions(permissionList)
: hasAnyPermission(permissionList);
} else {
// 没有指定权限,默认允许访问
hasAccess = true;
}
if (!hasAccess) {
return <>{fallback}</>;
}
return <>{children}</>;
}