Files
vf_react/CLAUDE.md

3028 lines
90 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)
│ ├── ReduxProviderstore 来自 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错误隔离
- 通过 `<Outlet />` 渲染子路由内容
- 布局内可访问认证状态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 (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
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+ 个组件中重复
- 组件逻辑可以独立测试
- 需要封装浏览器 APIlocalStorage, 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';
<img src={logo} alt="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/<stock_code>', 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/<task_id>', 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
# 启动 FlowerCelery 监控工具)
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`(日线数据)<br>`stock_minute`(分钟数据)<br>`concept_daily`(概念板块数据) |
| **MySQL/PostgreSQL** | 事务数据存储 | OLTPACID 保证、关系型) | `users`(用户表)<br>`orders`(订单表)<br>`portfolios`(持仓表)<br>`subscriptions`(订阅表) |
| **Redis** | 缓存 + 消息队列 | 内存数据库(高速读写) | 股票行情缓存<br>用户 Session<br>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`
**开发服务器:**
- 端口 3000prestart 时杀死现有进程)
- 代理(当 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 <Spinner />;
if (error) return <Alert status="error">{error}</Alert>;
return (
<Box>
<Text>总市值:¥{totalValue}</Text>
{holdings.map(h => (
<Text key={h.stockCode}>
{h.stockCode} - {h.shares}
</Text>
))}
<Button onClick={handleAddStock}>添加持仓</Button>
</Box>
);
};
```
#### 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 (
<Box p={8}>
<Heading mb={6}>我的投资组合</Heading>
<SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} spacing={6}>
{/* 组件内容 */}
</SimpleGrid>
</Box>
);
};
```
```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 (
<Box>
<Text>股票代码{stockCode}</Text>
<Text>当前价格{quote?.price || '--'}</Text>
<Text>涨跌幅{quote?.change || '--'}%</Text>
</Box>
);
};
```
**使用自定义 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 (
<Box>
<Badge colorScheme={connected ? 'green' : 'red'}>
{connected ? '已连接' : '未连接'}
</Badge>
<Text>价格{quote?.price}</Text>
</Box>
);
};
```
#### 5. 如何调试 API 请求Mock vs Real
**切换 Mock/Real 模式**
```bash
# ✅ Mock 模式MSW 拦截所有请求)
npm start
# 使用 .env.mockREACT_APP_ENABLE_MOCK=true
# ✅ 真实后端模式
npm run start:real
# 使用 .env.localREACT_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 (
<ChakraProvider theme={chakraTheme}>
<ConfigProvider theme={antdCustomTheme}>
{children}
</ConfigProvider>
</ChakraProvider>
);
}
```
**2. 布局策略**
外层使用 Chakra UI 布局,内层嵌入 Ant Design 数据组件:
```jsx
// ✅ 推荐Chakra UI 容器 + Ant Design 数据
import { Box, Heading } from '@chakra-ui/react';
import { Table } from 'antd';
<Box p={6} bg={useColorModeValue('white', 'gray.800')} borderRadius="lg" boxShadow="md">
<Heading size="md" mb={4}>交易历史</Heading> {/* Chakra 标题 */}
<Table {/* Ant Design 表格 */}
columns={columns}
dataSource={data}
pagination={{ pageSize: 50 }}
/>
</Box>
// ❌ 避免:全用 Ant Design 做布局(失去主题控制)
<Card title="交易历史"> {/* 无法使用 useColorModeValue */}
<Table ... />
</Card>
```
**3. 样式冲突处理**
如果出现样式冲突,使用 Chakra UI 的 `sx` prop 覆盖 Ant Design 样式:
```jsx
import { Box, useColorModeValue } from '@chakra-ui/react';
import { Table } from 'antd';
<Box
sx={{
// 覆盖 Ant Design Table 的默认样式
'.ant-table': {
backgroundColor: 'transparent',
color: useColorModeValue('gray.800', 'white'),
},
'.ant-table-thead > 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'),
},
}}
>
<Table columns={columns} dataSource={data} />
</Box>
```
**4. 图标统一**
优先使用 `lucide-react`(已安装),减少对多个图标库的依赖:
```jsx
// ✅ 推荐lucide-react体积小、一致性好
import { Star, TrendingUp, Calendar } from 'lucide-react';
import { Icon } from '@chakra-ui/react';
<Icon as={Star} boxSize={5} color="yellow.400" />
<Icon as={TrendingUp} boxSize={5} color="green.500" />
// ⚠️ 仅在 Ant Design 组件内部使用 @ant-design/icons
import { StarFilled, ArrowUpOutlined } from '@ant-design/icons';
<Table
columns={[
{
title: '自选',
render: () => <StarFilled style={{ color: '#faad14' }} />
}
]}
/>
```
#### 实际应用案例
**案例 1: 交易面板**
```jsx
// 布局: Chakra UI (Box, Stack, SimpleGrid)
// 表单: Ant Design (Form, InputNumber)
// 按钮: Chakra UI (Button)
// 统计: Chakra UI (Stat)
<Box p={6} bg={cardBg} borderRadius="lg">
<SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} spacing={6} mb={6}>
{/* Chakra UI 统计卡片 */}
<Stat>
<StatLabel>账户余额</StatLabel>
<StatNumber>¥123,456.78</StatNumber>
<StatHelpText>
<StatArrow type="increase" />
+23.45%
</StatHelpText>
</Stat>
</SimpleGrid>
{/* Ant Design 表单 */}
<Form layout="vertical">
<Form.Item label="交易数量">
<InputNumber
step={100}
formatter={value => `${value} 股`}
parser={value => value.replace(' 股', '')}
/>
</Form.Item>
</Form>
{/* Chakra UI 按钮 */}
<Stack direction="row" spacing={4}>
<Button colorScheme="blue" size="lg">买入</Button>
<Button colorScheme="red" size="lg">卖出</Button>
</Stack>
</Box>
```
**案例 2: 板块详情(涨停分析)**
```jsx
// 容器: Chakra UI (Card, Box)
// 折叠面板: Ant Design (Collapse) - 数据量大
// 表格: Ant Design (Table) - 需要虚拟滚动
// 标签: Chakra UI (Badge, Tag)
<Box p={6}>
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={4} mb={6}>
{/* Chakra UI 统计卡片 */}
<Card>
<CardBody>
<Stat>
<StatLabel>涨停股票数</StatLabel>
<StatNumber>156</StatNumber>
</Stat>
</CardBody>
</Card>
</SimpleGrid>
{/* Ant Design 折叠面板 + 表格 */}
<Collapse>
{sectors.map(sector => (
<Collapse.Panel
header={
<Flex justify="space-between">
<Text>{sector.name}</Text>
<Badge colorScheme="red">{sector.count} </Badge>
</Flex>
}
>
<Table
columns={columns}
dataSource={sector.stocks}
pagination={false}
scroll={{ y: 300 }}
/>
</Collapse.Panel>
))}
</Collapse>
</Box>
```
**案例 3: 投资日历**
```jsx
// 布局: Chakra UI (SimpleGrid)
// 日历: Ant Design (Calendar) - 自定义渲染
// 抽屉: Ant Design (Drawer) - 900px宽
// 统计卡片: Chakra UI (Stat)
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={6}>
{/* Ant Design 日历 */}
<Box>
<Calendar
dateCellRender={date => {
const count = getEventCount(date);
return (
<Badge colorScheme="blue" fontSize="xs">
{count}
</Badge>
);
}}
onSelect={onDateSelect}
/>
</Box>
{/* Chakra UI 统计区域 */}
<SimpleGrid columns={2} spacing={4}>
<Stat>
<StatLabel>本月事件</StatLabel>
<StatNumber>245</StatNumber>
</Stat>
</SimpleGrid>
</SimpleGrid>
{/* Ant Design 抽屉 */}
<Drawer
width={900}
title="事件详情"
placement="right"
onClose={onClose}
open={visible}
>
{/* 内容 */}
</Drawer>
```
#### 性能优化建议
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
// 推荐: 可组合
<Card>
<CardHeader>标题</CardHeader>
<CardBody>内容</CardBody>
</Card>
// 避免: 过多的 props
<Card title="标题" body="内容" showBorder={true} theme="dark" />
```
**就近原则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 && <Suspense fallback={<Spinner />}><HeavyChart /></Suspense>}
```
#### 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 (
<Box>
<Text>{state}</Text>
</Box>
);
};
// 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 文件是一个持续更新的文档。在以下情况下应更新它:
- 添加新的架构模式或指南
- 发现新的最佳实践
- 解决或添加技术债务项
- 做出重要的技术决策
- 进行重要的代码清理或重构
所有开发人员应在入职时审查本文档,并在做出架构决策时参考它。
**文档维护指南**:
- 重要变更应记录在"项目变更历史"章节
- 技术决策应记录在"技术路径与开发指南"章节
- 开发规范更新应记录在"开发规范与最佳实践"章节