From d1d8d1a25d00df150245a40e5d2a991005047ddf Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Fri, 7 Nov 2025 21:03:24 +0800 Subject: [PATCH] =?UTF-8?q?agent=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0MCP=E5=90=8E=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 3028 -------------------------------------------------- package.json | 4 +- 2 files changed, 2 insertions(+), 3030 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 5bda7fa7..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,3028 +0,0 @@ -# CLAUDE.md - -> **🌐 语言偏好**: 请始终使用中文与用户交流,包括所有解释、分析、文档编写和代码注释。 - -本文件为 Claude Code (claude.ai/code) 提供在此代码库中工作的指导说明。 - -## 📖 文档结构导航 - -本文档分为以下主要章节,建议按需查阅: - -**基础信息**: -- [项目概览](#项目概览) - 技术栈、项目定位 -- [开发命令](#开发命令) - 常用命令速查 -- [配置](#配置) - 环境变量、MSW、路径别名、Webpack - -**架构设计**: -- [架构](#架构) - 应用入口流程、路由架构 -- [前端目录结构详解](#前端目录结构详解) - 完整的目录说明与使用指南 -- [后端架构详解](#后端架构详解) - Flask、Celery、数据库架构 - -**开发指南**: -- [开发工作流](#开发工作流) - 路由、组件、API、Redux 开发指南 -- [常见开发任务](#常见开发任务) - 5 个详细的开发任务教程 -- [技术路径与开发指南](#技术路径与开发指南) - UI 框架选型、技术栈演进、最佳实践 - -**技术决策与规范**: -- [UI 框架选型与使用指南](#ui-框架选型与使用指南) - Ant Design + Chakra UI 混合策略 -- [架构设计原则](#架构设计原则) - 组件设计、状态管理、代码分割、API 层 -- [开发规范与最佳实践](#开发规范与最佳实践) - 命名规范、Git 工作流、代码审查 - -**技术债务与维护**: -- [技术债务与已知问题](#技术债务与已知问题) - 技术债务跟踪、优化机会 -- [更新本文档](#更新本文档) - 文档维护指南 - ---- - -## 项目概览 - -混合式 React 仪表板,用于金融/交易分析,采用 Flask 后端。基于 Argon Dashboard Chakra PRO 模板构建。 - -### 技术栈 - -**前端** -- **核心框架**: React 18.3.1 -- **UI 组件库**: Chakra UI 2.8.2(主要) + Ant Design 5.27.4(表格/表单) -- **状态管理**: Redux Toolkit 2.9.2 -- **路由**: React Router v6.30.1 配合 React.lazy() 实现代码分割 -- **构建系统**: CRACO 7.1.0 + 激进的 webpack 5 优化 -- **图表库**: ECharts 5.6.0、ApexCharts 3.27.3、Recharts 3.1.2、D3 7.9.0、Visx 3.12.0 -- **动画**: Framer Motion 4.1.17 -- **日历**: FullCalendar 5.9.0、React Big Calendar 0.33.2 -- **图标**: Lucide React 0.540.0(推荐)、React Icons 4.12.0、@ant-design/icons 6.0.0 -- **网络请求**: Axios 1.10.0 -- **实时通信**: Socket.IO Client 4.7.4 -- **数据分析**: PostHog 1.281.0 -- **开发工具**: MSW (Mock Service Worker) 用于 API mocking -- **虚拟化**: @tanstack/react-virtual 3.13.12(性能优化) -- **其他**: Draft.js(富文本编辑)、React Markdown、React Quill - -**后端** -- Flask + SQLAlchemy ORM -- ClickHouse(分析型数据库)+ MySQL/PostgreSQL(事务型数据库) -- Flask-SocketIO 实现 WebSocket 实时更新 -- Celery + Redis 处理后台任务 -- 腾讯云短信 + 微信支付集成 - -## 开发命令 - -### 前端开发 -```bash -npm start # 使用 mock 数据启动(.env.mock),代理到 localhost:5001 -npm run start:real # 使用真实后端启动(.env.local) -npm run start:dev # 使用开发配置启动(.env.development) -npm run start:test # 同时启动后端(app.py)和前端(.env.test) -npm run dev # 'npm start' 的别名 -npm run backend # 仅启动 Flask 服务器(python app.py) - -npm run build # 生产环境构建,包含 Gulp 许可证头 -npm run build:analyze # 使用 webpack bundle analyzer 构建 -npm test # 运行 React 测试套件(CRACO) - -npm run lint:check # 检查 ESLint 规则(退出码 0) -npm run lint:fix # 自动修复 ESLint 问题 -npm run clean # 删除 node_modules 和 package-lock.json -npm run reinstall # 清洁安装(运行 clean + install) -``` - -### 后端开发 -```bash -python app.py # 主 Flask 服务器 -python simulation_background_processor.py # 交易模拟的后台任务处理器 -pip install -r requirements.txt # 安装 Python 依赖 -``` - -### 部署 -```bash -npm run deploy # 从本地部署(scripts/deploy-from-local.sh) -npm run rollback # 回滚到上一个版本 -``` - -## 架构 - -### 应用入口流程 -``` -src/index.js -└── src/App.js(根组件) - ├── AppProviders (src/providers/AppProviders.js) - │ ├── ReduxProvider(store 来自 src/store/) - │ ├── ChakraProvider(主题来自 src/theme/) - │ ├── NotificationProvider (src/contexts/NotificationContext.js) - │ └── AuthProvider (src/contexts/AuthContext.js) - ├── AppRoutes (src/routes/index.js) - │ ├── MainLayout 路由(带导航栏/页脚) - │ └── 独立路由(认证页面、全屏视图) - └── GlobalComponents(模态框覆盖层、全局 UI) -``` - -### 路由架构(模块化设计) -路由采用**声明式**设计,并拆分到 `src/routes/` 中的多个文件: - -- **index.js** - 主路由器(组合配置 + 渲染路由) -- **routeConfig.js** - 路由定义(路径、组件、保护模式、布局、子路由) -- **lazy-components.js** - React.lazy() 导入,用于代码分割 -- **homeRoutes.js** - 嵌套的首页路由 -- **constants/** - 保护模式、布局映射 -- **utils/** - 路由渲染逻辑(wrapWithProtection、renderRoute) - -路由保护模式(PROTECTION_MODES): -- `PUBLIC` - 无需认证 -- `MODAL` - 未登录时显示认证模态框 -- `REDIRECT` - 未登录时重定向到 /auth/sign-in - -### 前端目录结构详解 - -本项目采用**功能驱动的目录结构**,按职责将代码组织到不同目录中。以下是完整的目录结构及详细说明: - -#### 核心目录概览 - -``` -src/ -├── index.js # 应用入口文件 -├── App.js # 根组件(组合 Providers + Routes) -│ -├── providers/ # Provider 组合层 -├── routes/ # 路由配置与管理 -├── layouts/ # 页面布局模板 -├── views/ # 页面级组件 -├── components/ # 可复用 UI 组件 -│ -├── contexts/ # React Context 状态管理 -├── store/ # Redux 全局状态管理 -├── hooks/ # 自定义 React Hooks -│ -├── services/ # API 服务层 -├── utils/ # 工具函数库 -├── constants/ # 全局常量定义 -│ -├── theme/ # UI 主题配置 -├── assets/ # 静态资源 -├── styles/ # 全局样式 -│ -├── mocks/ # 开发环境 Mock 数据 -├── lib/ # 第三方库配置 -└── variables/ # 可配置变量 -``` - ---- - -#### 详细目录说明 - -##### 📁 `src/providers/` - Provider 组合层 - -**用途**: 集中管理应用的所有 Context Providers,避免在 App.js 中嵌套过深。 - -**核心文件**: -- `AppProviders.js` - 所有 Provider 的组合容器 - -**Provider 嵌套顺序**(从外到内): -```javascript -ReduxProvider // 1. Redux 状态管理(最外层) - └─ ChakraProvider // 2. Chakra UI 主题系统 - └─ ConfigProvider // 3. Ant Design 主题配置 - └─ NotificationProvider // 4. 通知系统 - └─ AuthProvider // 5. 认证系统(最内层) -``` - -**何时修改**: -- 添加新的全局 Provider(如 i18n、Analytics) -- 调整 Provider 顺序(注意依赖关系) - ---- - -##### 📁 `src/routes/` - 路由系统 - -**用途**: 模块化路由配置,采用声明式设计,支持懒加载和路由保护。 - -**核心文件**: -- `index.js` - 主路由器(渲染所有路由) -- `routeConfig.js` - **路由配置中心**(所有路由定义) -- `lazy-components.js` - React.lazy() 懒加载组件导入 -- `homeRoutes.js` - 首页嵌套路由 -- `constants/protectionModes.js` - 路由保护模式定义 -- `utils/wrapWithProtection.js` - 路由保护逻辑 -- `utils/renderRoute.js` - 路由渲染工具 - -**路由保护模式**: -- `PUBLIC` - 公开访问,无需登录 -- `MODAL` - 未登录时显示登录模态框(不跳转) -- `REDIRECT` - 未登录时重定向到 /auth/sign-in - -**添加新路由的步骤**: 参见"常见开发任务 → 如何添加新的页面路由" - -**命名约定**: -- 路由路径使用 kebab-case: `portfolio`, `trading-simulation` -- 组件名使用 PascalCase: `Portfolio`, `TradingSimulation` - ---- - -##### 📁 `src/layouts/` - 页面布局 - -**用途**: 定义页面的通用布局结构(导航栏、侧边栏、页脚等),复用于多个页面。 - -**核心文件**: -- `MainLayout.js` - 主布局(带顶部导航栏 + 侧边栏 + 页脚) -- `Auth.js` - 认证页面布局(无导航栏,纯净背景) - -**布局特性**: -- 每个布局包含独立的 ErrorBoundary(错误隔离) -- 通过 `` 渲染子路由内容 -- 布局内可访问认证状态(AuthContext) - -**何时创建新布局**: -- 需要完全不同的页面结构(如打印页、全屏图表) -- 特定页面需要自定义导航栏/侧边栏 - ---- - -##### 📁 `src/views/` - 页面级组件 - -**用途**: 存放路由对应的页面组件,每个文件对应一个路由页面。 - -**组织结构**: -``` -views/ -├── Community/ # 社区页面(大型功能模块) -│ ├── index.js # 页面入口 -│ ├── components/ # 页面专属组件 -│ │ ├── EventList/ -│ │ ├── EventCard/ -│ │ └── StockDetailPanel/ -│ ├── hooks/ # 页面专属 Hooks -│ │ └── useCommunityData.js -│ └── utils/ # 页面专属工具函数 -│ -├── TradingSimulation/ # 交易模拟页面 -│ ├── index.js -│ ├── components/ -│ └── ... -│ -├── Dashboard/ # 仪表板页面 -│ └── index.js -│ -└── Portfolio/ # 投资组合页面(简单页面) - └── index.js -``` - -**命名约定**: -- 目录名使用 PascalCase: `Community`, `TradingSimulation` -- 主文件始终为 `index.js`(方便导入) - -**原则**: -- 页面组件主要负责**数据获取和布局组合**,不应包含复杂的 UI 逻辑 -- 复杂 UI 逻辑应拆分到 `components/` 子目录 -- 页面超过 500 行时考虑拆分(参见"组件组织模式") - ---- - -##### 📁 `src/components/` - 可复用 UI 组件 - -**用途**: 存放跨页面复用的通用 UI 组件(按钮、卡片、表格、模态框等)。 - -**组织结构**(推荐采用原子设计模式): -``` -components/ -├── Atoms/ # 原子组件(1-50 行) -│ ├── Button/ -│ ├── Badge/ -│ └── Icon/ -│ -├── Molecules/ # 分子组件(50-150 行) -│ ├── Card/ -│ ├── StatCard/ -│ └── SearchBar/ -│ -├── Organisms/ # 有机体组件(150-500 行) -│ ├── Navbar/ -│ ├── Sidebar/ -│ ├── DataTable/ -│ └── FilterPanel/ -│ -└── Templates/ # 模板组件(可选) - └── PageTemplate/ -``` - -**何时添加新组件**: -- 组件在 2+ 个页面中使用 -- 组件具有明确的职责和边界 -- 组件可独立测试 - -**命名约定**: -- 组件目录使用 PascalCase: `EventCard`, `StockTable` -- 每个组件目录包含: - - `index.js` - 主导出文件 - - `ComponentName.js` - 实现代码(可选,简单组件直接在 index.js) - - `ComponentName.test.js` - 测试文件(可选) - -**避免**: -- 将页面特定组件放在这里(应放在 `views/{PageName}/components/`) -- 过度拆分(不要为了拆分而拆分) - ---- - -##### 📁 `src/contexts/` - React Context 状态管理 - -**用途**: 存放使用 React Context API 管理的跨组件状态。 - -**核心 Contexts**: -- `AuthContext.js` - 认证状态(用户信息、登录状态、登录/登出方法) -- `NotificationContext.js` - 通知系统(显示 Toast/Alert) -- `SidebarContext.js` - 侧边栏状态(展开/收起) - -**何时使用 Context**: -- 跨层级传递数据(避免 props drilling) -- 不频繁变化的数据(主题、语言、认证状态) -- 依赖注入场景 - -**何时不使用 Context**: -- 频繁变化的数据 → 使用 Redux -- 服务端数据 → 使用 React Query(计划中) -- 本地 UI 状态 → 使用 useState - -**Context 文件结构**: -```javascript -// AuthContext.js -export const AuthContext = createContext(); - -export const AuthProvider = ({ children }) => { - const [user, setUser] = useState(null); - // ... - return ( - - {children} - - ); -}; - -export const useAuth = () => useContext(AuthContext); // 自定义 Hook -``` - ---- - -##### 📁 `src/store/` - Redux 全局状态管理 - -**用途**: 使用 Redux Toolkit 管理全局状态(UI 状态、跨页面共享数据)。 - -**目录结构**: -``` -store/ -├── index.js # Store 配置(combineReducers) -└── slices/ # Redux Slices - ├── authModalSlice.js # 认证模态框状态 - ├── posthogSlice.js # PostHog 分析配置 - ├── stockSlice.js # 股票数据 - ├── industrySlice.js # 行业/概念数据 - ├── subscriptionSlice.js # 用户订阅状态 - └── communityDataSlice.js # 社区数据 -``` - -**何时使用 Redux**: -- 全局 UI 状态(模态框开关、侧边栏状态) -- 需要在多个不相关组件间共享的数据 -- 需要持久化的状态(与 redux-persist 结合) -- 需要中间件处理的复杂逻辑 - -**Slice 命名约定**: -- 文件名: `{feature}Slice.js` (如 `authModalSlice.js`) -- Slice 名称: `{feature}` (如 `name: 'authModal'`) -- Actions: 动词开头 (如 `openModal`, `closeModal`, `setUser`) - -**添加新 Slice**: 参见"常见开发任务 → 如何添加新的 Redux Slice" - ---- - -##### 📁 `src/hooks/` - 自定义 React Hooks - -**用途**: 存放可复用的自定义 Hooks,封装常见逻辑模式。 - -**Hook 类型示例**: -``` -hooks/ -├── useAuth.js # 认证相关(实际导出自 AuthContext) -├── useStockData.js # 数据获取 Hook -├── useDebounce.js # 防抖 Hook -├── useIntersectionObserver.js # 懒加载/无限滚动 -├── useLocalStorage.js # 本地存储持久化 -├── useMediaQuery.js # 响应式查询 -└── usePrevious.js # 获取前一个值 -``` - -**命名约定**: -- 文件名: `use{PascalCase}.js` (如 `useStockData.js`) -- 必须以 `use` 开头(React Hooks 规则) -- 一个文件导出一个 Hook - -**Hook 设计原则**: -- 职责单一(只做一件事) -- 返回对象或数组(不返回原始值) -- 包含完整的状态管理(loading, error, data) - -**何时创建新 Hook**: -- 逻辑在 2+ 个组件中重复 -- 组件逻辑可以独立测试 -- 需要封装浏览器 API(localStorage, IntersectionObserver) - ---- - -##### 📁 `src/services/` - API 服务层 - -**用途**: 封装所有后端 API 调用,提供统一的接口供组件调用。 - -**组织结构**(按业务领域划分): -``` -services/ -├── authService.js # 认证相关 API(登录、注册、登出) -├── stockService.js # 股票数据 API -├── portfolioService.js # 投资组合 API -├── communityService.js # 社区内容 API -├── tradingService.js # 交易模拟 API -└── userService.js # 用户信息 API -``` - -**服务函数命名约定**: -- `fetch{Entity}` - 获取数据 (如 `fetchStockData`) -- `create{Entity}` - 创建记录 (如 `createOrder`) -- `update{Entity}` - 更新记录 (如 `updateProfile`) -- `delete{Entity}` - 删除记录 (如 `deletePost`) - -**标准 API 调用结构**: -```javascript -// stockService.js -import axios from 'axios'; -import { getApiBase } from '@utils/apiConfig'; - -const api = axios.create({ - baseURL: getApiBase(), - timeout: 10000, -}); - -export const fetchStockData = async (stockCode) => { - try { - const response = await api.get(`/api/stocks/${stockCode}`); - return response.data; - } catch (error) { - console.error('fetchStockData error:', error); - throw error; - } -}; -``` - -**原则**: -- 组件不应直接调用 axios(通过 service 层) -- 每个 service 函数应包含错误处理 -- 使用 `getApiBase()` 获取 API 基础 URL(支持 Mock 模式) - ---- - -##### 📁 `src/utils/` - 工具函数库 - -**用途**: 存放纯函数工具、格式化工具、计算逻辑等不依赖 React 的通用代码。 - -**核心工具文件**: -``` -utils/ -├── apiConfig.js # API 配置(getApiBase, isMockMode) -├── priceFormatters.js # 价格/数字格式化函数 -├── dateFormatters.js # 日期格式化函数 -├── logger.js # 统一日志工具 -├── validators.js # 表单验证函数 -├── calculations.js # 金融计算函数 -└── string.js # 字符串处理函数 -``` - -**命名约定**: -- 文件名: `camelCase.js` (如 `priceFormatters.js`) -- 函数名: `camelCase` (如 `formatPrice`, `calculateProfit`) -- 导出方式: 命名导出 (如 `export const formatPrice = ...`) - -**工具函数特点**: -- **纯函数**: 相同输入始终返回相同输出,无副作用 -- **可测试**: 易于编写单元测试 -- **可复用**: 多处使用的逻辑 - -**示例**: -```javascript -// priceFormatters.js -export const formatPrice = (price, currency = '¥') => { - return `${currency}${price.toFixed(2)}`; -}; - -export const formatPercent = (value, decimals = 2) => { - return `${(value * 100).toFixed(decimals)}%`; -}; -``` - ---- - -##### 📁 `src/constants/` - 全局常量定义 - -**用途**: 存放全局常量、枚举值、配置对象等不可变数据。 - -**常见常量文件**: -``` -constants/ -├── animations.js # 动画配置(duration、easing) -├── routes.js # 路由路径常量 -├── apiEndpoints.js # API 端点常量 -├── colors.js # 颜色常量(补充主题) -├── tradingConfig.js # 交易相关配置 -└── errorMessages.js # 错误消息常量 -``` - -**命名约定**: -- 常量名使用 SCREAMING_SNAKE_CASE: `API_BASE_URL`, `MAX_RETRY_COUNT` -- 配置对象使用 camelCase: `animationConfig`, `chartColors` - -**示例**: -```javascript -// animations.js -export const ANIMATION_DURATION = { - FAST: 150, - NORMAL: 300, - SLOW: 500, -}; - -export const EASING = { - EASE_IN_OUT: 'cubic-bezier(0.4, 0, 0.2, 1)', - EASE_OUT: 'cubic-bezier(0, 0, 0.2, 1)', -}; -``` - -**原则**: -- 避免魔法数字/字符串(在代码中硬编码) -- 方便统一修改(单一数据源) -- 提高代码可读性 - ---- - -##### 📁 `src/theme/` - UI 主题配置 - -**用途**: 定义 Chakra UI 主题配置(颜色、字体、间距、组件样式等)。 - -**核心文件**: -``` -theme/ -├── theme.js # 主题主文件(导出完整主题) -├── foundations/ # 基础样式(颜色、字体、间距) -│ ├── colors.js -│ ├── typography.js -│ └── spacing.js -└── components/ # 组件级主题覆盖 - ├── button.js - ├── card.js - └── modal.js -``` - -**主题配置示例**: -```javascript -// theme.js -import { extendTheme } from '@chakra-ui/react'; -import { colors } from './foundations/colors'; -import { ButtonStyles as Button } from './components/button'; - -const theme = extendTheme({ - colors, - fonts: { - heading: `'Open Sans', sans-serif`, - body: `'Raleway', sans-serif`, - }, - components: { - Button, - }, -}); - -export default theme; -``` - -**何时修改主题**: -- 添加自定义颜色/渐变 -- 统一修改所有 Button/Card 的默认样式 -- 配置深色模式 - ---- - -##### 📁 `src/assets/` - 静态资源 - -**用途**: 存放图片、字体、图标等静态文件。 - -**组织结构**: -``` -assets/ -├── img/ # 图片资源 -│ ├── logo.png -│ ├── backgrounds/ -│ └── illustrations/ -├── icons/ # SVG 图标 -└── fonts/ # 自定义字体文件(如需要) -``` - -**使用方式**: -```javascript -import logo from '@assets/img/logo.png'; - -Logo -``` - ---- - -##### 📁 `src/mocks/` - 开发环境 Mock 数据 - -**用途**: 使用 MSW (Mock Service Worker) 拦截 API 请求,返回 mock 数据,实现前后端分离开发。 - -**目录结构**: -``` -mocks/ -├── browser.js # MSW 初始化(浏览器环境) -├── handlers/ # API Mock Handlers(按领域划分) -│ ├── index.js # 汇总所有 handlers -│ ├── auth.js # 认证相关 API mock -│ ├── stock.js # 股票数据 API mock -│ ├── portfolio.js # 投资组合 API mock -│ └── community.js # 社区内容 API mock -└── data/ # Mock 数据文件 - ├── stocks.json - ├── users.json - └── events.json -``` - -**启用 Mock 模式**: -```bash -# 方式 1: 使用 .env.mock 配置(默认) -npm start - -# 方式 2: 手动设置环境变量 -REACT_APP_ENABLE_MOCK=true npm start -``` - -**添加新 Mock Handler**: 参见"常见开发任务 → 如何添加新的 MSW Mock Handler" - -**原则**: -- Mock 数据应尽量模拟真实数据结构 -- 使用 MSW 的 `http.get/post/put/delete` 定义 handlers -- Handler 中可模拟延迟、错误响应(测试加载/错误状态) - ---- - -##### 📁 其他目录 - -**`src/lib/`** - 第三方库配置 -- 存放第三方库的自定义配置(如 PostHog、Analytics) - -**`src/styles/`** - 全局样式 -- 存放全局 CSS/SCSS 文件(如 reset.css、global.scss) - -**`src/variables/`** - 可配置变量 -- 存放可通过环境变量覆盖的配置(如 API URLs、Feature Flags) - ---- - -#### 目录结构最佳实践 - -**1. 就近原则(Co-location)** -- 将相关文件放在一起(组件、样式、测试、工具函数) -- 示例: `EventCard/` 目录包含 `EventCard.js`、`EventCard.test.js`、`utils.js` - -**2. 单一职责** -- 每个目录只负责一个领域(不要混合业务逻辑和 UI 组件) -- 示例: `services/` 只包含 API 调用,不包含 UI 组件 - -**3. 避免深层嵌套** -- 目录层级不超过 4 层(超过则考虑重构) -- 示例: `src/views/Community/components/EventCard/` 已经是 4 层 - -**4. 命名一致性** -- 组件: PascalCase (`EventCard`) -- 文件: camelCase (`priceFormatters.js`) -- 常量: SCREAMING_SNAKE_CASE (`API_BASE_URL`) - -**5. 导出规范** -- 每个目录包含 `index.js` 作为主导出文件 -- 使用命名导出 (`export const ...`) 而非默认导出(除组件外) - ---- - -#### 查找文件指南 - -**如何快速找到想要修改的文件?** - -| 需求 | 目录 | 示例 | -|------|------|------| -| 修改页面布局 | `src/layouts/` | `MainLayout.js` | -| 修改某个页面 | `src/views/{PageName}/` | `Community/index.js` | -| 修改可复用组件 | `src/components/` | `DataTable/index.js` | -| 修改 API 调用 | `src/services/` | `stockService.js` | -| 修改工具函数 | `src/utils/` | `priceFormatters.js` | -| 修改全局状态 | `src/store/slices/` | `stockSlice.js` | -| 添加新路由 | `src/routes/routeConfig.js` | - | -| 修改主题颜色 | `src/theme/foundations/colors.js` | - | -| 添加 Mock API | `src/mocks/handlers/` | `stock.js` | - -**使用路径别名快速导入**: -```javascript -// ✅ 使用别名(推荐) -import { EventCard } from '@components/EventCard'; -import { formatPrice } from '@utils/priceFormatters'; -import { fetchStockData } from '@services/stockService'; - -// ❌ 使用相对路径(不推荐) -import { EventCard } from '../../../components/EventCard'; -import { formatPrice } from '../../utils/priceFormatters'; -``` - -### 后端架构详解 - -本项目后端采用 **Flask 微服务架构**,结合多种数据库和消息队列,实现高性能金融数据处理和实时通信。 - -#### 后端目录结构 - -``` -项目根目录/ -├── app.py # 主 Flask 应用(Web 服务器 + API 路由) -├── simulation_background_processor.py # Celery 后台处理器(交易模拟任务) -├── concept_api.py # 概念/行业分析独立 API 服务 -│ -├── wechat_pay.py # 微信支付业务逻辑 -├── wechat_pay_config.py # 微信支付配置(商户号、密钥等) -│ -├── tdays.csv # 交易日历数据(A 股交易日) -├── requirements.txt # Python 依赖包清单 -│ -├── models/ # 数据库模型定义(SQLAlchemy) -│ ├── user.py -│ ├── stock.py -│ └── transaction.py -│ -├── services/ # 业务逻辑服务层 -│ ├── stock_service.py -│ ├── trading_service.py -│ └── notification_service.py -│ -├── utils/ # 后端工具函数 -│ ├── db_utils.py # 数据库连接工具 -│ ├── clickhouse_client.py # ClickHouse 客户端封装 -│ └── date_utils.py # 日期处理工具 -│ -└── config/ # 配置文件 - ├── development.py - ├── production.py - └── test.py -``` - ---- - -#### 核心组件详解 - -##### 1️⃣ `app.py` - 主 Flask 应用 - -**职责**: Web 服务器、API 路由、认证、会话管理、WebSocket 通信 - -**核心功能**: -- **Flask 应用初始化**: 配置 CORS、Session、Logging -- **API 路由定义**: RESTful API 端点(`/api/stocks`, `/api/portfolio`, `/api/community`) -- **认证系统**: Flask-Login 集成(登录、登出、权限校验) -- **WebSocket 服务**: Flask-SocketIO 实现实时推送(股票行情、事件通知) -- **数据库连接**: SQLAlchemy ORM + ClickHouse Client -- **交易日历加载**: 启动时从 `tdays.csv` 加载 A 股交易日数据到全局变量 - -**技术栈**: -- **Flask** - 轻量级 Web 框架 -- **Flask-Login** - 用户会话管理 -- **Flask-SocketIO** - WebSocket 实时通信 -- **Flask-CORS** - 跨域资源共享配置 -- **SQLAlchemy** - ORM(对象关系映射) - -**API 路由示例**: -```python -# 获取股票数据 -@app.route('/api/stocks/', methods=['GET']) -@login_required -def get_stock_data(stock_code): - # 从 ClickHouse 查询历史数据 - data = clickhouse_client.query(f"SELECT * FROM stocks WHERE code = '{stock_code}'") - return jsonify(data) - -# 创建交易订单 -@app.route('/api/orders', methods=['POST']) -@login_required -def create_order(): - data = request.json - order = Order(user_id=current_user.id, stock_code=data['stock_code'], ...) - db.session.add(order) - db.session.commit() - return jsonify({'order_id': order.id}) -``` - -**WebSocket 事件示例**: -```python -# 客户端订阅股票行情 -@socketio.on('subscribe_stock') -def handle_subscribe(data): - stock_code = data['stock_code'] - join_room(f'stock_{stock_code}') - emit('subscribed', {'stock_code': stock_code}) - -# 服务端推送行情更新 -def push_quote_update(stock_code, quote_data): - socketio.emit('stock_quote', quote_data, room=f'stock_{stock_code}') -``` - -**交易日历加载**: -```python -# 全局变量存储交易日 -trading_days = [] - -# 启动时加载 tdays.csv -def load_trading_days(): - global trading_days - with open('tdays.csv', 'r') as f: - trading_days = [line.strip() for line in f.readlines()] - -# 判断是否为交易日 -def is_trading_day(date_str): - return date_str in trading_days -``` - ---- - -##### 2️⃣ `simulation_background_processor.py` - Celery 后台处理器 - -**职责**: 处理长时间运行的后台任务(交易模拟、报表生成、数据同步) - -**为什么需要后台处理器?** -- 交易模拟需要执行数百次历史回测计算(耗时 10-60 秒) -- 避免阻塞 Flask 主线程(影响其他 API 响应) -- 支持任务队列、重试、失败处理 - -**技术栈**: -- **Celery** - 分布式任务队列 -- **Redis** - 消息代理(Broker)和结果存储(Backend) -- **ClickHouse** - 高性能查询历史股票数据 - -**任务示例**: -```python -from celery import Celery - -# Celery 实例 -celery_app = Celery('tasks', broker='redis://localhost:6379/0') - -# 交易模拟任务 -@celery_app.task(bind=True) -def run_simulation(self, user_id, strategy_config): - """ - 执行交易模拟任务 - :param user_id: 用户 ID - :param strategy_config: 策略配置(买入条件、卖出条件、资金管理) - :return: 模拟结果(收益率、最大回撤、交易明细) - """ - try: - # 1. 从 ClickHouse 获取历史数据 - stock_data = clickhouse_client.query(...) - - # 2. 执行回测计算 - for i in range(len(stock_data)): - # 模拟交易逻辑 - if should_buy(stock_data[i]): - buy(stock_data[i]) - if should_sell(stock_data[i]): - sell(stock_data[i]) - - # 3. 计算绩效指标 - result = { - 'total_return': calculate_return(), - 'max_drawdown': calculate_drawdown(), - 'trades': get_trade_history(), - } - - # 4. 保存结果到 MySQL - save_simulation_result(user_id, result) - - return result - except Exception as e: - # 失败重试(最多 3 次) - self.retry(exc=e, countdown=60, max_retries=3) -``` - -**从 Flask 调用 Celery 任务**: -```python -# app.py -@app.route('/api/simulations', methods=['POST']) -@login_required -def start_simulation(): - strategy = request.json - - # 异步提交任务到 Celery - task = run_simulation.delay(current_user.id, strategy) - - return jsonify({ - 'task_id': task.id, - 'status': 'pending', - 'message': '模拟任务已提交,请稍后查看结果' - }) - -# 查询任务状态 -@app.route('/api/simulations/', methods=['GET']) -def get_simulation_status(task_id): - task = run_simulation.AsyncResult(task_id) - - if task.state == 'PENDING': - response = {'status': 'pending', 'progress': 0} - elif task.state == 'SUCCESS': - response = {'status': 'completed', 'result': task.result} - elif task.state == 'FAILURE': - response = {'status': 'failed', 'error': str(task.info)} - - return jsonify(response) -``` - -**启动 Celery Worker**: -```bash -# 启动 worker(监听任务队列) -celery -A simulation_background_processor worker --loglevel=info - -# 启动 Flower(Celery 监控工具) -celery -A simulation_background_processor flower -``` - ---- - -##### 3️⃣ `concept_api.py` - 概念/行业分析 API - -**职责**: 独立的概念板块分析服务(涨停分析、热点行业、资金流向) - -**为什么独立部署?** -- 概念分析计算密集(需要遍历数千只股票) -- 独立扩展性(可部署到多台服务器) -- 服务隔离(不影响主应用性能) - -**部署架构**: -``` -前端请求 → Nginx 反向代理 - ├─ /api → Flask 主应用 (端口 5001) - └─ /concept-api → Concept API (端口 6801) -``` - -**API 示例**: -```python -from flask import Flask, jsonify -import clickhouse_driver - -app = Flask(__name__) - -# 获取涨停股票列表 -@app.route('/limit_up', methods=['GET']) -def get_limit_up_stocks(): - """ - 获取当日涨停股票列表,按概念板块分组 - """ - query = """ - SELECT - stock_code, - stock_name, - concept, - close_price, - change_percent - FROM stock_daily - WHERE trade_date = today() - AND change_percent >= 9.9 - ORDER BY concept, change_percent DESC - """ - - data = clickhouse_client.query(query) - - # 按概念分组 - result = {} - for row in data: - concept = row['concept'] - if concept not in result: - result[concept] = [] - result[concept].append(row) - - return jsonify(result) - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=6801) -``` - -**前端调用**(通过代理): -```javascript -// craco.config.js 配置代理 -proxy: { - '/concept-api': { - target: 'http://49.232.185.254:6801', - pathRewrite: { '^/concept-api': '' }, - } -} - -// 前端调用 -fetch('/concept-api/limit_up') - .then(res => res.json()) - .then(data => console.log(data)); -``` - ---- - -##### 4️⃣ 微信支付集成 - -**文件**: -- `wechat_pay.py` - 微信支付业务逻辑(下单、查询、退款) -- `wechat_pay_config.py` - 配置(商户号、API 密钥、证书路径) - -**核心功能**: -- **统一下单**: 创建支付订单(JSAPI、Native、H5) -- **支付回调**: 接收微信支付结果通知 -- **订单查询**: 查询订单支付状态 -- **退款处理**: 发起退款请求 - -**示例代码**: -```python -# wechat_pay.py -from wechatpy.pay import WeChatPay -from wechat_pay_config import MCHID, API_KEY, CERT_PATH - -# 初始化微信支付客户端 -wechat_pay = WeChatPay( - appid='your_appid', - api_key=API_KEY, - mch_id=MCHID, - mch_cert=CERT_PATH, -) - -# 创建订单 -def create_order(user_id, amount, description): - result = wechat_pay.order.create( - trade_type='JSAPI', - body=description, - total_fee=int(amount * 100), # 单位:分 - notify_url='https://yourdomain.com/api/wechat/callback', - user_id=user_id, - ) - return result - -# 支付回调处理 -@app.route('/api/wechat/callback', methods=['POST']) -def wechat_callback(): - xml_data = request.data - data = wechat_pay.parse_payment_result(xml_data) - - if data['return_code'] == 'SUCCESS': - # 更新订单状态 - order_id = data['out_trade_no'] - update_order_status(order_id, 'paid') - - return wechat_pay.reply('OK', True) -``` - ---- - -##### 5️⃣ 数据库架构 - -**多数据库策略**: - -| 数据库 | 用途 | 特点 | 示例表 | -|--------|------|------|--------| -| **ClickHouse** | 时序数据存储 | OLAP(列式存储、查询速度快) | `stock_daily`(日线数据)
`stock_minute`(分钟数据)
`concept_daily`(概念板块数据) | -| **MySQL/PostgreSQL** | 事务数据存储 | OLTP(ACID 保证、关系型) | `users`(用户表)
`orders`(订单表)
`portfolios`(持仓表)
`subscriptions`(订阅表) | -| **Redis** | 缓存 + 消息队列 | 内存数据库(高速读写) | 股票行情缓存
用户 Session
Celery 任务队列 | - -**ClickHouse 使用场景**: -```python -# 查询某只股票的历史数据(100 万行数据,查询耗时 < 100ms) -query = """ - SELECT - trade_date, - open, high, low, close, volume - FROM stock_daily - WHERE stock_code = '600000.SH' - AND trade_date >= '2020-01-01' - ORDER BY trade_date -""" - -data = clickhouse_client.query(query) -``` - -**MySQL 使用场景**: -```python -# 创建订单(事务保证) -@app.route('/api/orders', methods=['POST']) -def create_order(): - try: - # 开启事务 - order = Order(user_id=user_id, stock_code=stock_code, ...) - db.session.add(order) - - # 扣减账户余额 - account = Account.query.get(user_id) - account.balance -= order.total_amount - - # 提交事务 - db.session.commit() - return jsonify({'success': True}) - except Exception as e: - # 回滚事务 - db.session.rollback() - return jsonify({'error': str(e)}), 500 -``` - ---- - -#### 后端技术栈详解 - -**核心框架**: -- **Flask 2.x** - 轻量级 Web 框架,易于扩展 -- **Flask-Login** - 用户认证与会话管理 -- **Flask-SocketIO** - WebSocket 实时通信(基于 Socket.IO 协议) -- **Flask-CORS** - 跨域资源共享配置 - -**数据库与 ORM**: -- **SQLAlchemy** - Python ORM,支持 MySQL/PostgreSQL -- **ClickHouse Driver** - ClickHouse Python 客户端 -- **Redis-py** - Redis Python 客户端 - -**后台任务处理**: -- **Celery** - 分布式任务队列 -- **Redis** - Celery 消息代理(Broker) - -**第三方服务**: -- **微信支付 SDK** - `wechatpy` -- **腾讯云短信 SDK** - 发送验证码、通知 - ---- - -#### 后端开发最佳实践 - -**1. API 设计规范**: -```python -# ✅ RESTful 风格 -GET /api/stocks # 获取股票列表 -GET /api/stocks/:id # 获取单个股票 -POST /api/stocks # 创建股票 -PUT /api/stocks/:id # 更新股票 -DELETE /api/stocks/:id # 删除股票 - -# ✅ 统一响应格式 -{ - "code": 200, - "message": "success", - "data": { ... } -} - -# ❌ 避免使用动词命名 -POST /api/getStockData # 不推荐 -POST /api/createNewOrder # 不推荐 -``` - -**2. 错误处理**: -```python -# 统一错误处理 -@app.errorhandler(Exception) -def handle_error(e): - if isinstance(e, ValidationError): - return jsonify({'error': str(e)}), 400 - elif isinstance(e, Unauthorized): - return jsonify({'error': 'Unauthorized'}), 401 - else: - logger.error(f'Unhandled exception: {e}') - return jsonify({'error': 'Internal server error'}), 500 -``` - -**3. 数据库连接管理**: -```python -# 使用连接池 -engine = create_engine( - 'mysql://user:pass@localhost/db', - pool_size=10, - max_overflow=20, - pool_recycle=3600, -) - -# 请求结束后自动关闭连接 -@app.teardown_appcontext -def shutdown_session(exception=None): - db.session.remove() -``` - -**4. 性能优化**: -- **数据库索引**: 为常用查询字段添加索引 -- **查询优化**: 避免 N+1 查询,使用 `joinedload` -- **缓存策略**: 使用 Redis 缓存热点数据(股票行情、用户信息) -- **异步任务**: 耗时操作使用 Celery 异步处理 - ---- - -#### 后端部署架构 - -**生产环境部署**: -``` -Nginx (反向代理 + 负载均衡) - ├─ Gunicorn (WSGI 服务器) × 4 进程 - │ └─ Flask 应用 (app.py) - │ - ├─ Celery Worker × 2 进程 - │ └─ 后台任务处理 - │ - ├─ Redis (缓存 + 消息队列) - ├─ MySQL (事务数据) - └─ ClickHouse (时序数据) -``` - -**启动命令**: -```bash -# 启动 Flask 应用(Gunicorn) -gunicorn -w 4 -b 0.0.0.0:5001 app:app - -# 启动 Celery Worker -celery -A simulation_background_processor worker --loglevel=info - -# 启动 Concept API -python concept_api.py -``` - -**监控与日志**: -- **日志**: 使用 Python `logging` 模块记录日志 -- **监控**: Celery Flower 监控任务状态 -- **性能**: 使用 APM 工具(如 New Relic、Datadog) - ---- - -## 配置 - -### 环境文件 -``` -.env.mock - Mock 模式(默认):MSW 拦截所有 API 调用,无需后端 -.env.development - 开发模式:连接到开发后端 -.env.test - 测试模式:用于 'npm run start:test'(后端 + 前端一起) -.env.production - 生产环境构建配置 -``` - -**关键环境变量:** -- `REACT_APP_ENABLE_MOCK=true` - 启用 MSW mocking -- `REACT_APP_API_URL` - 后端 URL(空字符串 = 使用相对路径或 MSW) - -### MSW (Mock Service Worker) 设置 -MSW 用于开发期间的 API mocking: - -1. **激活方式**:在 env 文件中设置 `REACT_APP_ENABLE_MOCK=true` -2. **Worker 文件**:`public/mockServiceWorker.js`(自动生成) -3. **Handlers**:`src/mocks/handlers/`(按领域组织:auth、stock、company 等) -4. **模式**:`onUnhandledRequest: 'warn'` - 未处理的请求会传递到后端 - -当 MSW 激活时,开发服务器代理被禁用(MSW 优先拦截)。 - -### 路径别名 (craco.config.js) -所有别名解析到 `src/` 子目录: -``` -@/ → src/ -@assets/ → src/assets/ -@components/ → src/components/ -@constants/ → src/constants/ -@contexts/ → src/contexts/ -@data/ → src/data/ -@hooks/ → src/hooks/ -@layouts/ → src/layouts/ -@lib/ → src/lib/ -@mocks/ → src/mocks/ -@providers/ → src/providers/ -@routes/ → src/routes/ -@services/ → src/services/ -@store/ → src/store/ -@styles/ → src/styles/ -@theme/ → src/theme/ -@utils/ → src/utils/ -@variables/ → src/variables/ -@views/ → src/views/ -``` - -### Webpack 优化 (craco.config.js) -**性能特性:** -- 文件系统缓存(重新构建速度提升 50-80%) -- 按库激进的代码分割: - - `react-vendor` - React 核心(优先级 30) - - `charts-lib` - echarts、d3、apexcharts、recharts(优先级 25) - - `chakra-ui` - Chakra UI + Emotion(优先级 23) - - `antd-lib` - Ant Design(优先级 22) - - `three-lib` - Three.js(优先级 20) - - `calendar-lib` - moment、date-fns、FullCalendar(优先级 18) -- 从构建中移除 ESLint 插件(速度提升 20-30%) -- 启用 Babel 缓存 -- moment locale 剥离(IgnorePlugin) -- Source maps:生产环境禁用,开发环境使用 `eval-cheap-module-source-map` - -**开发服务器:** -- 端口 3000(prestart 时杀死现有进程) -- 代理(当 MSW 禁用时):`/api` → `http://49.232.185.254:5001` -- Bundle 分析器:`ANALYZE=true npm run build:analyze` - -### 构建流程 -1. `npm run build` 使用 CRACO + webpack 优化编译 -2. Gulp 任务(`gulp licenses`)为 JS/HTML 添加 Creative Tim 许可证头 -3. 输出:`build/` 目录 - -**Node 兼容性:** -```bash -NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' -``` - -## 开发工作流 - -### 路由开发 -添加新路由的步骤: - -1. 在 `src/routes/lazy-components.js` 中添加 lazy import -2. 在 `src/routes/routeConfig.js` 中添加路由配置: - - `path` - URL 路径 - - `component` - 来自 lazyComponents - - `protection` - MODAL/REDIRECT/PUBLIC - - `layout` - 'main'(带导航栏)或 'none'(全屏) -3. 路由自动使用 Suspense + ErrorBoundary 渲染(由 PageTransitionWrapper 处理) - -### 组件组织模式 -基于最近的重构(详见 README.md): - -**原子设计模式:** -- **Atoms(原子)** - 基础 UI 元素(按钮、徽章、输入框) -- **Molecules(分子)** - 原子的组合(卡片、表单) -- **Organisms(有机体)** - 复杂组件(列表、面板) - -**大型组件示例结构(1000+ 行):** -``` -src/views/Community/components/ -├── EventCard/ -│ ├── index.js - 智能包装器(路由紧凑 vs 详细视图) -│ ├── CompactEventCard.js - 紧凑视图 -│ ├── DetailedEventCard.js - 详细视图 -│ ├── EventTimeline.js - 原子组件 -│ ├── EventImportanceBadge.js - 原子组件 -│ └── ... -``` - -**工具提取:** -- 将可复用逻辑提取到 `src/utils/`(如 `priceFormatters.js`) -- 将共享常量提取到 `src/constants/`(如 `animations.js`) - -### API 集成 -**服务层**(`src/services/`): -- 使用 `src/utils/apiConfig.js` 中的 `getApiBase()` 获取基础 URL -- 示例:`${getApiBase()}/api/endpoint` -- 在 mock 模式下,MSW 拦截;在开发/生产环境下,请求后端 - -**添加新的 API 端点:** -1. 在 `src/services/` 中添加服务函数(或在组件中内联) -2. 如果使用 MSW:在 `src/mocks/handlers/{domain}.js` 中添加 handler -3. 在 `src/mocks/handlers/index.js` 中导入 handler - -### Redux 状态管理 -**现有 slices**(`src/store/slices/`): -- `authModalSlice` - 认证模态框状态 -- `posthogSlice` - PostHog 分析 -- `stockSlice` - 股票数据 -- `industrySlice` - 行业/概念数据 -- `subscriptionSlice` - 用户订阅 -- `communityDataSlice` - 社区内容 - -**添加新 slice:** -1. 创建 `src/store/slices/yourSlice.js` -2. 在 `src/store/index.js` 中导入并添加 -3. 通过 `useSelector` 访问,通过 `useDispatch` 派发 - ---- - -### 常见开发任务 - -本节提供常见开发任务的详细步骤和代码示例,帮助快速上手项目开发。 - -#### 1. 如何添加新的 MSW Mock Handler - -**使用场景**: 开发新功能时需要 mock API 接口 - -**步骤**: - -1. 在 `src/mocks/handlers/` 创建新的 handler 文件 -2. 定义 HTTP handlers -3. 在 `src/mocks/handlers/index.js` 中导入并导出 - -**完整示例**: - -```javascript -// ✅ 步骤 1: 创建 src/mocks/handlers/portfolio.js -import { http, HttpResponse } from 'msw'; - -export const portfolioHandlers = [ - // GET 请求示例 - http.get('/api/portfolio/:userId', ({ params }) => { - return HttpResponse.json({ - userId: params.userId, - holdings: [ - { stockCode: '600000', shares: 1000, costPrice: 10.50 }, - { stockCode: '000001', shares: 2000, costPrice: 15.80 }, - ], - totalValue: 42100.00, - }); - }), - - // POST 请求示例 - http.post('/api/portfolio/buy', async ({ request }) => { - const body = await request.json(); - return HttpResponse.json({ - success: true, - orderId: 'ORDER_' + Date.now(), - stockCode: body.stockCode, - shares: body.shares, - }); - }), - - // 模拟错误响应 - http.get('/api/portfolio/error', () => { - return HttpResponse.json( - { error: '服务器错误' }, - { status: 500 } - ); - }), - - // 模拟延迟响应(测试加载状态) - http.get('/api/portfolio/slow', async () => { - await new Promise(resolve => setTimeout(resolve, 2000)); - return HttpResponse.json({ data: 'slow response' }); - }), -]; -``` - -```javascript -// ✅ 步骤 2: 在 src/mocks/handlers/index.js 中导入 -import { authHandlers } from './auth'; -import { stockHandlers } from './stock'; -import { portfolioHandlers } from './portfolio'; // 新增 - -export const handlers = [ - ...authHandlers, - ...stockHandlers, - ...portfolioHandlers, // 新增 - // ... 其他 handlers -]; -``` - -**测试 Mock Handler**: - -```bash -# 启动 Mock 模式 -npm start - -# 查看浏览器控制台,应该看到: -# [MSW] Mocking enabled -# [MSW] GET /api/portfolio/123 (200) -``` - -#### 2. 如何添加新的 Redux Slice - -**使用场景**: 添加需要全局共享的状态(UI 状态、缓存数据) - -**步骤**: - -1. 在 `src/store/slices/` 创建新 slice -2. 在 `src/store/index.js` 中注册 reducer -3. 在组件中使用 - -**完整示例**: - -```javascript -// ✅ 步骤 1: 创建 src/store/slices/portfolioSlice.js -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import { fetchPortfolio } from '@services/portfolioService'; - -// 异步 thunk(用于 API 调用) -export const fetchPortfolioData = createAsyncThunk( - 'portfolio/fetchData', - async (userId, { rejectWithValue }) => { - try { - const data = await fetchPortfolio(userId); - return data; - } catch (error) { - return rejectWithValue(error.response?.data || '加载失败'); - } - } -); - -const portfolioSlice = createSlice({ - name: 'portfolio', - initialState: { - holdings: [], - totalValue: 0, - loading: false, - error: null, - }, - reducers: { - // 同步 action - addHolding: (state, action) => { - state.holdings.push(action.payload); - }, - removeHolding: (state, action) => { - state.holdings = state.holdings.filter( - h => h.stockCode !== action.payload - ); - }, - clearPortfolio: (state) => { - state.holdings = []; - state.totalValue = 0; - }, - }, - extraReducers: (builder) => { - // 异步 action 的状态处理 - builder - .addCase(fetchPortfolioData.pending, (state) => { - state.loading = true; - state.error = null; - }) - .addCase(fetchPortfolioData.fulfilled, (state, action) => { - state.loading = false; - state.holdings = action.payload.holdings; - state.totalValue = action.payload.totalValue; - }) - .addCase(fetchPortfolioData.rejected, (state, action) => { - state.loading = false; - state.error = action.payload; - }); - }, -}); - -export const { addHolding, removeHolding, clearPortfolio } = portfolioSlice.actions; -export default portfolioSlice.reducer; -``` - -```javascript -// ✅ 步骤 2: 在 src/store/index.js 中注册 -import { configureStore } from '@reduxjs/toolkit'; -import authModalReducer from './slices/authModalSlice'; -import portfolioReducer from './slices/portfolioSlice'; // 新增 - -export const store = configureStore({ - reducer: { - authModal: authModalReducer, - portfolio: portfolioReducer, // 新增 - // ... 其他 reducers - }, -}); -``` - -```javascript -// ✅ 步骤 3: 在组件中使用 -import { useSelector, useDispatch } from 'react-redux'; -import { fetchPortfolioData, addHolding } from '@store/slices/portfolioSlice'; - -const PortfolioComponent = () => { - const dispatch = useDispatch(); - const { holdings, totalValue, loading, error } = useSelector( - state => state.portfolio - ); - - useEffect(() => { - // 异步获取数据 - dispatch(fetchPortfolioData('user123')); - }, [dispatch]); - - const handleAddStock = () => { - // 同步更新状态 - dispatch(addHolding({ - stockCode: '600519', - shares: 100, - costPrice: 1800.00, - })); - }; - - if (loading) return ; - if (error) return {error}; - - return ( - - 总市值:¥{totalValue} - {holdings.map(h => ( - - {h.stockCode} - {h.shares}股 - - ))} - - - ); -}; -``` - -#### 3. 如何添加新的页面路由 - -**使用场景**: 添加新的功能页面 - -**步骤**: - -1. 在 `src/views/` 创建页面组件 -2. 在 `src/routes/lazy-components.js` 添加懒加载导入 -3. 在 `src/routes/routeConfig.js` 添加路由配置 - -**完整示例**: - -```javascript -// ✅ 步骤 1: 创建 src/views/Portfolio/index.js -import React from 'react'; -import { Box, Heading, SimpleGrid } from '@chakra-ui/react'; - -export const Portfolio = () => { - return ( - - 我的投资组合 - - {/* 组件内容 */} - - - ); -}; -``` - -```javascript -// ✅ 步骤 2: 在 src/routes/lazy-components.js 添加 -import { lazy } from 'react'; - -export const lazyComponents = { - // ... 现有组件 - Community: lazy(() => import('@views/Community')), - Portfolio: lazy(() => import('@views/Portfolio')), // 新增 -}; -``` - -```javascript -// ✅ 步骤 3: 在 src/routes/routeConfig.js 添加配置 -import { lazyComponents } from './lazy-components'; -import { PROTECTION_MODES } from './constants/protectionModes'; - -export const routeConfig = [ - // ... 现有路由 - { - path: 'portfolio', // URL: /portfolio - component: lazyComponents.Portfolio, - protection: PROTECTION_MODES.REDIRECT, // 需要登录,否则跳转 - layout: 'main', // 使用主布局(带导航栏) - meta: { - title: '投资组合', - description: '查看我的投资组合和持仓', - }, - }, -]; -``` - -**路由保护模式说明**: - -```javascript -// PUBLIC - 无需登录 -{ - path: 'about', - protection: PROTECTION_MODES.PUBLIC, - // 任何人都可以访问 -} - -// MODAL - 未登录时显示登录模态框,不跳转 -{ - path: 'community', - protection: PROTECTION_MODES.MODAL, - // 未登录用户可以看页面,点击某些操作会弹出登录框 -} - -// REDIRECT - 未登录时跳转到登录页 -{ - path: 'portfolio', - protection: PROTECTION_MODES.REDIRECT, - // 未登录用户会被重定向到 /auth/sign-in -} -``` - -**添加嵌套路由**(子路由): - -```javascript -// 父路由 -{ - path: 'portfolio', - component: null, // 使用 Outlet 渲染子路由 - protection: PROTECTION_MODES.REDIRECT, - layout: 'main', - children: [ - { - path: '', // /portfolio - component: lazyComponents.PortfolioOverview, - }, - { - path: 'holdings', // /portfolio/holdings - component: lazyComponents.PortfolioHoldings, - }, - { - path: 'transactions', // /portfolio/transactions - component: lazyComponents.PortfolioTransactions, - }, - ], -} -``` - -#### 4. 如何实现实时数据更新(WebSocket) - -**使用场景**: 股票行情、账户资金、动态消息等实时数据 - -**步骤**: - -1. 创建 WebSocket 服务 -2. 在组件中订阅事件 - -**完整示例**: - -```javascript -// ✅ 步骤 1: 创建 src/services/websocketService.js -import io from 'socket.io-client'; -import { getApiBase } from '@utils/apiConfig'; -import { logger } from '@utils/logger'; - -class WebSocketService { - constructor() { - this.socket = null; - this.listeners = new Map(); - } - - // 连接 WebSocket - connect() { - if (this.socket?.connected) { - logger.info('WebSocket', '已连接,跳过重复连接'); - return; - } - - this.socket = io(getApiBase(), { - transports: ['websocket'], - reconnection: true, - reconnectionDelay: 1000, - reconnectionAttempts: 5, - }); - - this.socket.on('connect', () => { - logger.info('WebSocket', '连接成功'); - }); - - this.socket.on('disconnect', () => { - logger.warn('WebSocket', '连接断开'); - }); - - this.socket.on('error', (error) => { - logger.error('WebSocket', '连接错误', error); - }); - } - - // 断开连接 - disconnect() { - if (this.socket) { - this.socket.disconnect(); - this.socket = null; - } - } - - // 订阅事件 - subscribe(event, callback) { - if (!this.socket) this.connect(); - - this.socket.on(event, callback); - logger.debug('WebSocket', `订阅事件: ${event}`); - - // 存储回调引用,方便取消订阅 - if (!this.listeners.has(event)) { - this.listeners.set(event, new Set()); - } - this.listeners.get(event).add(callback); - } - - // 取消订阅 - unsubscribe(event, callback) { - if (this.socket) { - this.socket.off(event, callback); - } - - const callbacks = this.listeners.get(event); - if (callbacks) { - callbacks.delete(callback); - if (callbacks.size === 0) { - this.listeners.delete(event); - } - } - } - - // 发送消息 - emit(event, data) { - if (this.socket?.connected) { - this.socket.emit(event, data); - } else { - logger.warn('WebSocket', '未连接,无法发送消息'); - } - } -} - -export const wsService = new WebSocketService(); -``` - -```javascript -// ✅ 步骤 2: 在组件中使用 -import { useEffect, useState } from 'react'; -import { Box, Text } from '@chakra-ui/react'; -import { wsService } from '@services/websocketService'; - -const StockQuoteComponent = ({ stockCode }) => { - const [quote, setQuote] = useState(null); - - useEffect(() => { - // 订阅实时行情 - const handleQuoteUpdate = (data) => { - if (data.stockCode === stockCode) { - setQuote(data); - } - }; - - wsService.subscribe('stock_quote', handleQuoteUpdate); - - // 请求订阅特定股票 - wsService.emit('subscribe_stock', { stockCode }); - - // 清理:取消订阅 - return () => { - wsService.unsubscribe('stock_quote', handleQuoteUpdate); - wsService.emit('unsubscribe_stock', { stockCode }); - }; - }, [stockCode]); - - return ( - - 股票代码:{stockCode} - 当前价格:{quote?.price || '--'} - 涨跌幅:{quote?.change || '--'}% - - ); -}; -``` - -**使用自定义 Hook 封装**: - -```javascript -// src/hooks/useStockQuote.js -import { useState, useEffect } from 'react'; -import { wsService } from '@services/websocketService'; - -export const useStockQuote = (stockCode) => { - const [quote, setQuote] = useState(null); - const [connected, setConnected] = useState(false); - - useEffect(() => { - if (!stockCode) return; - - const handleQuote = (data) => { - if (data.stockCode === stockCode) { - setQuote(data); - } - }; - - const handleConnect = () => setConnected(true); - const handleDisconnect = () => setConnected(false); - - wsService.subscribe('stock_quote', handleQuote); - wsService.subscribe('connect', handleConnect); - wsService.subscribe('disconnect', handleDisconnect); - wsService.emit('subscribe_stock', { stockCode }); - - return () => { - wsService.unsubscribe('stock_quote', handleQuote); - wsService.unsubscribe('connect', handleConnect); - wsService.unsubscribe('disconnect', handleDisconnect); - wsService.emit('unsubscribe_stock', { stockCode }); - }; - }, [stockCode]); - - return { quote, connected }; -}; - -// 使用 -const MyComponent = () => { - const { quote, connected } = useStockQuote('600000'); - - return ( - - - {connected ? '已连接' : '未连接'} - - 价格:{quote?.price} - - ); -}; -``` - -#### 5. 如何调试 API 请求(Mock vs Real) - -**切换 Mock/Real 模式**: - -```bash -# ✅ Mock 模式(MSW 拦截所有请求) -npm start -# 使用 .env.mock,REACT_APP_ENABLE_MOCK=true - -# ✅ 真实后端模式 -npm run start:real -# 使用 .env.local,REACT_APP_ENABLE_MOCK=false -# 代理到 http://49.232.185.254:5001 - -# ✅ 开发模式(可自定义配置) -npm run start:dev -# 使用 .env.development -``` - -**查看 MSW 拦截的请求**: - -打开浏览器控制台(F12),查看以下日志: - -``` -[MSW] Mocking enabled (warn mode) -[MSW] GET /api/stocks/600000 → 200 (mock data) -[MSW] POST /api/portfolio/buy → 200 (mock data) -``` - -**调试技巧**: - -```javascript -// src/utils/apiConfig.js -export const getApiBase = () => { - const isMock = process.env.REACT_APP_ENABLE_MOCK === 'true'; - const apiUrl = process.env.REACT_APP_API_URL; - - // 添加调试日志 - console.group('🔧 API 配置'); - console.log('Mock 模式:', isMock ? 'ON ✅' : 'OFF ❌'); - console.log('API URL:', apiUrl || '默认后端'); - console.log('环境:', process.env.NODE_ENV); - console.groupEnd(); - - // ... -}; -``` - -**使用 Redux DevTools 调试状态**: - -1. 安装 Chrome 插件:[Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools) -2. 打开浏览器控制台 → Redux 标签页 -3. 查看 Actions、State Diff、State Tree - -**使用 React DevTools 调试组件**: - -1. 安装 Chrome 插件:[React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools) -2. 打开浏览器控制台 → Components 标签页 -3. 查看组件树、Props、Hooks 状态 - -**使用 Network 面板调试 API**: - -1. 打开浏览器控制台(F12)→ Network 标签页 -2. 筛选 XHR/Fetch 请求 -3. 查看请求头、响应体、状态码 - -**测试 API 错误处理**: - -```javascript -// 在 MSW handler 中模拟错误 -http.get('/api/test-error', () => { - return HttpResponse.json( - { error: '服务器错误' }, - { status: 500 } - ); -}); - -// 测试组件是否正确处理错误 -try { - const data = await fetchData(); -} catch (error) { - console.error('API 错误:', error); - showNotification('数据加载失败', 'error'); -} -``` - ---- - -## 重要说明 - -### 代码分割策略 -重量级页面进行懒加载以减小初始 bundle 大小: -- Community、TradingSimulation、Company 页面使用 `React.lazy()` -- Webpack 将大型库拆分为独立 chunks -- 使用 `npm run build:analyze` 检查 bundle 大小 - -### 错误边界 -- **布局级**:每个布局(MainLayout、Auth)都有自己的 ErrorBoundary -- **页面级**:PageTransitionWrapper 为每个路由包裹 ErrorBoundary -- **策略**:错误被隔离,防止整个应用崩溃 - -### PostHog 分析 -- 在 Redux 中初始化(`posthogSlice`) -- 在 `App.js` 中应用启动时配置 -- 用于用户行为追踪 - -### 性能考虑 -- **大型组件**:如果组件超过 ~500 行,考虑重构(见 README.md) -- **重渲染**:对昂贵操作使用 `React.memo`、`useMemo`、`useCallback` -- **Bundle 大小**:使用 webpack-bundle-analyzer 监控 -- **缓存**:Webpack 文件系统缓存显著加速重新构建 - ---- - -## 技术路径与开发指南 - -本章节定义了项目的技术方向、架构原则和开发标准。所有开发工作都应遵循这些指南。 - -### UI 框架选型与使用指南 - -#### 架构决策:混合 UI 框架策略 - -**决策时间**: 2025-01-07 -**决策内容**: Ant Design 60% + Chakra UI 40% 混合架构 - -**选型理由**: - -本项目是**金融交易分析平台**,需要同时满足: -1. 企业级数据展示能力(复杂表格、表单、日历) -2. 现代化视觉设计(统计卡片、图表容器、主题切换) -3. 高开发效率(减少自定义代码) -4. 灵活的主题系统(深色/浅色模式) - -经过评估,单一 UI 框架无法同时满足所有需求,因此采用**混合策略**: - -**Ant Design (60%)** - 数据组件为主 -- 复杂数据表格(Table - 排序、筛选、分页、虚拟滚动) -- 高级表单组件(Form、InputNumber、Cascader、DatePicker) -- 日历组件(Calendar - 事件标记、自定义单元格) -- 抽屉和模态框(Drawer、Modal - 大尺寸、多Tab) -- 企业级交互组件(Tooltip、Popover、Dropdown) - -**Chakra UI (40%)** - 视觉与布局为主 -- 全局布局系统(SimpleGrid、Stack、Flex、Box) -- 统计卡片(Stat - 支持渐变、动画、主题切换) -- 图表容器(Box、Card - 灵活样式定制) -- 主题系统(useColorModeValue - 深色模式) -- 通用 UI 组件(Button、Badge、Tag - 可选) - -#### 组件选择决策指南 - -**何时使用 Chakra UI**: -``` -✅ 页面布局(SimpleGrid、Stack、Flex、Box) -✅ 统计卡片(Stat - 带渐变背景、动画效果) -✅ 图表容器(Box、Card - 需要灵活样式) -✅ 主题切换(useColorModeValue、useTheme) -✅ 需要 CSS-in-JS 动态定制的组件 -✅ 响应式设计(breakpoints: base、md、lg) -``` - -**何时使用 Ant Design**: -``` -✅ 数据表格(需要排序、筛选、分页、虚拟滚动) -✅ 复杂表单(多字段、联动计算、实时验证) -✅ 数字输入(金融场景、格式化、单位转换) -✅ 日期选择(禁用日期、范围选择、自定义渲染) -✅ 级联选择器(多级联动、搜索、异步加载) -✅ 日历组件(事件标记、自定义单元格、月份切换) -✅ 大尺寸抽屉/模态框(900px+、多Tab、额外操作区) -✅ 企业级交互(Tooltip、Popover、Dropdown、Menu) -``` - -#### 混合使用最佳实践 - -**1. 主题统一** - -使用 Chakra UI 的主题系统作为主色调,通过 Ant Design 的 `ConfigProvider` 同步样式: - -```javascript -// src/providers/AppProviders.js -import { ConfigProvider, theme as antdTheme } from 'antd'; -import { ChakraProvider, useColorMode, useTheme } from '@chakra-ui/react'; - -function AppProviders({ children }) { - const { colorMode } = useColorMode(); - const chakraTheme = useTheme(); - - // 🎨 将 Chakra 主题色同步到 Ant Design - const antdCustomTheme = { - token: { - colorPrimary: chakraTheme.colors.blue[500], // 主色 - colorLink: chakraTheme.colors.blue[600], // 链接色 - colorSuccess: chakraTheme.colors.green[500], // 成功色 - colorWarning: chakraTheme.colors.orange[500], // 警告色 - colorError: chakraTheme.colors.red[500], // 错误色 - borderRadius: 8, // 圆角 - fontSize: 14, // 字体大小 - }, - algorithm: colorMode === 'dark' - ? antdTheme.darkAlgorithm - : antdTheme.defaultAlgorithm, - }; - - return ( - - - {children} - - - ); -} -``` - -**2. 布局策略** - -外层使用 Chakra UI 布局,内层嵌入 Ant Design 数据组件: - -```jsx -// ✅ 推荐:Chakra UI 容器 + Ant Design 数据 -import { Box, Heading } from '@chakra-ui/react'; -import { Table } from 'antd'; - - - 交易历史 {/* Chakra 标题 */} - - - -// ❌ 避免:全用 Ant Design 做布局(失去主题控制) - {/* 无法使用 useColorModeValue */} -
- -``` - -**3. 样式冲突处理** - -如果出现样式冲突,使用 Chakra UI 的 `sx` prop 覆盖 Ant Design 样式: - -```jsx -import { Box, useColorModeValue } from '@chakra-ui/react'; -import { Table } from 'antd'; - - tr > th': { - backgroundColor: useColorModeValue('gray.50', 'gray.700'), - color: useColorModeValue('gray.700', 'gray.200'), - borderColor: useColorModeValue('gray.200', 'gray.600'), - }, - '.ant-table-tbody > tr > td': { - borderColor: useColorModeValue('gray.200', 'gray.600'), - }, - '.ant-table-tbody > tr:hover > td': { - backgroundColor: useColorModeValue('gray.50', 'gray.750'), - }, - }} -> -
- -``` - -**4. 图标统一** - -优先使用 `lucide-react`(已安装),减少对多个图标库的依赖: - -```jsx -// ✅ 推荐:lucide-react(体积小、一致性好) -import { Star, TrendingUp, Calendar } from 'lucide-react'; -import { Icon } from '@chakra-ui/react'; - - - - -// ⚠️ 仅在 Ant Design 组件内部使用 @ant-design/icons -import { StarFilled, ArrowUpOutlined } from '@ant-design/icons'; - -
- } - ]} -/> -``` - -#### 实际应用案例 - -**案例 1: 交易面板** -```jsx -// 布局: Chakra UI (Box, Stack, SimpleGrid) -// 表单: Ant Design (Form, InputNumber) -// 按钮: Chakra UI (Button) -// 统计: Chakra UI (Stat) - - - - {/* Chakra UI 统计卡片 */} - - 账户余额 - ¥123,456.78 - - - +23.45% - - - - - {/* Ant Design 表单 */} -
- - `${value} 股`} - parser={value => value.replace(' 股', '')} - /> - - - - {/* Chakra UI 按钮 */} - - - - -
-``` - -**案例 2: 板块详情(涨停分析)** -```jsx -// 容器: Chakra UI (Card, Box) -// 折叠面板: Ant Design (Collapse) - 数据量大 -// 表格: Ant Design (Table) - 需要虚拟滚动 -// 标签: Chakra UI (Badge, Tag) - - - - {/* Chakra UI 统计卡片 */} - - - - 涨停股票数 - 156 - - - - - - {/* Ant Design 折叠面板 + 表格 */} - - {sectors.map(sector => ( - - {sector.name} - {sector.count} 只 - - } - > -
- - ))} - - -``` - -**案例 3: 投资日历** -```jsx -// 布局: Chakra UI (SimpleGrid) -// 日历: Ant Design (Calendar) - 自定义渲染 -// 抽屉: Ant Design (Drawer) - 900px宽 -// 统计卡片: Chakra UI (Stat) - - - {/* Ant Design 日历 */} - - { - const count = getEventCount(date); - return ( - - {count} 条 - - ); - }} - onSelect={onDateSelect} - /> - - - {/* Chakra UI 统计区域 */} - - - 本月事件 - 245 - - - - -{/* Ant Design 抽屉 */} - - {/* 内容 */} - -``` - -#### 性能优化建议 - -1. **按需加载**: 使用 webpack 代码分割(已在 craco.config.js 配置) -2. **Tree Shaking**: 从 Ant Design 按需导入组件 - ```javascript - // ✅ 推荐:按需导入 - import { Table, Form, Input } from 'antd'; - - // ❌ 避免:全量导入 - import antd from 'antd'; - ``` -3. **样式隔离**: Ant Design 组件包裹在独立的 Box 中,避免全局样式污染 -4. **虚拟化**: 大型列表使用 Ant Design Table 的 `virtual` 属性 -5. **懒加载**: 图表组件使用 React.lazy() 按需加载 - -#### Bundle 大小监控 - -**当前配置**(craco.config.js): -- `chakra-ui` chunk: ~150-200KB gzipped -- `antd-lib` chunk: ~200-250KB gzipped -- **总计**: ~400KB gzipped - -**优化目标**: 保持 < 500KB gzipped - -**监控命令**: -```bash -npm run build:analyze # 打开 webpack bundle analyzer -``` - -**优化机会**: -- 减少 `@ant-design/icons` 使用,改用 `lucide-react` -- 按需加载 Ant Design 组件(避免全量导入) -- 考虑移除未使用的 Chakra UI 组件 - -#### 技术债务说明 - -**为什么不迁移到单一框架?** - -**经过评估(2025-01-07)**: -- **迁移成本**: - - Ant Design Table → @tanstack/react-table: 5-7 天 - - Ant Design Form → 自定义表单: 3-5 天 - - Ant Design Calendar → react-big-calendar: 2-3 天 - - **总计**: 10-15 天工作量 - -- **ROI 分析**: - - Bundle 减少: ~200KB - - 开发体验: 下降(需要更多自定义代码) - - 维护成本: 上升(自定义组件需要维护) - - 视觉一致性: 提升(单一设计语言) - -- **结论**: 当前混合方案的性价比更高,暂不迁移 - -**未来演进方向**: - -**短期(3-6 个月)**: -- 优化 Ant Design 组件样式,与 Chakra UI 主题更统一 -- 减少 `@ant-design/icons` 使用,改用 `lucide-react` -- 统一按钮、Badge、Tag 组件(优先使用 Chakra UI) - -**中期(6-12 个月)**: -- 监控两个框架的使用率(通过 webpack-bundle-analyzer) -- 如果 Chakra UI 使用率 < 10%,考虑完全移除 -- 如果 Ant Design 使用率 < 10%,考虑完全移除 -- 观察社区动态,评估新的 UI 框架(Radix UI + Tailwind CSS) - -**长期(12+ 个月)**: -- 如果 React 19 Server Components 成熟,重新评估 UI 框架选型 -- 考虑自建设计系统(基于 Radix UI Primitives) - ---- - -### 技术栈决策与演进路线 - -#### 当前技术选型及理由 - -**前端框架栈:** -- **React 18.3.1**: 成熟的生态系统、并发渲染特性、广泛的社区支持 -- **Chakra UI 2.8.2**: 主UI库,优先考虑可访问性、主题定制能力强、基于 Emotion 的样式系统 -- **Ant Design**: 辅助UI库,用于复杂的数据密集型组件(表格、表单),弥补 Chakra UI 的不足 -- **Redux Toolkit**: 可预测的全局状态管理、优秀的 DevTools 集成、完善的 TypeScript 支持 - -**构建与开发工具:** -- **CRACO**: 允许在不 eject 的情况下自定义 webpack 配置 -- **MSW (Mock Service Worker)**: 在网络层进行 API mock,提供更真实的开发体验,加快开发迭代 -- **Webpack 5**: 利用文件系统缓存,重新构建速度提升 50-80% - -**图表库(多库策略):** -- **ApexCharts**: 主要用于金融/交易图表(K线图、折线图) -- **ECharts**: 用于复杂的交互式可视化和大数据集渲染 -- **Recharts**: 轻量级选项,用于仪表板中的简单图表 -- **D3.js**: 底层控制,用于自定义可视化 -- **选型理由**: 不同图表类型有不同的最优库,多库策略提供灵活性 - -**后端技术栈:** -- **Flask + SQLAlchemy**: Python 生态系统与金融/数据科学工作流契合度高 -- **ClickHouse**: 针对时序分析查询优化(股票数据、交易指标) -- **MySQL/PostgreSQL**: 传统关系型数据库,用于事务性数据,提供 ACID 保证 -- **Celery + Redis**: 处理长时间运行的后台任务(交易模拟、报表生成) - -#### 计划中的技术演进 - -**短期(未来 3-6 个月):** -- **React Query / TanStack Query**: 逐步从 Redux 迁移到 React Query 管理服务端状态 - - 优势: 自动缓存、后台自动刷新、乐观更新 - - 策略: 从新功能开始,然后迁移现有 API 调用 - - Redux 将继续用于客户端状态(UI 状态、模态框等) - -- **Vite 迁移**: 考虑从 CRACO/webpack 迁移到 Vite,提升开发体验 - - 优势: HMR 速度提升 10-50 倍,配置更简单 - - 风险: 可能需要大量重构构建配置 - - 决策节点: React Query 迁移稳定后 - -**中期(6-12 个月):** -- **React 19**: 生态系统稳定后升级 - - 关注点: Chakra UI 和 Ant Design 的兼容性 - - 新特性利用: Server Components(如适用)、改进的 Suspense - -- **TypeScript 迁移**: 逐步为关键路径添加 TypeScript - - 优先级: API 服务层 → Redux slices → 共享组件 → 视图组件 - - 新文件使用 `.ts`/`.tsx`,遗留代码保持 `.js`/`.jsx`(渐进式迁移) - -**长期(12+ 个月):** -- **Monorepo 结构**: 如果项目规模扩大,考虑 Nx 或 Turborepo -- **微前端架构**: 如果团队规模扩大,考虑模块联邦(Module Federation) - -#### 废弃/避免使用的技术 - -- **Moment.js**: 正在逐步淘汰,改用 `date-fns` 或原生 `Intl.DateTimeFormat` - - 当前已配置 webpack `IgnorePlugin` 以移除 locale 文件 - - 新的日期处理代码请使用 `date-fns` - -- **jQuery**: 新代码中禁止使用(与 React 虚拟 DOM 冲突) - -- **Class 组件**: 所有新组件必须使用函数式组件 + Hooks - -### 架构设计原则 - -#### 1. 组件设计原则 - -**原子设计层级:** -``` -原子组件 (Atoms, 1-50 行) - ↓ 组合成 -分子组件 (Molecules, 50-150 行) - ↓ 组合成 -有机体组件 (Organisms, 150-500 行) - ↓ 组合成 -模板/视图 (Templates/Views, 500+ 行,但主要是组合) -``` - -**单一职责原则:** -- 每个组件应该只做一件事,并做好 -- 如果组件超过 500 行,很可能做了太多事情 → 需要重构 -- 将逻辑提取到自定义 hooks、工具函数或子组件中 - -**组合优于配置:** -```jsx -// 推荐: 可组合 - - 标题 - 内容 - - -// 避免: 过多的 props - -``` - -**就近原则(Co-location):** -``` -ComponentName/ -├── index.js - 主导出文件 -├── ComponentName.js - 实现代码 -├── ComponentName.test.js - 测试 -├── hooks/ - 组件专用 hooks -│ └── useComponentLogic.js -├── utils.js - 组件专用工具函数 -└── constants.js - 组件专用常量 -``` - -#### 2. 状态管理决策树 - -使用此决策树确定状态应该存储在哪里: - -``` -是否是服务端数据(来自 API)? -├─ 是 → 使用 React Query(或暂时使用 Redux,后续迁移) -│ -└─ 否 → 是否需要在多个不相关的组件间共享? - ├─ 是 → 是否是 UI 状态(模态框、侧边栏开关等)? - │ ├─ 是 → Redux (authModalSlice, sidebarSlice 等) - │ └─ 否 → React Context (AuthContext, NotificationContext) - │ - └─ 否 → 是否需要在父组件和多个子组件间共享? - ├─ 是 → 状态提升到共同父组件,通过 props 传递 - └─ 否 → 本地组件状态 (useState, useReducer) -``` - -**状态管理规则:** -- **Redux**: 全局 UI 状态、跨切面关注点、需要在路由切换间保持的状态 -- **Context**: 依赖注入(主题、认证)、避免属性透传,适合稳定值 -- **组件状态**: 表单输入、开关、本地 UI 状态(下拉菜单、标签页) -- **React Query**(未来): 所有服务端状态、API 缓存、数据变更 - -**性能规则:** -- 如果 Context 值频繁变化,拆分为多个 Context 或使用 Redux -- 在 Context 边界使用 `React.memo` 防止不必要的重渲染 - -#### 3. 代码分割策略 - -**路由级分割(主要策略):** -- 所有主要视图在 `src/routes/lazy-components.js` 中使用 `React.lazy()` -- 重量级页面(Community、TradingSimulation、Company)始终懒加载 -- 认证页面可以懒加载(不在关键路径上) - -**库级分割(webpack):** -- 已在 `craco.config.js` 中配置 -- 大型库独立打包:charts、Three.js、Chakra、Ant Design -- 关键规则: 保持初始 bundle < 500KB gzipped - -**组件级分割(选择性使用):** -- 仅用于真正重量级且条件渲染的组件(如 3D 可视化) -```jsx -const HeavyChart = lazy(() => import('./HeavyChart')); -// 仅在需要时渲染 -{showChart && }>} -``` - -#### 4. API 层设计模式 - -**服务层架构:** -``` -组件 (Component) - ↓ 调用 -自定义 Hook (useStockData) - ↓ 使用 -服务函数 (src/services/stockService.js) - ↓ 调用 -Axios 实例(配置了 baseURL、拦截器) - ↓ 请求 -后端 API 或 MSW Mock -``` - -**API 服务模板:** -```javascript -// src/services/exampleService.js -import axios from 'axios'; -import { getApiBase } from '@utils/apiConfig'; - -const api = axios.create({ - baseURL: getApiBase(), - timeout: 10000, -}); - -// 请求拦截器(添加认证 token) -api.interceptors.request.use(config => { - const token = localStorage.getItem('token'); - if (token) config.headers.Authorization = `Bearer ${token}`; - return config; -}); - -// 响应拦截器(全局错误处理) -api.interceptors.response.use( - response => response.data, - error => { - // 全局错误处理 - console.error('API Error:', error); - return Promise.reject(error); - } -); - -export const fetchExample = async (id) => { - return api.get(`/api/example/${id}`); -}; -``` - -**MSW Mock Handler 模板:** -```javascript -// src/mocks/handlers/example.js -import { http, HttpResponse } from 'msw'; - -export const exampleHandlers = [ - http.get('/api/example/:id', ({ params }) => { - return HttpResponse.json({ - id: params.id, - data: 'mock data', - }); - }), -]; -``` - -#### 5. 错误处理架构 - -**三层错误边界策略:** -``` -App.js(根组件) - ↓ 捕获致命错误(显示降级 UI,记录到监控服务) -布局 (MainLayout, AuthLayout) - ↓ 捕获布局特定错误(显示错误页面,允许重试) -页面(通过 PageTransitionWrapper 包裹的各个路由) - ↓ 捕获页面特定错误(显示错误消息,允许导航) -``` - -**API 错误处理模式:** -```javascript -try { - const data = await fetchData(); - // 处理成功情况 -} catch (error) { - if (error.response?.status === 401) { - // 跳转到登录页 - } else if (error.response?.status === 403) { - // 显示权限错误 - } else { - // 显示通用错误 - showNotification('数据加载失败', 'error'); - } -} -``` - -### 开发规范与最佳实践 - -#### 1. 组件开发规范 - -**文件命名约定:** -- 组件: `PascalCase.js` (如 `EventCard.js`) -- 工具函数: `camelCase.js` (如 `priceFormatters.js`) -- 常量: `SCREAMING_SNAKE_CASE.js` 或 `camelCase.js` (如 `API_ENDPOINTS.js`, `animations.js`) -- Hooks: `useCamelCase.js` (如 `useStockData.js`) - -**组件文件结构:** -```javascript -// 1. 导入(分组并排序) -import React, { useState, useEffect } from 'react'; -import { Box, Text } from '@chakra-ui/react'; -import { useDispatch } from 'react-redux'; -import PropTypes from 'prop-types'; -import { CustomComponent } from '@components/CustomComponent'; -import { useCustomHook } from '@hooks/useCustomHook'; -import { helperFunction } from './utils'; - -// 2. 常量(组件专用) -const DEFAULT_VALUE = 10; - -// 3. 组件定义 -export const MyComponent = ({ prop1, prop2 }) => { - // 3a. Hooks(按类型分组) - const dispatch = useDispatch(); - const [state, setState] = useState(DEFAULT_VALUE); - const customValue = useCustomHook(); - - // 3b. 副作用 - useEffect(() => { - // 副作用逻辑 - }, []); - - // 3c. 事件处理器 - const handleClick = () => { - setState(prev => prev + 1); - }; - - // 3d. 渲染逻辑 - return ( - - {state} - - ); -}; - -// 4. PropTypes(或 TypeScript 类型) -MyComponent.propTypes = { - prop1: PropTypes.string.isRequired, - prop2: PropTypes.number, -}; - -// 5. 默认 Props -MyComponent.defaultProps = { - prop2: 0, -}; -``` - -**Props 设计指南:** -- 在组件参数中使用对象解构 -- 必需的 props 在前,可选的 props 在后 -- 布尔类型 props 使用 `is`、`has`、`should` 前缀(如 `isLoading`、`hasError`) -- 回调函数使用 `onEventName` 格式(如 `onClick`、`onSubmit`) -- 避免传递整个对象,只传递需要的几个属性 - -**性能优化检查清单:** -- [ ] 昂贵的计算是否用 `useMemo` 包裹? -- [ ] 传递给子组件的回调函数是否用 `useCallback` 包裹? -- [ ] 列表项是否使用唯一且稳定的 `key` props(不是数组索引)? -- [ ] 大型列表是否虚拟化(react-window 或 react-virtuoso)? -- [ ] 频繁接收 prop 更新的组件是否用 `React.memo` 包裹? -- [ ] 图片是否懒加载(`loading="lazy"`)? - -#### 2. Hooks 开发规范 - -**自定义 Hook 规则:** -- 必须以 `use` 前缀开头 -- 应该在多个组件间可复用 -- 应该处理一个特定关注点(数据获取、表单状态等) -- 应该返回对象或数组(不直接返回原始值) - -**自定义 Hook 模板:** -```javascript -// src/hooks/useStockData.js -import { useState, useEffect } from 'react'; -import { fetchStockData } from '@services/stockService'; - -export const useStockData = (stockId) => { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - useEffect(() => { - if (!stockId) return; - - let isMounted = true; - setLoading(true); - - fetchStockData(stockId) - .then(result => { - if (isMounted) { - setData(result); - setError(null); - } - }) - .catch(err => { - if (isMounted) { - setError(err); - } - }) - .finally(() => { - if (isMounted) setLoading(false); - }); - - return () => { - isMounted = false; - }; - }, [stockId]); - - return { data, loading, error }; -}; -``` - -#### 3. Git 工作流与提交规范 - -**分支命名约定:** -``` -feature/简短描述 - 新功能 -fix/bug描述 - Bug 修复 -refactor/组件名称 - 代码重构 -docs/变更内容 - 文档更新 -chore/任务描述 - 维护任务 -``` - -**提交信息格式:** -``` -<类型>(<范围>): <主题> - -<正文> - -<脚注> -``` - -**类型:** -- `feat`: 新功能 -- `fix`: Bug 修复 -- `refactor`: 既不修复 Bug 也不添加功能的代码更改 -- `style`: 代码风格更改(格式化、缺少分号等) -- `docs`: 文档更改 -- `test`: 添加或更新测试 -- `chore`: 维护任务(依赖、构建配置等) - -**示例:** -``` -feat(community): 添加按日期范围筛选事件功能 - -实现了日期范围选择器用于筛选事件。 -添加了 Redux slice 用于筛选状态管理。 - -Closes #123 - ---- - -fix(trading): 修正模拟交易中的利润计算 - -修复了导致盈亏显示不正确的浮点精度问题。 - ---- - -refactor(components): 将 EventCard 拆分为原子组件 - -将 1200 行的 EventCard 拆分为: -- CompactEventCard -- DetailedEventCard -- EventTimeline (原子组件) -- EventImportanceBadge (原子组件) -``` - -**代码审查检查清单:** -- [ ] 代码是否遵循组件结构指南? -- [ ] 是否有超过 500 行应该拆分的组件? -- [ ] 状态管理是否合适(Redux vs Context vs Local)? -- [ ] 是否存在性能问题(不必要的重渲染、缺少记忆化)? -- [ ] 新功能是否有错误边界? -- [ ] 错误处理是否全面(API 错误、边界情况)? -- [ ] 新的 API 是否在 MSW 中 mock 以便开发? -- [ ] 是否定义了 PropTypes 或 TypeScript 类型? -- [ ] 代码是否具有可访问性(ARIA 标签、键盘导航)? -- [ ] 是否有 console 错误或警告? - -### 技术债务与已知问题 - -本节跟踪技术债务、性能问题和计划中的重构工作。发现或解决问题时请更新此列表。 - -#### 高优先级技术债务 - -**1. Redux 到 React Query 迁移** -- **状态**: 已计划 -- **影响**: 高 - 影响所有 API 调用 -- **时间线**: 2025 年 Q2 开始 -- **描述**: 将服务端状态从 Redux 迁移到 React Query,以获得更好的缓存、自动重新获取和减少样板代码 -- **受影响文件**: `src/store/slices/*Slice.js`、整个应用中的 API 服务调用 -- **迁移策略**: - 1. 在 `AppProviders.js` 中设置 React Query provider - 2. 创建 `src/queries/` 目录存放 query hooks - 3. 一次迁移一个领域(从 Community 或 Stock 数据开始) - 4. Redux 保留用于 UI 状态(模态框、侧边栏等) - -**2. 大型组件重构** -- **状态**: 进行中 -- **影响**: 中 - 可维护性和可读性 -- **需要重构的组件**: - - 任何超过 500 行的新组件 - - 审查 `src/views/` 中的组件,寻找潜在的拆分机会 -- **遵循模式**: 参见上面的"组件组织模式"章节 - -**3. TypeScript 渐进式迁移** -- **状态**: 已计划 -- **影响**: 高 - 类型安全、减少运行时错误 -- **时间线**: 2025 年 Q3 开始 -- **迁移顺序**: - 1. `src/utils/` (工具函数 - 最容易添加类型) - 2. `src/services/` (API 层 - 高价值) - 3. `src/store/` (Redux slices - 中等价值) - 4. `src/components/` (共享组件) - 5. `src/views/` (页面组件 - 最后) - -#### 中优先级技术债务 - -**4. Webpack 到 Vite 迁移** -- **状态**: 评估中 -- **影响**: 高 - 开发体验 -- **好处**: HMR 速度提升 10-50 倍,配置更简单 -- **风险**: 可能需要大量重构构建配置、CRACO 自定义配置 -- **决策节点**: React Query 迁移完成后 - -**5. Moment.js 替换** -- **状态**: 进行中(已配置移除 locale 文件) -- **影响**: 低 - bundle 大小减少(约 20KB) -- **策略**: 新代码中用 `date-fns` 替换 `moment()`,现有代码逐步迁移 -- **使用 moment.js 的文件**: 搜索 `import moment` 以识别 - -**6. 统一错误处理** -- **状态**: 需要改进 -- **影响**: 中 - 用户体验、调试 -- **问题**: - - 部分 API 调用缺少错误处理 - - 错误边界使用不一致 - - 某些地方的错误消息对用户不友好 -- **行动项**: - - 审计所有 API 调用的错误处理 - - 为所有主要视图添加错误边界 - - 标准化错误消息显示(NotificationContext) - -#### 低优先级技术债务 - -**7. 测试覆盖率** -- **状态**: 不足 -- **影响**: 中 - 变更信心 -- **当前覆盖率**: 未知(需要测量) -- **目标**: 关键路径 60%+ 覆盖率 -- **优先区域**: - 1. 工具函数(易于测试、高价值) - 2. Redux slices(状态管理逻辑) - 3. 自定义 hooks(可复用逻辑) - 4. 关键 UI 流程(认证、交易模拟) - -**8. Bundle 大小优化** -- **状态**: 持续监控中 -- **影响**: 低 - 加载时间 -- **当前大小**: 使用 `npm run build:analyze` 检查 -- **优化机会**: - - 移除未使用的依赖 - - Tree-shake 大型库(lodash → lodash-es) - - 考虑懒加载 Three.js 和其他重量级库 - -#### 已知问题与限制 - -**性能:** -- **Community 视图中的大型事件列表**: 如果列表超过 100 项,考虑虚拟化(react-window) -- **图表重渲染**: ApexCharts 在大数据集时可能较慢;考虑节流数据更新 - -**浏览器兼容性:** -- **不支持 IE11**: 现代 JavaScript 特性需要 polyfills 才能支持旧浏览器 -- **Safari 兼容性**: 某些 Chakra UI 动画在 Safari 中行为可能不同 - -**移动端响应式:** -- **交易模拟**: 复杂表格在移动端未完全优化 -- **图表**: 可能需要改进触摸事件处理 - -**数据/API:** -- **实时 WebSocket**: 连接断开时不总是能优雅处理;需添加重连逻辑 -- **ClickHouse 查询**: 大日期范围的某些分析查询可能较慢;考虑分页或聚合 - ---- - -## 项目变更历史 - -本节记录项目的重要变更、重构和架构调整,帮助理解项目演进过程。 - -> **📝 页面级变更历史**: 特定页面的详细变更历史和技术文档已迁移到各自的文档中: -> - **Community 页面**: [docs/Community.md](./docs/Community.md) - 页面架构、组件结构、数据流、变更历史 -> - **Agent 系统**: [AGENT_INTEGRATION_COMPLETE.md](./AGENT_INTEGRATION_COMPLETE.md) - Agent 系统集成完整说明 -> - **其他页面**: 根据需要创建独立的页面文档 - -### 2025-11-07: Agent 系统集成到 mcp_server.py - -**影响范围**: 后端 MCP 服务器 + 前端 Agent 聊天功能 - -**集成成果**: -- 将独立的 Agent 系统完全集成到 `mcp_server.py` 中 -- 使用 **Kimi (kimi-k2-thinking)** 进行计划制定和推理 -- 使用 **DeepMoney (本地部署)** 进行新闻总结 -- 实现三阶段智能分析流程(计划→执行→总结) -- 前端使用 ChatInterfaceV2 + 可折叠卡片展示执行过程 -- **无需运行多个脚本**,所有功能集成在单一服务中 - -**技术要点**: -- 新增 `MCPAgentIntegrated` 类(991-1367行) -- 新增 `/agent/chat` API 端点 -- 新增特殊工具 `summarize_news`(使用 DeepMoney) -- Kimi 使用 `reasoning_content` 字段记录思考过程 -- 自动替换占位符("前面的新闻数据" → 实际数据) - -**前端组件**: -- `ChatInterfaceV2.js` - 新版聊天界面 -- `PlanCard.js` - 执行计划展示 -- `StepResultCard.js` - 步骤结果卡片(可折叠) -- 路由:`/agent-chat` - -**详细文档**: 参见 [AGENT_INTEGRATION_COMPLETE.md](./AGENT_INTEGRATION_COMPLETE.md) - -### 2025-10-30: EventList.js 组件化重构 - -**影响范围**: Community 页面核心组件 - -**重构成果**: -- 将 1095 行的 `EventList.js` 拆分为 497 行主组件 + 10 个子组件 -- 代码行数减少 **54.6%** (598 行) -- 创建了 7 个原子组件 (Atoms) 和 2 个组合组件 (Molecules) - -**新增组件**: -- `EventCard/` - 统一入口,智能路由紧凑/详细模式 - - `CompactEventCard.js` - 紧凑模式事件卡片 - - `DetailedEventCard.js` - 详细模式事件卡片 - - 7 个原子组件: EventTimeline, EventImportanceBadge, EventStats, EventFollowButton, EventPriceDisplay, EventDescription, EventHeader - -**新增工具函数**: -- `src/utils/priceFormatters.js` - 价格格式化工具 (getPriceChangeColor, formatPriceChange, PriceArrow) -- `src/constants/animations.js` - 动画常量 (pulseAnimation, fadeIn, slideInUp) - -**优势**: 提高了代码可维护性、可复用性、可测试性和性能 - -**详细文档**: 参见 [docs/Community.md](./docs/Community.md) - ---- - -## 更新本文档 - -本 CLAUDE.md 文件是一个持续更新的文档。在以下情况下应更新它: -- 添加新的架构模式或指南 -- 发现新的最佳实践 -- 解决或添加技术债务项 -- 做出重要的技术决策 -- 进行重要的代码清理或重构 - -所有开发人员应在入职时审查本文档,并在做出架构决策时参考它。 - -**文档维护指南**: -- 重要变更应记录在"项目变更历史"章节 -- 技术决策应记录在"技术路径与开发指南"章节 -- 开发规范更新应记录在"开发规范与最佳实践"章节 \ No newline at end of file diff --git a/package.json b/package.json index 5ececf89..4e18f16a 100755 --- a/package.json +++ b/package.json @@ -95,10 +95,10 @@ "start:real": "NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' env-cmd -f .env.local craco start", "prestart:dev": "kill-port 3000", "start:dev": "NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' env-cmd -f .env.development craco start", - "start:test": "concurrently \"python app_2.py\" \"npm run frontend:test\" --names \"backend,frontend\" --prefix-colors \"blue,green\"", + "start:test": "concurrently \"python app.py\" \"npm run frontend:test\" --names \"backend,frontend\" --prefix-colors \"blue,green\"", "frontend:test": "NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' env-cmd -f .env.test craco start", "dev": "npm start", - "backend": "python app_2.py", + "backend": "python app.py", "build": "NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' env-cmd -f .env.production craco build && gulp licenses", "build:analyze": "NODE_OPTIONS='--openssl-legacy-provider --max_old_space_size=4096' ANALYZE=true craco build", "test": "craco test --env=jsdom",