import { ReactNode } from 'react' import { Navigate, useLocation } from 'react-router-dom' import { useAuth } from '../auth' import { extractTenantSlug } from '../tenant' import { Result, Button } from 'antd' import { LockOutlined } from '@ant-design/icons' /** * 路由守卫组件 * * 检查顺序: * 1. 检查是否已登录(未登录→携带租户标识重定向到登录页) * 2. 检查模块权限(无权限→显示权限不足页面) * 3. 有权限→渲染子组件 * * @version 2026-03-14 V4.0:租户感知重定向 * - 期刊租户路径(/jtim/*)→ /jtim/login?redirect=/jtim/dashboard * - 主平台路径(/rvw/*, /ai-qa/* 等)→ /login(行为不变) * - extractTenantSlug 由 useTenantObserver 模块统一维护,自动排除所有注册模块路径 */ /** * 构造未登录时的登录跳转目标。 * * 对齐 App.tsx 中已定义的路由: * * - 期刊租户下:/jtim/login?redirect=%2Fjtim%2Fdashboard * - 主站下:/login(保持原有行为,向后兼容) */ function buildLoginRedirect(pathname: string): string { const tenantSlug = extractTenantSlug(pathname) if (!tenantSlug) return '/login' const redirectParam = encodeURIComponent(pathname) return `/${tenantSlug}/login?redirect=${redirectParam}` } interface RouteGuardProps { /** 子组件 */ children: ReactNode /** 所需模块代码(如 'AIA', 'PKB') */ requiredModule?: string /** 模块名称(用于显示友好提示) */ moduleName?: string /** 是否重定向到首页(默认显示无权限页面) */ redirectToHome?: boolean /** 兼容旧参数(废弃) */ requiredVersion?: any } const RouteGuard = ({ children, requiredModule, moduleName, redirectToHome = false, }: RouteGuardProps) => { const location = useLocation() const { isAuthenticated, isLoading, user, hasModule } = useAuth() // 加载中显示空白 if (isLoading) { return null } // 1. 检查是否登录 if (!isAuthenticated) { // V4.0 租户感知重定向:保留 tenantSlug,让登录页渲染对应期刊品牌 const loginTarget = buildLoginRedirect(location.pathname) return } // 2. 检查模块权限(如果指定了 requiredModule) if (requiredModule && !hasModule(requiredModule)) { console.log('🔒 模块权限不足:', { module: moduleName || requiredModule, requiredModule, userModules: user?.modules, userId: user?.id, timestamp: new Date().toISOString(), }) if (redirectToHome) { return } // 显示权限不足页面 return (
} title="权限不足" subTitle={`您暂无访问 "${moduleName || requiredModule}" 模块的权限,请联系管理员开通。`} extra={ } />
) } // 3. 有权限,渲染子组件 return <>{children} } export default RouteGuard /** * 🛡️ 路由守卫最佳实践: * * 1. 双重防护策略: * - 第一道防线:TopNavigation(用户看不到无权限模块) * - 第二道防线:RouteGuard(防止URL直接访问) * - 为什么需要两道?防止用户通过浏览器直接输入URL绕过导航 * * 2. 权限检查时机: * ✅ 路由渲染前检查(RouteGuard) * ✅ API请求前检查(后端) * ✅ 组件渲染前检查(usePermission) * * 3. 无权限时的处理策略: * 【推荐】显示PermissionDenied页面 * - 优点:引导用户升级,商业转化机会 * - 缺点:需要额外页面 * 【备选】重定向到首页 * - 优点:简单直接 * - 缺点:用户体验不好,不利于转化 * * 4. 后续演进计划: * 【Week 2 Day 8-9】对接后端JWT认证 * - 实现真实的登录流程 * - 从token解析用户权限 * - 处理token过期 * * 【Week 3-4】ASL模块测试 * - ASL需要advanced权限 * - 测试权限控制是否生效 * - 优化无权限页面的转化率 * * 【Week 5+】完善权限系统 * - 动态权限配置 * - 功能级权限控制(不仅是模块级) * - 权限变更实时生效 * * 5. 安全注意事项: * ⚠️ 前端权限检查不是安全保障,仅用于用户体验 * ✅ 后端必须进行权限验证(真正的安全防线) * ✅ 敏感数据不应该发送到前端 * ✅ API调用必须携带认证token */