feat(frontend): add frontend-v2 modular architecture (Task 17)
- React 19 + TypeScript + Vite - Module registration mechanism with dynamic loading - Permission management system (basic/advanced/premium) - Route guards for access control - Error boundaries for module isolation - 6 business module placeholders (AIA/ASL/PKB/DC/SSA/ST) - Top navigation layout - Tailwind CSS 3 + Ant Design 5
This commit is contained in:
44
frontend-v2/src/framework/layout/MainLayout.tsx
Normal file
44
frontend-v2/src/framework/layout/MainLayout.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Suspense } from 'react'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import { Spin } from 'antd'
|
||||
import TopNavigation from './TopNavigation'
|
||||
import ErrorBoundary from '../modules/ErrorBoundary'
|
||||
|
||||
/**
|
||||
* 主布局组件
|
||||
*
|
||||
* @description
|
||||
* - 顶部导航栏
|
||||
* - 错误边界保护 ⭐ Week 2 Day 7 新增
|
||||
* - 懒加载支持(Suspense)
|
||||
* - 主内容区(Outlet)
|
||||
*
|
||||
* @version Week 2 Day 7 - 任务17:集成错误边界
|
||||
*/
|
||||
const MainLayout = () => {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-gray-50">
|
||||
{/* 顶部导航 */}
|
||||
<TopNavigation />
|
||||
|
||||
{/* 主内容区 - 添加错误边界保护 ⭐ */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
<ErrorBoundary moduleName="主应用">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<Spin size="large" tip="加载中..." />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainLayout
|
||||
|
||||
|
||||
140
frontend-v2/src/framework/layout/TopNavigation.tsx
Normal file
140
frontend-v2/src/framework/layout/TopNavigation.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { Dropdown, Avatar, Tooltip } from 'antd'
|
||||
import { UserOutlined, LogoutOutlined, SettingOutlined, LockOutlined } from '@ant-design/icons'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { getAvailableModules } from '../modules/moduleRegistry'
|
||||
import { usePermission } from '../permission'
|
||||
|
||||
/**
|
||||
* 顶部导航栏组件
|
||||
*
|
||||
* @description
|
||||
* - 显示Logo和平台名称
|
||||
* - 显示模块导航(根据用户权限过滤)⭐ Week 2 Day 7 新增
|
||||
* - 显示用户菜单
|
||||
*
|
||||
* @version Week 2 Day 7 - 任务17:集成权限系统
|
||||
*/
|
||||
const TopNavigation = () => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { user, checkModulePermission, logout } = usePermission()
|
||||
|
||||
// 获取用户有权访问的模块列表(权限过滤)⭐ 新增
|
||||
const availableModules = getAvailableModules(user?.version || 'basic')
|
||||
|
||||
// 获取当前激活的模块
|
||||
const activeModule = availableModules.find(module =>
|
||||
location.pathname.startsWith(module.path)
|
||||
)
|
||||
|
||||
// 用户菜单
|
||||
const userMenuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: 'profile',
|
||||
icon: <UserOutlined />,
|
||||
label: '个人中心',
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
icon: <SettingOutlined />,
|
||||
label: '设置',
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <LogoutOutlined />,
|
||||
label: '退出登录',
|
||||
danger: true,
|
||||
},
|
||||
]
|
||||
|
||||
// 处理用户菜单点击
|
||||
const handleUserMenuClick = ({ key }: { key: string }) => {
|
||||
if (key === 'logout') {
|
||||
logout()
|
||||
navigate('/')
|
||||
} else {
|
||||
navigate(`/user/${key}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理模块点击(检查权限)
|
||||
const handleModuleClick = (modulePath: string, requiredVersion?: string) => {
|
||||
if (!checkModulePermission(requiredVersion as any)) {
|
||||
// 理论上不会到这里,因为已经过滤了
|
||||
console.warn('权限不足,无法访问该模块')
|
||||
return
|
||||
}
|
||||
navigate(modulePath)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-16 bg-white border-b border-gray-200 px-6 flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<div
|
||||
className="flex items-center gap-3 cursor-pointer"
|
||||
onClick={() => navigate('/')}
|
||||
>
|
||||
<div className="text-2xl">🏥</div>
|
||||
<span className="text-xl font-bold text-blue-600">AI临床研究平台</span>
|
||||
</div>
|
||||
|
||||
{/* 导航菜单 - 根据用户权限动态显示 ⭐ Week 2 Day 7 更新 */}
|
||||
<div className="flex items-center gap-2">
|
||||
{availableModules.map(module => {
|
||||
const hasPermission = checkModulePermission(module.requiredVersion as any)
|
||||
const isActive = activeModule?.id === module.id
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={module.id}
|
||||
title={!hasPermission ? `需要${module.requiredVersion}版本` : ''}
|
||||
>
|
||||
<div
|
||||
onClick={() => hasPermission && handleModuleClick(module.path, module.requiredVersion)}
|
||||
className={`
|
||||
px-4 py-2 rounded-md transition-all
|
||||
${!hasPermission
|
||||
? 'text-gray-400 cursor-not-allowed opacity-50'
|
||||
: isActive
|
||||
? 'bg-blue-50 text-blue-600 font-semibold cursor-pointer'
|
||||
: 'text-gray-600 hover:bg-gray-50 hover:text-blue-600 cursor-pointer'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{!hasPermission && <LockOutlined className="text-xs" />}
|
||||
{module.name}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 用户菜单 - 显示真实用户信息 ⭐ Week 2 Day 7 更新 */}
|
||||
<Dropdown
|
||||
menu={{ items: userMenuItems, onClick: handleUserMenuClick }}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<div className="flex items-center gap-2 cursor-pointer px-3 py-2 rounded-md hover:bg-gray-50">
|
||||
<Avatar
|
||||
src={user?.avatar}
|
||||
icon={<UserOutlined />}
|
||||
size="small"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 text-sm">{user?.name || '访客'}</span>
|
||||
<span className="text-xs text-gray-400">{user?.version || 'basic'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TopNavigation
|
||||
|
||||
152
frontend-v2/src/framework/modules/ErrorBoundary.tsx
Normal file
152
frontend-v2/src/framework/modules/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import { Component, ErrorInfo, ReactNode } from 'react'
|
||||
import ModuleErrorFallback from './ModuleErrorFallback'
|
||||
|
||||
/**
|
||||
* 错误边界组件
|
||||
*
|
||||
* @description
|
||||
* React错误边界,捕获子组件树中的JavaScript错误
|
||||
* 防止整个应用崩溃,提供友好的错误提示和恢复机制
|
||||
*
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ErrorBoundary>
|
||||
* <YourComponent />
|
||||
* </ErrorBoundary>
|
||||
* ```
|
||||
*
|
||||
* 注意事项:
|
||||
* - 错误边界无法捕获以下错误:
|
||||
* 1. 事件处理器中的错误(使用try-catch)
|
||||
* 2. 异步代码中的错误(使用try-catch)
|
||||
* 3. 服务端渲染的错误
|
||||
* 4. 错误边界自身的错误
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
children: ReactNode
|
||||
/** 错误回退UI(可选,默认使用ModuleErrorFallback) */
|
||||
fallback?: ReactNode
|
||||
/** 模块名称(用于错误日志) */
|
||||
moduleName?: string
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean
|
||||
error: Error | null
|
||||
errorInfo: ErrorInfo | null
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
hasError: false,
|
||||
error: null,
|
||||
errorInfo: null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当子组件抛出错误时调用
|
||||
*/
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return {
|
||||
hasError: true,
|
||||
error,
|
||||
errorInfo: null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误被捕获后调用,用于记录错误日志
|
||||
*/
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
const { moduleName } = this.props
|
||||
|
||||
// 记录错误日志(当前使用console,后续接入真实日志系统)
|
||||
console.error('🚨 ErrorBoundary caught an error:', {
|
||||
module: moduleName || 'Unknown',
|
||||
error: error.toString(),
|
||||
componentStack: errorInfo.componentStack,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
|
||||
// 更新状态以显示错误信息
|
||||
this.setState({
|
||||
error,
|
||||
errorInfo,
|
||||
})
|
||||
|
||||
// TODO: 接入真实日志系统
|
||||
// 例如:Sentry.captureException(error, { extra: errorInfo })
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置错误状态,尝试重新渲染
|
||||
*/
|
||||
handleReset = () => {
|
||||
this.setState({
|
||||
hasError: false,
|
||||
error: null,
|
||||
errorInfo: null,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hasError, error, errorInfo } = this.state
|
||||
const { children, fallback, moduleName } = this.props
|
||||
|
||||
if (hasError) {
|
||||
// 如果提供了自定义fallback,使用自定义UI
|
||||
if (fallback) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
// 使用默认错误提示UI
|
||||
return (
|
||||
<ModuleErrorFallback
|
||||
error={error}
|
||||
errorInfo={errorInfo}
|
||||
onReset={this.handleReset}
|
||||
moduleName={moduleName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
||||
/**
|
||||
* 🔧 开发说明:错误边界最佳实践
|
||||
*
|
||||
* 1. 错误边界的放置位置:
|
||||
* - 全局级别:App根组件
|
||||
* - 模块级别:每个业务模块入口
|
||||
* - 关键组件级别:复杂的第三方组件
|
||||
*
|
||||
* 2. 错误日志:
|
||||
* 【当前阶段】console.error(开发调试)
|
||||
* 【Week 5+】接入 Sentry/LogRocket 等日志系统
|
||||
*
|
||||
* 3. 错误恢复策略:
|
||||
* - 提供"重试"按钮,尝试重新渲染
|
||||
* - 提供"返回首页"按钮,避免用户卡住
|
||||
* - 显示友好的错误提示,而非技术错误信息
|
||||
*
|
||||
* 4. 无法捕获的错误类型:
|
||||
* - 事件处理器:使用 try-catch
|
||||
* - 异步代码:使用 try-catch 或 .catch()
|
||||
* - setTimeout/setInterval:使用 try-catch 包裹回调
|
||||
*
|
||||
* 5. 生产环境优化:
|
||||
* - 隐藏详细错误堆栈(避免泄露代码信息)
|
||||
* - 提供错误报告功能(用户可以反馈问题)
|
||||
* - 记录完整错误上下文(用户操作、路由、设备信息)
|
||||
*/
|
||||
|
||||
215
frontend-v2/src/framework/modules/ModuleErrorFallback.tsx
Normal file
215
frontend-v2/src/framework/modules/ModuleErrorFallback.tsx
Normal file
@@ -0,0 +1,215 @@
|
||||
import { ErrorInfo } from 'react'
|
||||
import { Result, Button, Collapse, Typography } from 'antd'
|
||||
import { BugOutlined, ReloadOutlined, HomeOutlined } from '@ant-design/icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
/**
|
||||
* 模块错误提示组件
|
||||
*
|
||||
* @description
|
||||
* 当模块加载或运行出错时显示的友好错误页面
|
||||
* 提供重试和返回首页的操作
|
||||
*
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*/
|
||||
|
||||
const { Paragraph, Text } = Typography
|
||||
|
||||
interface ModuleErrorFallbackProps {
|
||||
/** 错误对象 */
|
||||
error: Error | null
|
||||
/** 错误详细信息 */
|
||||
errorInfo?: ErrorInfo | null
|
||||
/** 重置错误状态的回调 */
|
||||
onReset?: () => void
|
||||
/** 模块名称 */
|
||||
moduleName?: string
|
||||
}
|
||||
|
||||
const ModuleErrorFallback = ({
|
||||
error,
|
||||
errorInfo,
|
||||
onReset,
|
||||
moduleName,
|
||||
}: ModuleErrorFallbackProps) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
// 是否显示详细错误信息(开发环境显示,生产环境隐藏)
|
||||
const isDevelopment = import.meta.env?.DEV ?? false
|
||||
|
||||
/**
|
||||
* 处理重试
|
||||
*/
|
||||
const handleRetry = () => {
|
||||
if (onReset) {
|
||||
onReset()
|
||||
} else {
|
||||
// 如果没有提供onReset,刷新页面
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回首页
|
||||
*/
|
||||
const handleGoHome = () => {
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center bg-gray-50 p-8">
|
||||
<div className="max-w-2xl w-full">
|
||||
<Result
|
||||
status="error"
|
||||
icon={<BugOutlined style={{ fontSize: 72, color: '#ff4d4f' }} />}
|
||||
title={
|
||||
<span className="text-2xl">
|
||||
{moduleName ? `${moduleName}模块` : '页面'}加载失败
|
||||
</span>
|
||||
}
|
||||
subTitle={
|
||||
<div className="space-y-3 mt-4">
|
||||
<Paragraph className="text-gray-600">
|
||||
抱歉,模块加载时遇到了一些问题。这可能是由于网络问题或代码错误导致的。
|
||||
</Paragraph>
|
||||
<Paragraph className="text-gray-500 text-sm">
|
||||
您可以尝试重新加载,或者返回首页。如果问题持续存在,请联系技术支持。
|
||||
</Paragraph>
|
||||
</div>
|
||||
}
|
||||
extra={
|
||||
<div className="flex gap-3 justify-center">
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={handleRetry}
|
||||
size="large"
|
||||
>
|
||||
重新加载
|
||||
</Button>
|
||||
<Button
|
||||
icon={<HomeOutlined />}
|
||||
onClick={handleGoHome}
|
||||
size="large"
|
||||
>
|
||||
返回首页
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 开发环境:显示详细错误信息 */}
|
||||
{isDevelopment && error && (
|
||||
<div className="mt-8">
|
||||
<Collapse
|
||||
ghost
|
||||
items={[
|
||||
{
|
||||
key: 'error-details',
|
||||
label: (
|
||||
<Text type="secondary" className="text-sm">
|
||||
🔧 开发模式:查看错误详情
|
||||
</Text>
|
||||
),
|
||||
children: (
|
||||
<div className="space-y-4">
|
||||
{/* 错误消息 */}
|
||||
<div>
|
||||
<Text strong className="text-red-600">错误消息:</Text>
|
||||
<Paragraph
|
||||
code
|
||||
copyable
|
||||
className="mt-2 bg-red-50 p-3 rounded"
|
||||
>
|
||||
{error.toString()}
|
||||
</Paragraph>
|
||||
</div>
|
||||
|
||||
{/* 错误堆栈 */}
|
||||
{error.stack && (
|
||||
<div>
|
||||
<Text strong className="text-red-600">错误堆栈:</Text>
|
||||
<Paragraph
|
||||
code
|
||||
copyable
|
||||
className="mt-2 bg-gray-50 p-3 rounded text-xs overflow-x-auto"
|
||||
style={{ whiteSpace: 'pre-wrap' }}
|
||||
>
|
||||
{error.stack}
|
||||
</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 组件堆栈 */}
|
||||
{errorInfo?.componentStack && (
|
||||
<div>
|
||||
<Text strong className="text-red-600">组件堆栈:</Text>
|
||||
<Paragraph
|
||||
code
|
||||
copyable
|
||||
className="mt-2 bg-gray-50 p-3 rounded text-xs overflow-x-auto"
|
||||
style={{ whiteSpace: 'pre-wrap' }}
|
||||
>
|
||||
{errorInfo.componentStack}
|
||||
</Paragraph>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 时间戳 */}
|
||||
<div>
|
||||
<Text type="secondary" className="text-xs">
|
||||
错误时间:{new Date().toLocaleString('zh-CN')}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 生产环境:错误ID提示 */}
|
||||
{!isDevelopment && (
|
||||
<div className="mt-6 text-center">
|
||||
<Text type="secondary" className="text-xs">
|
||||
错误ID: {Date.now().toString(36)}
|
||||
</Text>
|
||||
<br />
|
||||
<Text type="secondary" className="text-xs">
|
||||
如需技术支持,请提供此错误ID
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModuleErrorFallback
|
||||
|
||||
/**
|
||||
* 🎨 设计说明:
|
||||
*
|
||||
* 1. 用户体验:
|
||||
* - ✅ 友好的错误提示(不显示技术术语)
|
||||
* - ✅ 明确的操作指引(重试/返回首页)
|
||||
* - ✅ 视觉上与整体风格一致(Ant Design Result)
|
||||
*
|
||||
* 2. 开发体验:
|
||||
* - ✅ 开发环境显示详细错误(方便调试)
|
||||
* - ✅ 错误信息可复制(方便分享给团队)
|
||||
* - ✅ 显示完整堆栈(快速定位问题)
|
||||
*
|
||||
* 3. 生产环境:
|
||||
* - ✅ 隐藏技术细节(安全性)
|
||||
* - ✅ 提供错误ID(方便追踪)
|
||||
* - ✅ 引导用户联系支持
|
||||
*
|
||||
* 4. 后续优化:
|
||||
* - [ ] 添加"反馈问题"按钮
|
||||
* - [ ] 集成错误报告系统
|
||||
* - [ ] 记录用户操作路径
|
||||
* - [ ] 自动重试机制(网络错误)
|
||||
*/
|
||||
|
||||
125
frontend-v2/src/framework/modules/moduleRegistry.ts
Normal file
125
frontend-v2/src/framework/modules/moduleRegistry.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { lazy } from 'react'
|
||||
import { ModuleDefinition } from './types'
|
||||
import {
|
||||
MessageOutlined,
|
||||
FileSearchOutlined,
|
||||
FolderOpenOutlined,
|
||||
ClearOutlined,
|
||||
BarChartOutlined,
|
||||
LineChartOutlined
|
||||
} from '@ant-design/icons'
|
||||
|
||||
/**
|
||||
* 模块注册中心
|
||||
* 按照平台架构文档顺序注册所有业务模块
|
||||
* 参考:docs/01-平台基础层/06-前端架构/01-前端总体架构设计.md
|
||||
*/
|
||||
export const MODULES: ModuleDefinition[] = [
|
||||
{
|
||||
id: 'ai-qa',
|
||||
name: 'AI问答',
|
||||
path: '/ai-qa',
|
||||
icon: MessageOutlined,
|
||||
component: lazy(() => import('@/modules/aia')),
|
||||
placeholder: true, // 后续重写
|
||||
requiredVersion: 'basic',
|
||||
description: '基于LLM的智能问答系统',
|
||||
},
|
||||
{
|
||||
id: 'literature-platform',
|
||||
name: 'AI智能文献',
|
||||
path: '/literature',
|
||||
icon: FileSearchOutlined,
|
||||
component: lazy(() => import('@/modules/asl')),
|
||||
placeholder: false, // Week 3 开发
|
||||
requiredVersion: 'advanced',
|
||||
description: 'AI驱动的文献筛选和分析系统',
|
||||
standalone: true, // 支持独立运行
|
||||
},
|
||||
{
|
||||
id: 'knowledge-base',
|
||||
name: '知识库',
|
||||
path: '/knowledge-base',
|
||||
icon: FolderOpenOutlined,
|
||||
component: lazy(() => import('@/modules/pkb')),
|
||||
placeholder: true, // 后续重写
|
||||
requiredVersion: 'basic',
|
||||
description: '个人知识库管理系统',
|
||||
},
|
||||
{
|
||||
id: 'data-cleaning',
|
||||
name: '智能数据清洗',
|
||||
path: '/data-cleaning',
|
||||
icon: ClearOutlined,
|
||||
component: lazy(() => import('@/modules/dc')),
|
||||
placeholder: true, // 占位
|
||||
requiredVersion: 'advanced',
|
||||
description: '智能数据清洗整理工具',
|
||||
},
|
||||
{
|
||||
id: 'statistical-analysis',
|
||||
name: '智能统计分析',
|
||||
path: '/intelligent-analysis',
|
||||
icon: BarChartOutlined,
|
||||
component: lazy(() => import('@/modules/ssa')),
|
||||
placeholder: true, // Java团队开发,前端集成
|
||||
requiredVersion: 'premium',
|
||||
description: '智能统计分析系统(Java团队开发)',
|
||||
isExternal: true, // 外部模块
|
||||
},
|
||||
{
|
||||
id: 'statistical-tools',
|
||||
name: '统计分析工具',
|
||||
path: '/statistical-tools',
|
||||
icon: LineChartOutlined,
|
||||
component: lazy(() => import('@/modules/st')),
|
||||
placeholder: true, // Java团队开发,前端集成
|
||||
requiredVersion: 'premium',
|
||||
description: '统计分析工具集(Java团队开发)',
|
||||
isExternal: true, // 外部模块
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* 根据ID获取模块
|
||||
*/
|
||||
export const getModuleById = (id: string): ModuleDefinition | undefined => {
|
||||
return MODULES.find(module => module.id === id)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路径获取模块
|
||||
*/
|
||||
export const getModuleByPath = (path: string): ModuleDefinition | undefined => {
|
||||
return MODULES.find(module => path.startsWith(module.path))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用模块(根据权限过滤)
|
||||
*
|
||||
* @param userVersion 用户版本(权限等级)
|
||||
* @returns 用户有权访问的模块列表
|
||||
*
|
||||
* @version Week 2 Day 7 - 任务17:实现权限过滤逻辑
|
||||
*/
|
||||
export const getAvailableModules = (userVersion: string = 'premium'): ModuleDefinition[] => {
|
||||
// 权限等级映射
|
||||
const versionLevel: Record<string, number> = {
|
||||
basic: 1,
|
||||
advanced: 2,
|
||||
premium: 3,
|
||||
}
|
||||
|
||||
const currentLevel = versionLevel[userVersion] || 0
|
||||
|
||||
// 过滤出用户有权限访问的模块
|
||||
return MODULES.filter(module => {
|
||||
// 如果模块没有权限要求,所有人都可以访问
|
||||
if (!module.requiredVersion) return true
|
||||
|
||||
// 检查用户权限等级是否满足模块要求
|
||||
const requiredLevel = versionLevel[module.requiredVersion] || 0
|
||||
return currentLevel >= requiredLevel
|
||||
})
|
||||
}
|
||||
|
||||
64
frontend-v2/src/framework/modules/types.ts
Normal file
64
frontend-v2/src/framework/modules/types.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { ReactNode, LazyExoticComponent, ComponentType } from 'react'
|
||||
import { RouteObject } from 'react-router-dom'
|
||||
|
||||
/**
|
||||
* 用户版本类型
|
||||
*/
|
||||
export type UserVersion = 'basic' | 'advanced' | 'premium'
|
||||
|
||||
/**
|
||||
* 左侧导航项配置
|
||||
*/
|
||||
export interface SideNavItem {
|
||||
id: string
|
||||
label: string
|
||||
path: string
|
||||
icon?: ReactNode
|
||||
children?: SideNavItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块定义接口
|
||||
* 每个业务模块必须实现这个接口
|
||||
*/
|
||||
export interface ModuleDefinition {
|
||||
/** 模块唯一标识 */
|
||||
id: string
|
||||
|
||||
/** 模块名称(显示在导航栏) */
|
||||
name: string
|
||||
|
||||
/** 模块路由前缀 */
|
||||
path: string
|
||||
|
||||
/** 模块图标组件(可选) */
|
||||
icon?: ComponentType
|
||||
|
||||
/** 模块入口组件(懒加载) */
|
||||
component: LazyExoticComponent<ComponentType<any>>
|
||||
|
||||
/** 模块路由配置 */
|
||||
routes?: RouteObject[]
|
||||
|
||||
/** 模块是否有左侧导航 */
|
||||
hasSideNav?: boolean
|
||||
|
||||
/** 左侧导航配置(如果有) */
|
||||
sideNavConfig?: SideNavItem[]
|
||||
|
||||
/** 权限要求(可选) */
|
||||
requiredVersion?: UserVersion
|
||||
|
||||
/** 是否为占位模块 */
|
||||
placeholder?: boolean
|
||||
|
||||
/** 是否支持独立部署 */
|
||||
standalone?: boolean
|
||||
|
||||
/** 是否为外部模块(如Java团队开发) */
|
||||
isExternal?: boolean
|
||||
|
||||
/** 模块描述 */
|
||||
description?: string
|
||||
}
|
||||
|
||||
140
frontend-v2/src/framework/permission/PermissionContext.tsx
Normal file
140
frontend-v2/src/framework/permission/PermissionContext.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import { createContext, useState, useCallback, ReactNode } from 'react'
|
||||
import { UserInfo, PermissionContextType, checkVersionLevel, UserVersion } from './types'
|
||||
|
||||
/**
|
||||
* 权限上下文
|
||||
*
|
||||
* @description 提供全局权限状态管理
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*
|
||||
* 注意:当前阶段(Week 2)用户信息为硬编码,方便开发测试
|
||||
* 后续计划:Week 2 Day 8-9 对接后端JWT认证
|
||||
*/
|
||||
|
||||
/**
|
||||
* 模拟用户数据(开发阶段使用)
|
||||
*
|
||||
* 🔧 开发说明:
|
||||
* - 当前硬编码为 premium 权限,可以访问所有模块
|
||||
* - 方便开发和测试所有功能
|
||||
* - 后续将从后端 JWT token 中解析真实用户信息
|
||||
*/
|
||||
const MOCK_USER: UserInfo = {
|
||||
id: 'test-user-001',
|
||||
name: '测试研究员',
|
||||
email: 'test@example.com',
|
||||
version: 'premium', // 👈 硬编码为最高权限
|
||||
avatar: null,
|
||||
isTrial: false,
|
||||
trialEndsAt: null,
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建权限上下文
|
||||
*/
|
||||
export const PermissionContext = createContext<PermissionContextType | undefined>(undefined)
|
||||
|
||||
/**
|
||||
* 权限提供者组件
|
||||
*/
|
||||
interface PermissionProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const PermissionProvider = ({ children }: PermissionProviderProps) => {
|
||||
// 当前用户状态(开发阶段使用模拟数据)
|
||||
const [user, setUser] = useState<UserInfo | null>(MOCK_USER)
|
||||
|
||||
/**
|
||||
* 检查模块权限
|
||||
* @param requiredVersion 所需权限等级
|
||||
* @returns 是否有权限访问
|
||||
*/
|
||||
const checkModulePermission = useCallback(
|
||||
(requiredVersion?: UserVersion): boolean => {
|
||||
// 未登录用户无权限
|
||||
if (!user) return false
|
||||
|
||||
// 没有权限要求,允许访问
|
||||
if (!requiredVersion) return true
|
||||
|
||||
// 检查权限等级
|
||||
return checkVersionLevel(user.version, requiredVersion)
|
||||
},
|
||||
[user]
|
||||
)
|
||||
|
||||
/**
|
||||
* 检查功能权限
|
||||
* @param feature 功能标识
|
||||
* @returns 是否有权限使用该功能
|
||||
*
|
||||
* TODO: 后续可以基于功能列表进行更细粒度的权限控制
|
||||
*/
|
||||
const checkFeaturePermission = useCallback(
|
||||
(feature: string): boolean => {
|
||||
if (!user) return false
|
||||
|
||||
// 当前简化实现:premium用户拥有所有功能
|
||||
if (user.version === 'premium') return true
|
||||
|
||||
// 后续可以扩展为基于功能配置表的权限检查
|
||||
console.log('Feature permission check:', feature)
|
||||
return true
|
||||
},
|
||||
[user]
|
||||
)
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
const logout = useCallback(() => {
|
||||
setUser(null)
|
||||
// TODO: 清除后端session/token
|
||||
console.log('User logged out')
|
||||
}, [])
|
||||
|
||||
const value: PermissionContextType = {
|
||||
user,
|
||||
isAuthenticated: !!user,
|
||||
checkModulePermission,
|
||||
checkFeaturePermission,
|
||||
setUser,
|
||||
logout,
|
||||
}
|
||||
|
||||
return (
|
||||
<PermissionContext.Provider value={value}>
|
||||
{children}
|
||||
</PermissionContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔧 开发说明:权限系统演进计划
|
||||
*
|
||||
* 【当前阶段 - Week 2】
|
||||
* - ✅ 用户信息:硬编码为 premium
|
||||
* - ✅ 权限检查:基于 UserVersion 等级
|
||||
* - ✅ 功能完整:支持模块级权限控制
|
||||
*
|
||||
* 【Week 2 Day 8-9 - 对接后端】
|
||||
* - [ ] 从后端获取真实用户信息
|
||||
* - [ ] 解析 JWT token 获取用户权限
|
||||
* - [ ] 实现登录/登出功能
|
||||
* - [ ] 集成用户管理API
|
||||
*
|
||||
* 【Week 3-4 - ASL开发】
|
||||
* - [ ] 在ASL模块中应用权限控制
|
||||
* - [ ] 实现功能级权限(如:LLM模型选择权限)
|
||||
* - [ ] 添加试用期限制逻辑
|
||||
*
|
||||
* 【Week 5+ - 完善】
|
||||
* - [ ] 动态权限配置
|
||||
* - [ ] 权限缓存优化
|
||||
* - [ ] 权限变更实时通知
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
15
frontend-v2/src/framework/permission/index.ts
Normal file
15
frontend-v2/src/framework/permission/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 权限系统模块导出
|
||||
*
|
||||
* @description 统一导出权限相关的组件、Hook和类型
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*/
|
||||
|
||||
export { PermissionProvider, PermissionContext } from './PermissionContext'
|
||||
export { usePermission } from './usePermission'
|
||||
export type { UserInfo, UserVersion, PermissionContextType } from './types'
|
||||
export { VERSION_LEVEL, checkVersionLevel } from './types'
|
||||
|
||||
|
||||
|
||||
|
||||
87
frontend-v2/src/framework/permission/types.ts
Normal file
87
frontend-v2/src/framework/permission/types.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 权限系统类型定义
|
||||
*
|
||||
* @description 定义用户权限、角色等类型
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*/
|
||||
|
||||
/**
|
||||
* 用户版本类型(权限等级)
|
||||
* - basic: 基础版(免费试用)
|
||||
* - advanced: 高级版(付费用户)
|
||||
* - premium: 旗舰版(完整功能)
|
||||
*/
|
||||
export type UserVersion = 'basic' | 'advanced' | 'premium'
|
||||
|
||||
/**
|
||||
* 用户信息接口
|
||||
*/
|
||||
export interface UserInfo {
|
||||
/** 用户ID */
|
||||
id: string
|
||||
|
||||
/** 用户名称 */
|
||||
name: string
|
||||
|
||||
/** 用户邮箱 */
|
||||
email: string
|
||||
|
||||
/** 用户版本(权限等级) */
|
||||
version: UserVersion
|
||||
|
||||
/** 头像URL */
|
||||
avatar?: string | null
|
||||
|
||||
/** 是否试用中 */
|
||||
isTrial?: boolean
|
||||
|
||||
/** 试用到期时间 */
|
||||
trialEndsAt?: Date | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限上下文接口
|
||||
*/
|
||||
export interface PermissionContextType {
|
||||
/** 当前用户信息 */
|
||||
user: UserInfo | null
|
||||
|
||||
/** 是否已登录 */
|
||||
isAuthenticated: boolean
|
||||
|
||||
/** 检查模块权限 */
|
||||
checkModulePermission: (requiredVersion?: UserVersion) => boolean
|
||||
|
||||
/** 检查功能权限 */
|
||||
checkFeaturePermission: (feature: string) => boolean
|
||||
|
||||
/** 设置用户信息(登录时) */
|
||||
setUser: (user: UserInfo | null) => void
|
||||
|
||||
/** 退出登录 */
|
||||
logout: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限等级映射(用于比较)
|
||||
*/
|
||||
export const VERSION_LEVEL: Record<UserVersion, number> = {
|
||||
basic: 1,
|
||||
advanced: 2,
|
||||
premium: 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查权限等级是否满足要求
|
||||
*/
|
||||
export const checkVersionLevel = (
|
||||
userVersion: UserVersion,
|
||||
requiredVersion?: UserVersion
|
||||
): boolean => {
|
||||
if (!requiredVersion) return true
|
||||
return VERSION_LEVEL[userVersion] >= VERSION_LEVEL[requiredVersion]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
44
frontend-v2/src/framework/permission/usePermission.ts
Normal file
44
frontend-v2/src/framework/permission/usePermission.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useContext } from 'react'
|
||||
import { PermissionContext } from './PermissionContext'
|
||||
import { PermissionContextType } from './types'
|
||||
|
||||
/**
|
||||
* 权限Hook
|
||||
*
|
||||
* @description 提供便捷的权限检查功能
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const MyComponent = () => {
|
||||
* const { user, checkModulePermission } = usePermission()
|
||||
*
|
||||
* if (!checkModulePermission('advanced')) {
|
||||
* return <UpgradePrompt />
|
||||
* }
|
||||
*
|
||||
* return <div>欢迎 {user?.name}</div>
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const usePermission = (): PermissionContextType => {
|
||||
const context = useContext(PermissionContext)
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
'usePermission must be used within a PermissionProvider. ' +
|
||||
'Please wrap your app with <PermissionProvider>.'
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出权限相关类型(方便使用)
|
||||
*/
|
||||
export type { UserInfo, UserVersion, PermissionContextType } from './types'
|
||||
|
||||
|
||||
|
||||
|
||||
154
frontend-v2/src/framework/router/PermissionDenied.tsx
Normal file
154
frontend-v2/src/framework/router/PermissionDenied.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import { Result, Button } from 'antd'
|
||||
import { LockOutlined, HomeOutlined, RocketOutlined } from '@ant-design/icons'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
/**
|
||||
* 无权限访问提示页面
|
||||
*
|
||||
* @description
|
||||
* 当用户尝试访问无权限的模块时显示
|
||||
* 引导用户升级版本或返回首页
|
||||
*
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*/
|
||||
|
||||
interface PermissionDeniedProps {
|
||||
/** 模块名称 */
|
||||
moduleName?: string
|
||||
/** 所需权限等级 */
|
||||
requiredVersion?: string
|
||||
/** 用户当前权限等级 */
|
||||
currentVersion?: string
|
||||
}
|
||||
|
||||
const PermissionDenied = ({
|
||||
moduleName = '该功能',
|
||||
requiredVersion = 'advanced',
|
||||
currentVersion = 'basic',
|
||||
}: PermissionDeniedProps) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
/**
|
||||
* 返回首页
|
||||
*/
|
||||
const handleGoHome = () => {
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
/**
|
||||
* 去升级(后续实现)
|
||||
*/
|
||||
const handleUpgrade = () => {
|
||||
// TODO: 跳转到升级页面或打开升级对话框
|
||||
console.log('用户点击升级按钮')
|
||||
// 暂时跳转到首页
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
// 权限等级名称映射
|
||||
const versionName: Record<string, string> = {
|
||||
basic: '基础版',
|
||||
advanced: '高级版',
|
||||
premium: '旗舰版',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center bg-gray-50">
|
||||
<Result
|
||||
icon={<LockOutlined style={{ fontSize: 72, color: '#faad14' }} />}
|
||||
title={<span className="text-2xl">访问受限</span>}
|
||||
subTitle={
|
||||
<div className="space-y-3 mt-4">
|
||||
<p className="text-gray-600">
|
||||
抱歉,您当前是<strong>{versionName[currentVersion]}</strong>用户,
|
||||
无法访问<strong>{moduleName}</strong>。
|
||||
</p>
|
||||
<p className="text-gray-500">
|
||||
该功能需要<strong className="text-blue-600">{versionName[requiredVersion]}</strong>及以上权限。
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
extra={
|
||||
<div className="flex gap-3 justify-center mt-6">
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<RocketOutlined />}
|
||||
onClick={handleUpgrade}
|
||||
size="large"
|
||||
>
|
||||
升级到{versionName[requiredVersion]}
|
||||
</Button>
|
||||
<Button
|
||||
icon={<HomeOutlined />}
|
||||
onClick={handleGoHome}
|
||||
size="large"
|
||||
>
|
||||
返回首页
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{/* 版本对比 */}
|
||||
<div className="mt-8 max-w-md mx-auto">
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<RocketOutlined className="text-blue-600" />
|
||||
<span className="font-semibold text-blue-900">
|
||||
升级{versionName[requiredVersion]}可解锁:
|
||||
</span>
|
||||
</div>
|
||||
<ul className="space-y-2 text-sm text-gray-700 list-disc list-inside">
|
||||
{requiredVersion === 'advanced' && (
|
||||
<>
|
||||
<li>AI智能文献筛选(4个LLM模型)</li>
|
||||
<li>更多存储空间</li>
|
||||
<li>优先技术支持</li>
|
||||
</>
|
||||
)}
|
||||
{requiredVersion === 'premium' && (
|
||||
<>
|
||||
<li>智能统计分析(外部系统集成)</li>
|
||||
<li>统计分析工具集</li>
|
||||
<li>无限存储空间</li>
|
||||
<li>专属客户经理</li>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Result>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PermissionDenied
|
||||
|
||||
/**
|
||||
* 🎨 设计说明:
|
||||
*
|
||||
* 1. 用户体验:
|
||||
* - ✅ 明确告知用户为什么无法访问
|
||||
* - ✅ 显示当前版本和所需版本
|
||||
* - ✅ 提供明确的升级路径
|
||||
* - ✅ 展示升级后的价值(功能列表)
|
||||
*
|
||||
* 2. 商业转化:
|
||||
* - ✅ 突出显示"升级"按钮(主按钮)
|
||||
* - ✅ 列举升级后可获得的功能
|
||||
* - ✅ 引导用户做出升级决策
|
||||
*
|
||||
* 3. 后续优化:
|
||||
* - [ ] 接入真实的升级流程(支付系统)
|
||||
* - [ ] 显示价格对比
|
||||
* - [ ] 添加"免费试用"选项
|
||||
* - [ ] 记录转化数据(用户点击升级的次数)
|
||||
*
|
||||
* 4. 权限策略:
|
||||
* 【当前阶段】所有用户都是premium,不会看到此页面
|
||||
* 【Week 3+】ASL模块需要advanced权限,可测试此页面
|
||||
* 【Week 5+】完整的权限和付费体系
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
143
frontend-v2/src/framework/router/RouteGuard.tsx
Normal file
143
frontend-v2/src/framework/router/RouteGuard.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Navigate } from 'react-router-dom'
|
||||
import { usePermission } from '../permission'
|
||||
import PermissionDenied from './PermissionDenied'
|
||||
import type { UserVersion } from '../permission'
|
||||
|
||||
/**
|
||||
* 路由守卫组件
|
||||
*
|
||||
* @description
|
||||
* 保护需要特定权限的路由,防止用户通过URL直接访问无权限页面
|
||||
* 这是权限控制的"第二道防线"(第一道是TopNavigation的过滤)
|
||||
*
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Route
|
||||
* path="/literature/*"
|
||||
* element={
|
||||
* <RouteGuard requiredVersion="advanced" moduleName="AI智能文献">
|
||||
* <ASLModule />
|
||||
* </RouteGuard>
|
||||
* }
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* 工作原理:
|
||||
* 1. 用户访问 /literature 路由
|
||||
* 2. RouteGuard 检查用户权限
|
||||
* 3. 如果有权限 → 渲染子组件
|
||||
* 4. 如果无权限 → 显示 PermissionDenied 页面
|
||||
* 5. 如果未登录 → 重定向到登录页(后续实现)
|
||||
*/
|
||||
|
||||
interface RouteGuardProps {
|
||||
/** 子组件 */
|
||||
children: ReactNode
|
||||
/** 所需权限等级 */
|
||||
requiredVersion?: UserVersion
|
||||
/** 模块名称(用于显示友好提示) */
|
||||
moduleName?: string
|
||||
/** 是否重定向到首页(默认显示无权限页面) */
|
||||
redirectToHome?: boolean
|
||||
}
|
||||
|
||||
const RouteGuard = ({
|
||||
children,
|
||||
requiredVersion,
|
||||
moduleName,
|
||||
redirectToHome = false,
|
||||
}: RouteGuardProps) => {
|
||||
const { user, isAuthenticated, checkModulePermission } = usePermission()
|
||||
|
||||
// 1. 检查是否登录(后续实现真实认证)
|
||||
if (!isAuthenticated) {
|
||||
// TODO: 后续实现真实的登录流程
|
||||
// 当前阶段:用户默认已登录(MOCK_USER)
|
||||
console.warn('用户未登录,应该重定向到登录页')
|
||||
// return <Navigate to="/login" replace />
|
||||
}
|
||||
|
||||
// 2. 检查权限等级
|
||||
const hasPermission = checkModulePermission(requiredVersion)
|
||||
|
||||
if (!hasPermission) {
|
||||
// 记录无权限访问尝试(用于后续分析和转化优化)
|
||||
console.log('🔒 权限不足:', {
|
||||
module: moduleName,
|
||||
requiredVersion,
|
||||
currentVersion: user?.version,
|
||||
userId: user?.id,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
|
||||
// 如果配置了重定向,直接返回首页
|
||||
if (redirectToHome) {
|
||||
return <Navigate to="/" replace />
|
||||
}
|
||||
|
||||
// 显示无权限页面(推荐,引导用户升级)
|
||||
return (
|
||||
<PermissionDenied
|
||||
moduleName={moduleName}
|
||||
requiredVersion={requiredVersion}
|
||||
currentVersion={user?.version}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
13
frontend-v2/src/framework/router/index.ts
Normal file
13
frontend-v2/src/framework/router/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 路由系统模块导出
|
||||
*
|
||||
* @description 统一导出路由守卫、权限拒绝页面等组件
|
||||
* @version Week 2 Day 7 - 任务17
|
||||
*/
|
||||
|
||||
export { default as RouteGuard } from './RouteGuard'
|
||||
export { default as PermissionDenied } from './PermissionDenied'
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user