From e80227840ab0dd6dcd8528003d6d621c3fdce6c1 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 7 Nov 2025 12:07:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=85md=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 2171 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 2121 insertions(+), 50 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e91100a0..dc49e559 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,36 @@ 本文件为 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 模板构建。 @@ -89,47 +119,1119 @@ src/index.js - `MODAL` - 未登录时显示认证模态框 - `REDIRECT` - 未登录时重定向到 /auth/sign-in -### 前端目录结构 +### 前端目录结构详解 + +本项目采用**功能驱动的目录结构**,按职责将代码组织到不同目录中。以下是完整的目录结构及详细说明: + +#### 核心目录概览 + ``` src/ -├── App.js - 根组件(providers + 路由) -├── providers/ - Provider 组合(AppProviders.js) -├── routes/ - 模块化路由系统(见上文) -├── layouts/ - 页面布局(MainLayout、Auth) -├── views/ - 页面组件(Community、TradingSimulation 等) -├── components/ - 可复用 UI 组件 -├── contexts/ - React contexts(Auth、Notification、Sidebar) -├── store/ - Redux store + slices(auth、posthog、stock、industry 等) -├── services/ - API 层(axios 封装) -├── utils/ - 工具函数(apiConfig、priceFormatters、logger) -├── constants/ - 应用常量(animations 等) -├── hooks/ - 自定义 React hooks -├── theme/ - Chakra UI 主题定制 -└── mocks/ - 开发用 MSW handlers - ├── handlers/ - 按领域划分的请求处理器(auth、stock、company 等) - ├── data/ - Mock 数据文件 - └── browser.js - MSW 设置(当 REACT_APP_ENABLE_MOCK=true 时启动) +├── 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/ # 可配置变量 ``` -### 后端结构 -``` -app.py - Flask 服务器(路由、认证、业务逻辑) -simulation_background_processor.py - 交易模拟的 Celery worker -concept_api.py - 概念/行业分析 API -wechat_pay.py / wechat_pay_config.py - 微信支付集成 -tdays.csv - 交易日历(启动时加载) -requirements.txt - Python 依赖 +--- + +#### 详细目录说明 + +##### 📁 `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. 认证系统(最内层) ``` -### 状态管理策略 -- **Redux Toolkit**: 全局状态(认证模态框、posthog、股票数据、行业数据、订阅、社区数据) -- **React Context**: 跨切面关注点(AuthContext、NotificationContext、SidebarContext) -- **组件状态**: 本地 UI 状态(表单、开关等) +**何时修改**: +- 添加新的全局 Provider(如 i18n、Analytics) +- 调整 Provider 顺序(注意依赖关系) -### 实时更新 -- Flask-SocketIO 用于 WebSocket 连接 -- 示例:社区事件通知通过 WebSocket 推送 -- 客户端:`socket.io-client` 库 +--- + +##### 📁 `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) + +--- ## 配置 @@ -271,25 +1373,617 @@ src/views/Community/components/ 2. 在 `src/store/index.js` 中导入并添加 3. 通过 `useSelector` 访问,通过 `useDispatch` 派发 -## 后端架构 +--- -### Flask 应用 (app.py) -- **认证**: Flask-Login + session 管理 -- **数据库**: SQLAlchemy 模型 + ClickHouse 客户端 -- **后台任务**: Celery 任务用于异步处理 -- **实时通信**: Flask-SocketIO 用于 WebSocket 事件 -- **交易日**: `tdays.csv` 在启动时加载到内存(全局 `trading_days` 变量) +### 常见开发任务 -### 后端关键模式 -- **ClickHouse**: 用于高容量分析查询(股票数据、时间序列) -- **MySQL/PostgreSQL**: 用于事务性数据(用户、订单、订阅) -- **Celery**: 后台处理器在单独进程中运行(`simulation_background_processor.py`) -- **CORS**: 已启用,用于前端通信 +本节提供常见开发任务的详细步骤和代码示例,帮助快速上手项目开发。 -### API 代理配置 -非 mock 模式时,前端代理到后端: -- `/api` → `http://49.232.185.254:5001`(主 API) -- `/concept-api` → `http://49.232.185.254:6801`(概念分析 API) +#### 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'); +} +``` + +--- ## 重要说明 @@ -321,6 +2015,383 @@ src/views/Community/components/ 本章节定义了项目的技术方向、架构原则和开发标准。所有开发工作都应遵循这些指南。 +### 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) + +--- + ### 技术栈决策与演进路线 #### 当前技术选型及理由