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:
2025-11-16 15:43:17 +08:00
parent 5579ffa78e
commit 11325f88a7
39 changed files with 8051 additions and 0 deletions

View 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. 生产环境优化:
* - 隐藏详细错误堆栈(避免泄露代码信息)
* - 提供错误报告功能(用户可以反馈问题)
* - 记录完整错误上下文(用户操作、路由、设备信息)
*/

View 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. 后续优化:
* - [ ] 添加"反馈问题"按钮
* - [ ] 集成错误报告系统
* - [ ] 记录用户操作路径
* - [ ] 自动重试机制(网络错误)
*/

View 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
})
}

View 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
}