Files
AIclinicalresearch/frontend-v2/src/framework/router/RouteGuard.tsx
HaHafeng 83e395824b feat(rvw): complete journal config center MVP and tenant login routing
Deliver the RVW V4.0 journal configuration center across backend, frontend, migration, and docs with zh/en editorial baseline support and tenant-level prompt/template overrides. Unify tenant login to /:tenantCode/login and auto-enable RVW module when tenant type is JOURNAL to prevent post-login access gaps.

Made-with: Cursor
2026-03-15 11:51:35 +08:00

168 lines
4.8 KiB
TypeScript
Raw 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.
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 中已定义的路由:<Route path="/:tenantCode/login" />
*
* - 期刊租户下:/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 <Navigate to={loginTarget} state={{ from: location }} replace />
}
// 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 <Navigate to="/" replace />
}
// 显示权限不足页面
return (
<div style={{ padding: '100px 0', textAlign: 'center' }}>
<Result
status="403"
icon={<LockOutlined style={{ fontSize: 72, color: '#faad14' }} />}
title="权限不足"
subTitle={`您暂无访问 "${moduleName || requiredModule}" 模块的权限,请联系管理员开通。`}
extra={
<Button type="primary" onClick={() => window.history.back()}>
</Button>
}
/>
</div>
)
}
// 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
*/