Files
vf_react/CLAUDE.md
2025-11-07 12:19:41 +08:00

87 KiB
Raw Blame History

CLAUDE.md

本文件为 Claude Code (claude.ai/code) 提供在此代码库中工作的指导说明。

📖 文档结构导航

本文档分为以下主要章节,建议按需查阅:

基础信息:

架构设计:

开发指南:

技术决策与规范:

技术债务与维护:


项目概览

混合式 React 仪表板,用于金融/交易分析,采用 Flask 后端。基于 Argon Dashboard Chakra PRO 模板构建。

技术栈

前端

  • React 18.3.1 + Chakra UI 2.8.2 + Ant Design
  • Redux Toolkit 用于状态管理
  • React Router v6 配合 React.lazy() 实现代码分割
  • CRACO 构建系统,具有激进的 webpack 优化
  • 图表库: ApexCharts、ECharts、Recharts、D3
  • 其他: Three.js、FullCalendar、Leaflet 地图

后端

  • Flask + SQLAlchemy ORM
  • ClickHouse分析型数据库+ MySQL/PostgreSQL事务型数据库
  • Flask-SocketIO 实现 WebSocket 实时更新
  • Celery + Redis 处理后台任务
  • 腾讯云短信 + 微信支付集成

开发命令

前端开发

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

后端开发

python app.py                              # 主 Flask 服务器
python simulation_background_processor.py  # 交易模拟的后台任务处理器
pip install -r requirements.txt            # 安装 Python 依赖

部署

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 嵌套顺序(从外到内):

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 文件结构:

// 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 调用结构:

// 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 = ...)

工具函数特点:

  • 纯函数: 相同输入始终返回相同输出,无副作用
  • 可测试: 易于编写单元测试
  • 可复用: 多处使用的逻辑

示例:

// 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

示例:

// 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

主题配置示例:

// 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/                        # 自定义字体文件(如需要)

使用方式:

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 模式:

# 方式 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.jsEventCard.test.jsutils.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

使用路径别名快速导入:

// ✅ 使用别名(推荐)
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 路由示例:

# 获取股票数据
@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 事件示例:

# 客户端订阅股票行情
@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}')

交易日历加载:

# 全局变量存储交易日
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 - 高性能查询历史股票数据

任务示例:

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 任务:

# 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:

# 启动 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 示例:

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)

前端调用(通过代理):

// 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
  • 支付回调: 接收微信支付结果通知
  • 订单查询: 查询订单支付状态
  • 退款处理: 发起退款请求

示例代码:

# 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 事务数据存储 OLTPACID 保证、关系型) users(用户表)
orders(订单表)
portfolios(持仓表)
subscriptions(订阅表)
Redis 缓存 + 消息队列 内存数据库(高速读写) 股票行情缓存
用户 Session
Celery 任务队列

ClickHouse 使用场景:

# 查询某只股票的历史数据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 使用场景:

# 创建订单(事务保证)
@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 设计规范:

# ✅ 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. 错误处理:

# 统一错误处理
@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. 数据库连接管理:

# 使用连接池
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 (时序数据)

启动命令:

# 启动 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. Handlerssrc/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 禁用时):/apihttp://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 兼容性:

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. 如果使用 MSWsrc/mocks/handlers/{domain}.js 中添加 handler
  3. src/mocks/handlers/index.js 中导入 handler

Redux 状态管理

现有 slicessrc/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 中导入并导出

完整示例

// ✅ 步骤 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' });
  }),
];
// ✅ 步骤 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

# 启动 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. 在组件中使用

完整示例

// ✅ 步骤 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;
// ✅ 步骤 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
  },
});
// ✅ 步骤 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 添加路由配置

完整示例

// ✅ 步骤 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>
  );
};
// ✅ 步骤 2: 在 src/routes/lazy-components.js 添加
import { lazy } from 'react';

export const lazyComponents = {
  // ... 现有组件
  Community: lazy(() => import('@views/Community')),
  Portfolio: lazy(() => import('@views/Portfolio')),  // 新增
};
// ✅ 步骤 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: '查看我的投资组合和持仓',
    },
  },
];

路由保护模式说明

// PUBLIC - 无需登录
{
  path: 'about',
  protection: PROTECTION_MODES.PUBLIC,
  // 任何人都可以访问
}

// MODAL - 未登录时显示登录模态框,不跳转
{
  path: 'community',
  protection: PROTECTION_MODES.MODAL,
  // 未登录用户可以看页面,点击某些操作会弹出登录框
}

// REDIRECT - 未登录时跳转到登录页
{
  path: 'portfolio',
  protection: PROTECTION_MODES.REDIRECT,
  // 未登录用户会被重定向到 /auth/sign-in
}

添加嵌套路由(子路由):

// 父路由
{
  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. 在组件中订阅事件

完整示例

// ✅ 步骤 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();
// ✅ 步骤 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 封装

// 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 模式

# ✅ 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)

调试技巧

// 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
  2. 打开浏览器控制台 → Redux 标签页
  3. 查看 Actions、State Diff、State Tree

使用 React DevTools 调试组件

  1. 安装 Chrome 插件:React Developer Tools
  2. 打开浏览器控制台 → Components 标签页
  3. 查看组件树、Props、Hooks 状态

使用 Network 面板调试 API

  1. 打开浏览器控制台F12→ Network 标签页
  2. 筛选 XHR/Fetch 请求
  3. 查看请求头、响应体、状态码

测试 API 错误处理

// 在 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.memouseMemouseCallback
  • 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 同步样式:

// 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 数据组件:

// ✅ 推荐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 样式:

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(已安装),减少对多个图标库的依赖:

// ✅ 推荐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: 交易面板

// 布局: 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: 板块详情(涨停分析)

// 容器: 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: 投资日历

// 布局: 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 按需导入组件
    // ✅ 推荐:按需导入
    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

监控命令:

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、工具函数或子组件中

组合优于配置:

// 推荐: 可组合
<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 可视化)
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 服务模板:

// 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 模板:

// 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 错误处理模式:

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.jscamelCase.js (如 API_ENDPOINTS.js, animations.js)
  • Hooks: useCamelCase.js (如 useStockData.js)

组件文件结构:

// 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 使用 ishasshould 前缀(如 isLoadinghasError
  • 回调函数使用 onEventName 格式(如 onClickonSubmit
  • 避免传递整个对象,只传递需要的几个属性

性能优化检查清单:

  • 昂贵的计算是否用 useMemo 包裹?
  • 传递给子组件的回调函数是否用 useCallback 包裹?
  • 列表项是否使用唯一且稳定的 key props不是数组索引
  • 大型列表是否虚拟化react-window 或 react-virtuoso
  • 频繁接收 prop 更新的组件是否用 React.memo 包裹?
  • 图片是否懒加载(loading="lazy"

2. Hooks 开发规范

自定义 Hook 规则:

  • 必须以 use 前缀开头
  • 应该在多个组件间可复用
  • 应该处理一个特定关注点(数据获取、表单状态等)
  • 应该返回对象或数组(不直接返回原始值)

自定义 Hook 模板:

// 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 查询: 大日期范围的某些分析查询可能较慢;考虑分页或聚合

更新本文档

本 CLAUDE.md 文件是一个持续更新的文档。在以下情况下应更新它:

  • 添加新的架构模式或指南
  • 发现新的最佳实践
  • 解决或添加技术债务项
  • 做出重要的技术决策

所有开发人员应在入职时审查本文档,并在做出架构决策时参考它。