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
168 lines
4.8 KiB
TypeScript
168 lines
4.8 KiB
TypeScript
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
|
||
*/
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|