Files
vf_react/docs/CENTER_PAGE_FLOW_ANALYSIS.md
zdl 09db05c448 docs: 将所有文档迁移到 docs/ 目录
- 移动42个文档文件到 docs/ 目录
  - 更新 .gitignore 允许 docs/ 下的 .md 文件
  - 删除根目录下的重复文档文件

  📁 文档分类:
  - StockDetailPanel 重构文档(3个)
  - PostHog 集成文档(6个)
  - 系统架构和API文档(33个)

  🤖 Generated with [Claude Code](https://claude.com/claude-code)

  Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 14:51:22 +08:00

72 KiB
Raw Blame History

个人中心页面业务流程分析文档

文档创建日期: 2025-01-19 分析范围: /home/center 个人中心页面 相关文件:

  • src/views/Dashboard/Center.js (主组件)
  • src/views/Dashboard/components/InvestmentCalendarChakra.js (日历组件)
  • src/views/Dashboard/components/InvestmentPlansAndReviews.js (计划复盘组件)

📋 目录


一、页面组件结构

CenterDashboard (主组件 - src/views/Dashboard/Center.js)
├── 头部区域
│   ├── 标题: "个人中心"
│   └── 刷新按钮 (onClick={loadData})
├── 统计卡片区域 (SimpleGrid 4列)
│   ├── 自选股票数量 (watchlist.length)
│   ├── 关注事件数量 (followingEvents.length)
│   ├── 我的评论数量 (eventComments.length)
│   └── 订阅状态 (可点击跳转到订阅管理页)
├── InvestmentCalendarChakra (投资日历组件)
│   ├── FullCalendar 月视图
│   ├── 添加计划按钮
│   ├── 事件详情 Modal
│   └── 添加计划 Modal
├── 主内容区域 (Grid 布局: 左侧1列 + 右侧2列)
│   ├── 左侧栏
│   │   ├── 自选股票卡片
│   │   │   ├── 刷新行情按钮 (onClick={loadRealtimeQuotes})
│   │   │   ├── 添加自选股按钮 (跳转到股票搜索页)
│   │   │   └── 股票列表 (最多显示10只)
│   │   │       ├── 股票名称/代码
│   │   │       ├── 当前价格
│   │   │       └── 涨跌幅 (红涨绿跌)
│   │   └── 订阅管理卡片
│   │       ├── 当前套餐显示
│   │       ├── 剩余天数
│   │       └── 升级/管理按钮
│   └── 右侧栏
│       ├── 关注事件卡片
│       │   ├── 事件列表 (最多显示5个)
│       │   │   ├── 事件标题 (可点击跳转到详情)
│       │   │   ├── 事件标签
│       │   │   ├── 统计数据 (浏览/评论/点赞)
│       │   │   └── 热度分数
│       │   └── 查看更多按钮
│       └── 我的评论卡片
│           ├── 评论列表 (最多显示5条)
│           │   ├── 评论内容
│           │   ├── 发布时间
│           │   └── 所属事件标题
│           └── 评论总数提示
└── InvestmentPlansAndReviews (投资计划与复盘组件)
    ├── Tab 1: 我的计划 (plans.length)
    │   ├── 新建计划按钮
    │   └── 计划卡片列表 (Grid 2列)
    │       ├── 计划标题
    │       ├── 日期
    │       ├── 状态 (进行中/已完成/已取消)
    │       ├── 内容摘要
    │       ├── 相关股票标签
    │       ├── 自定义标签
    │       ├── 编辑按钮
    │       └── 删除按钮
    └── Tab 2: 我的复盘 (reviews.length)
        ├── 新建复盘按钮
        └── 复盘卡片列表 (Grid 2列)
            └── (结构同计划卡片)

二、组件初始化和挂载流程

2.1 State 初始化

// src/views/Dashboard/Center.js:80-87
const [watchlist, setWatchlist] = useState([]);              // 自选股列表
const [realtimeQuotes, setRealtimeQuotes] = useState({});    // 实时行情 Map {stock_code: quote_data}
const [followingEvents, setFollowingEvents] = useState([]);  // 关注的事件列表
const [eventComments, setEventComments] = useState([]);      // 我的评论列表
const [subscriptionInfo, setSubscriptionInfo] = useState({   // 订阅信息
  type: 'free',        // 订阅类型: free/pro/max
  status: 'active',    // 订阅状态: active/expired
  days_left: 999,      // 剩余天数
  is_active: true      // 是否激活
});
const [loading, setLoading] = useState(true);                // 初次加载状态
const [refreshing, setRefreshing] = useState(false);         // 刷新状态
const [quotesLoading, setQuotesLoading] = useState(false);   // 行情加载状态

2.2 组件挂载钩子 (useEffect)

// src/views/Dashboard/Center.js:198-210
useEffect(() => {
    // 触发条件 1: 用户已登录
    // 触发条件 2: 当前路径包含 '/home/center'
    if (user && location.pathname.includes('/home/center')) {
        loadData();  // 🌐 触发主数据加载
    }

    // 监听页面可见性变化 (从后台切回前台时自动刷新)
    const onVis = () => {
        if (document.visibilityState === 'visible' &&
            location.pathname.includes('/home/center')) {
            loadData();  // 🌐 页面重新可见时刷新数据
        }
    };

    document.addEventListener('visibilitychange', onVis);
    return () => document.removeEventListener('visibilitychange', onVis);
}, [user, location.pathname, loadData]);

触发时机:

  1. 组件首次挂载 (如果用户已登录且路径正确)
  2. 用户从其他页面导航到个人中心
  3. 浏览器标签页从后台切换回前台

三、API 请求时间线和数据流

Phase 1: 主数据并行加载 (loadData)

函数位置: src/views/Dashboard/Center.js:89-124

触发时机:

  • 组件挂载
  • 用户点击"刷新数据"按钮
  • 页面从后台切回前台

执行流程:

const loadData = useCallback(async () => {
    try {
        setRefreshing(true);  // 🔄 显示刷新状态

        const base = process.env.NODE_ENV === 'production'
            ? ''
            : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');

        const ts = Date.now();  // 时间戳防止缓存

        // 🌐 并行发送 4 个 GET 请求
        const [w, e, c, s] = await Promise.all([
            fetch(base + `/api/account/watchlist?_=${ts}`, {
                credentials: 'include',
                cache: 'no-store',
                headers: { 'Cache-Control': 'no-cache' }
            }),
            fetch(base + `/api/account/events/following?_=${ts}`, {
                credentials: 'include',
                cache: 'no-store',
                headers: { 'Cache-Control': 'no-cache' }
            }),
            fetch(base + `/api/account/events/comments?_=${ts}`, {
                credentials: 'include',
                cache: 'no-store',
                headers: { 'Cache-Control': 'no-cache' }
            }),
            fetch(base + `/api/subscription/current?_=${ts}`, {
                credentials: 'include',
                cache: 'no-store',
                headers: { 'Cache-Control': 'no-cache' }
            }),
        ]);

        // 解析 JSON 响应
        const jw = await w.json();  // 自选股数据
        const je = await e.json();  // 关注事件数据
        const jc = await c.json();  // 评论数据
        const js = await s.json();  // 订阅信息

        // 更新状态
        if (jw.success) {
            setWatchlist(Array.isArray(jw.data) ? jw.data : []);

            // 🔗 如果有自选股,触发实时行情加载
            if (jw.data && jw.data.length > 0) {
                loadRealtimeQuotes();  // 🌐 Phase 2
            }
        }

        if (je.success) setFollowingEvents(Array.isArray(je.data) ? je.data : []);
        if (jc.success) setEventComments(Array.isArray(jc.data) ? jc.data : []);
        if (js.success) setSubscriptionInfo(js.data);

    } catch (err) {
        logger.error('Center', 'loadData', err, {
            userId: user?.id,
            timestamp: new Date().toISOString()
        });
    } finally {
        setLoading(false);     // 🔄 初次加载完成
        setRefreshing(false);  // 🔄 刷新状态重置
    }
}, [user]);

API 请求详情 (Phase 1):

序号 API Endpoint 响应数据结构
API 1 GET /api/account/watchlist { success: true, data: [{ id: 1, stock_code: "600519", stock_name: "贵州茅台", current_price: 1738.50, change_percent: 2.15, industry: "白酒" }] }
API 2 GET /api/account/events/following { success: true, data: [{ id: 101, title: "茅台发布Q3财报", tags: ["财报", "消费"], view_count: 1520, comment_count: 48, upvote_count: 256, heat_score: 85, creator: {...} }] }
API 3 GET /api/account/events/comments { success: true, data: [{ id: 1, content: "这个分析很到位", event_title: "茅台发布Q3财报", created_at: "2025-01-18T10:30:00Z" }] }
API 4 GET /api/subscription/current { success: true, data: { type: "pro", status: "active", days_left: 25, is_active: true, start_date: "2024-12-25", end_date: "2025-01-25" } }

Phase 2: 实时行情加载 (loadRealtimeQuotes)

函数位置: src/views/Dashboard/Center.js:127-155

触发时机:

  1. loadData() 检测到有自选股时自动触发
  2. 用户点击自选股区域的刷新按钮 (🔄 图标)
  3. 定时器每 60 秒自动触发 (Phase 3)

执行流程:

const loadRealtimeQuotes = useCallback(async () => {
    try {
        setQuotesLoading(true);  // 🔄 行情加载中 (显示小 Spinner)

        const base = process.env.NODE_ENV === 'production'
            ? ''
            : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');

        // 🌐 API 5: 获取实时行情
        const response = await fetch(base + '/api/account/watchlist/realtime', {
            credentials: 'include',
            cache: 'no-store',
            headers: { 'Cache-Control': 'no-cache' }
        });

        if (response.ok) {
            const data = await response.json();
            if (data.success) {
                // 转换数组为 Map 结构: { stock_code: quote_data }
                const quotesMap = {};
                data.data.forEach(item => {
                    quotesMap[item.stock_code] = item;
                });
                setRealtimeQuotes(quotesMap);
            }
        }
    } catch (error) {
        logger.error('Center', 'loadRealtimeQuotes', error, {
            userId: user?.id,
            watchlistLength: watchlist.length
        });
    } finally {
        setQuotesLoading(false);  // 🔄 行情加载完成
    }
}, []);

API 请求详情 (Phase 2):

序号 API Endpoint 响应数据结构
API 5 GET /api/account/watchlist/realtime { success: true, data: [{ stock_code: "600519", current_price: 1738.50, change_percent: 2.15, change_amount: 36.50, update_time: "15:00:00", volume: 1250000, turnover: 217500000 }] }

数据合并逻辑:

// UI 渲染时优先使用实时行情数据
const displayPrice = realtimeQuotes[stock.stock_code]?.current_price
                     || stock.current_price
                     || '--';

const displayChange = realtimeQuotes[stock.stock_code]?.change_percent
                      || stock.change_percent
                      || null;

Phase 3: 定时刷新机制

函数位置: src/views/Dashboard/Center.js:213-221

useEffect(() => {
    // 只在有自选股时启动定时器
    if (watchlist.length > 0) {
        const interval = setInterval(() => {
            loadRealtimeQuotes();  // 🌐 每 60 秒刷新实时行情
        }, 60000);  // 60000ms = 60秒

        return () => clearInterval(interval);  // 组件卸载时清理定时器
    }
}, [watchlist.length, loadRealtimeQuotes]);

定时器特性:

  • 仅在有自选股时启动
  • 自选股数量变化时重新创建定时器
  • 组件卸载时自动清理
  • 页面在后台时仍然运行 (但 visibilitychange 会在切回时刷新全部数据)

四、子组件的 API 请求

4.1 InvestmentCalendarChakra (投资日历组件)

文件位置: src/views/Dashboard/components/InvestmentCalendarChakra.js

4.1.1 自动加载事件数据

// InvestmentCalendarChakra.js:86-125
const loadEvents = useCallback(async () => {
    try {
        setLoading(true);
        const base = process.env.NODE_ENV === 'production'
            ? ''
            : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');

        // 🌐 API 6: 获取日历事件
        const userResponse = await fetch(base + '/api/account/calendar/events', {
            credentials: 'include'
        });

        if (userResponse.ok) {
            const userData = await userResponse.json();
            if (userData.success) {
                // 转换为 FullCalendar 事件格式
                const allEvents = (userData.data || []).map(event => ({
                    ...event,
                    id: `${event.source || 'user'}-${event.id}`,
                    title: event.title,
                    start: event.event_date,
                    date: event.event_date,
                    // 系统事件 (未来事件) 蓝色,用户计划紫色
                    backgroundColor: event.source === 'future' ? '#3182CE' : '#8B5CF6',
                    borderColor: event.source === 'future' ? '#3182CE' : '#8B5CF6',
                    extendedProps: {
                        ...event,
                        isSystem: event.source === 'future',
                    }
                }));

                setEvents(allEvents);
                logger.debug('InvestmentCalendar', '日历事件加载成功', {
                    count: allEvents.length
                });
            }
        }
    } catch (error) {
        logger.error('InvestmentCalendar', 'loadEvents', error);
    } finally {
        setLoading(false);
    }
}, []);

useEffect(() => {
    loadEvents();  // 组件挂载时自动加载
}, [loadEvents]);

API 6 响应数据结构:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "title": "关注半导体板块",
      "description": "重点关注龙头股走势",
      "event_date": "2025-10-20",
      "type": "plan",
      "importance": 4,
      "stocks": ["002415", "600584"],
      "source": "user",
      "created_at": "2025-01-18T10:00:00Z"
    },
    {
      "id": 201,
      "title": "贵州茅台发布Q4财报",
      "event_date": "2025-10-25",
      "source": "future",
      "importance": 5,
      "stocks": ["600519"]
    }
  ]
}

4.1.2 用户交互 API

添加投资计划 (API 7):

// InvestmentCalendarChakra.js:169-223
const handleAddEvent = async () => {
    try {
        const base = process.env.NODE_ENV === 'production'
            ? ''
            : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');

        const eventData = {
            ...newEvent,
            event_date: selectedDate ? selectedDate.format('YYYY-MM-DD') : moment().format('YYYY-MM-DD'),
            stocks: newEvent.stocks.split(',').map(s => s.trim()).filter(s => s),
        };

        // 🌐 API 7: POST 创建投资计划
        const response = await fetch(base + '/api/account/calendar/events', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            credentials: 'include',
            body: JSON.stringify(eventData),
        });

        if (response.ok) {
            const data = await response.json();
            if (data.success) {
                toast({ title: '添加成功', status: 'success', duration: 3000 });
                onAddClose();        // 关闭添加弹窗
                loadEvents();        // 重新加载日历事件
                setNewEvent({...});  // 重置表单
            }
        }
    } catch (error) {
        logger.error('InvestmentCalendar', 'handleAddEvent', error);
        toast({ title: '添加失败', status: 'error', duration: 3000 });
    }
};

请求体示例:

{
  "title": "关注半导体板块",
  "description": "重点关注龙头股走势",
  "event_date": "2025-10-20",
  "type": "plan",
  "importance": 4,
  "stocks": ["002415", "600584"]
}

删除事件 (API 8):

// InvestmentCalendarChakra.js:226-262
const handleDeleteEvent = async (eventId) => {
    if (!eventId) {
        toast({ title: '无法删除', description: '缺少事件 ID', status: 'error' });
        return;
    }

    try {
        const base = process.env.NODE_ENV === 'production'
            ? ''
            : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');

        // 🌐 API 8: DELETE 删除事件
        const response = await fetch(base + `/api/account/calendar/events/${eventId}`, {
            method: 'DELETE',
            credentials: 'include',
        });

        if (response.ok) {
            toast({ title: '删除成功', status: 'success', duration: 2000 });
            loadEvents();  // 重新加载日历事件
        }
    } catch (error) {
        logger.error('InvestmentCalendar', 'handleDeleteEvent', error);
        toast({ title: '删除失败', status: 'error', duration: 3000 });
    }
};

4.2 InvestmentPlansAndReviews (计划和复盘组件)

文件位置: src/views/Dashboard/components/InvestmentPlansAndReviews.js

4.2.1 自动加载数据

// InvestmentPlansAndReviews.js:97-124
const loadData = useCallback(async () => {
    try {
        setLoading(true);
        const base = process.env.NODE_ENV === 'production'
            ? ''
            : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');

        // 🌐 API 9: 获取投资计划和复盘
        const response = await fetch(base + '/api/account/investment-plans', {
            credentials: 'include'
        });

        if (response.ok) {
            const data = await response.json();
            if (data.success) {
                const allItems = data.data || [];
                // 按类型分类
                setPlans(allItems.filter(item => item.type === 'plan'));
                setReviews(allItems.filter(item => item.type === 'review'));

                logger.debug('InvestmentPlansAndReviews', '数据加载成功', {
                    plansCount: allItems.filter(item => item.type === 'plan').length,
                    reviewsCount: allItems.filter(item => item.type === 'review').length
                });
            }
        }
    } catch (error) {
        logger.error('InvestmentPlansAndReviews', 'loadData', error);
    } finally {
        setLoading(false);
    }
}, []);

useEffect(() => {
    loadData();  // 组件挂载时自动加载
}, [loadData]);

API 9 响应数据结构:

{
  "success": true,
  "data": [
    {
      "id": 1,
      "date": "2025-01-15",
      "title": "布局新能源板块",
      "content": "重点关注宁德时代和比亚迪...",
      "type": "plan",
      "stocks": ["300750", "002594"],
      "tags": ["新能源", "长期"],
      "status": "active",
      "created_at": "2025-01-15T09:00:00Z",
      "updated_at": "2025-01-15T09:00:00Z"
    },
    {
      "id": 2,
      "date": "2025-01-12",
      "title": "本周交易复盘",
      "content": "本周操作了3只股票总体盈利2.5%...",
      "type": "review",
      "stocks": ["600519", "000858"],
      "tags": ["周复盘", "A股"],
      "status": "completed",
      "created_at": "2025-01-12T20:00:00Z",
      "updated_at": "2025-01-12T20:00:00Z"
    }
  ]
}

4.2.2 用户交互 API

创建计划/复盘 (API 10):

// InvestmentPlansAndReviews.js:154-201
const handleSave = async () => {
    try {
        const base = process.env.NODE_ENV === 'production'
            ? ''
            : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');

        const url = editingItem
            ? base + `/api/account/investment-plans/${editingItem.id}`  // 更新
            : base + '/api/account/investment-plans';                   // 创建

        const method = editingItem ? 'PUT' : 'POST';

        // 🌐 API 10 (POST) 或 API 11 (PUT)
        const response = await fetch(url, {
            method,
            headers: { 'Content-Type': 'application/json' },
            credentials: 'include',
            body: JSON.stringify(formData),
        });

        if (response.ok) {
            toast({
                title: editingItem ? '更新成功' : '创建成功',
                status: 'success',
                duration: 2000,
            });
            onClose();    // 关闭编辑弹窗
            loadData();   // 重新加载数据
        } else {
            throw new Error('保存失败');
        }
    } catch (error) {
        logger.error('InvestmentPlansAndReviews', 'handleSave', error);
        toast({ title: '保存失败', status: 'error', duration: 3000 });
    }
};

请求体示例 (创建计划):

{
  "date": "2025-01-20",
  "title": "布局新能源板块",
  "content": "重点关注宁德时代和比亚迪的估值变化...",
  "type": "plan",
  "stocks": ["300750", "002594"],
  "tags": ["新能源", "长期"],
  "status": "active"
}

删除计划/复盘 (API 12):

// InvestmentPlansAndReviews.js:204-232
const handleDelete = async (id) => {
    if (!window.confirm('确定要删除吗?')) return;

    try {
        const base = process.env.NODE_ENV === 'production'
            ? ''
            : (process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');

        // 🌐 API 12: DELETE 删除计划/复盘
        const response = await fetch(base + `/api/account/investment-plans/${id}`, {
            method: 'DELETE',
            credentials: 'include',
        });

        if (response.ok) {
            toast({ title: '删除成功', status: 'success', duration: 2000 });
            loadData();  // 重新加载数据
        }
    } catch (error) {
        logger.error('InvestmentPlansAndReviews', 'handleDelete', error);
        toast({ title: '删除失败', status: 'error', duration: 3000 });
    }
};

五、完整 API 请求汇总表

序号 组件 API Endpoint 方法 触发时机 自动/手动 请求参数 响应数据
1 Center.js /api/account/watchlist GET 页面加载/刷新/可见 自动 - 自选股列表
2 Center.js /api/account/events/following GET 页面加载/刷新/可见 自动 - 关注事件列表
3 Center.js /api/account/events/comments GET 页面加载/刷新/可见 自动 - 我的评论列表
4 Center.js /api/subscription/current GET 页面加载/刷新/可见 自动 - 订阅信息
5 Center.js /api/account/watchlist/realtime GET 有自选股时/每60s/点击刷新 自动+手动 - 实时行情数据
6 InvestmentCalendar /api/account/calendar/events GET 组件挂载 自动 - 日历事件列表
7 InvestmentCalendar /api/account/calendar/events POST 点击"添加计划" 手动 { title, description, event_date, type, importance, stocks } { success: true, data: {...} }
8 InvestmentCalendar /api/account/calendar/events/:id DELETE 点击"删除"按钮 手动 - { success: true }
9 InvestmentPlans /api/account/investment-plans GET 组件挂载 自动 - 计划+复盘列表
10 InvestmentPlans /api/account/investment-plans POST 点击"新建计划/复盘" 手动 { date, title, content, type, stocks, tags, status } { success: true, data: {...} }
11 InvestmentPlans /api/account/investment-plans/:id PUT 点击"编辑" 手动 { date, title, content, type, stocks, tags, status } { success: true, data: {...} }
12 InvestmentPlans /api/account/investment-plans/:id DELETE 点击"删除"按钮 手动 - { success: true }

统计:

  • 自动加载 API: 5 个 (API 1-5, 6, 9)
  • 用户操作 API: 5 个 (API 7, 8, 10, 11, 12)
  • 总计: 12 个 API 端点

六、UI 状态变化和交互流程

6.1 页面加载完整流程

用户访问 /home/center
    ↓
[Component Mount]
    ├─ State 初始化
    │   ├─ loading = true
    │   ├─ watchlist = []
    │   ├─ followingEvents = []
    │   ├─ eventComments = []
    │   ├─ subscriptionInfo = { type: 'free', ... }
    │   ├─ realtimeQuotes = {}
    │   └─ refreshing = false
    ├─ 显示加载动画
    │   └─ <Spinner> + "加载个人中心数据..."
    └─ useEffect 触发条件检测
        ├─ user 存在 ✅
        └─ location.pathname 包含 '/home/center' ✅
            ↓
        loadData() 触发
            ├─ refreshing = true
            ├─ 🌐 并行发送 API 1-4
            │   ├─ GET /api/account/watchlist
            │   ├─ GET /api/account/events/following
            │   ├─ GET /api/account/events/comments
            │   └─ GET /api/subscription/current
            ├─ [等待响应 ~800ms]
            ├─ ✅ 响应成功,解析 JSON
            ├─ State 更新:
            │   ├─ setWatchlist([5只股票])
            │   ├─ setFollowingEvents([5个事件])
            │   ├─ setEventComments([5条评论])
            │   └─ setSubscriptionInfo({ type: 'pro', days_left: 25, ... })
            ├─ 检测自选股数量
            │   └─ if (watchlist.length > 0):
            │       └─ loadRealtimeQuotes()
            │           ├─ quotesLoading = true
            │           ├─ 🌐 GET /api/account/watchlist/realtime
            │           ├─ [等待响应 ~300ms]
            │           ├─ ✅ 解析行情数据
            │           ├─ setRealtimeQuotes({ "600519": {...}, ... })
            │           └─ quotesLoading = false
            ├─ refreshing = false
            └─ loading = false
                ↓
    页面内容渲染完成
        ├─ 统计卡片显示
        │   ├─ 自选股票: 5
        │   ├─ 关注事件: 5
        │   ├─ 我的评论: 5
        │   └─ 订阅状态: Pro版 (剩余25天)
        ├─ 自选股列表渲染
        │   ├─ 贵州茅台 600519  1738.50  +2.15% ↑
        │   ├─ 平安银行 000001   12.50   -0.80% ↓
        │   └─ ...
        ├─ 关注事件列表渲染
        │   └─ 显示前 5 个事件
        ├─ 我的评论列表渲染
        │   └─ 显示前 5 条评论
        └─ 启动定时器
            └─ 每 60 秒调用 loadRealtimeQuotes()
                ↓
[子组件挂载]
    ├─ InvestmentCalendarChakra 挂载
    │   ├─ 🌐 GET /api/account/calendar/events
    │   ├─ [等待响应 ~500ms]
    │   ├─ ✅ setEvents([7个事件])
    │   └─ FullCalendar 渲染完成
    │       ├─ 10月20日: 关注半导体板块 (紫色)
    │       ├─ 10月25日: 贵州茅台发布Q4财报 (蓝色)
    │       └─ ...
    └─ InvestmentPlansAndReviews 挂载
        ├─ 🌐 GET /api/account/investment-plans
        ├─ [等待响应 ~500ms]
        ├─ ✅ 数据解析
        ├─ setPlans([2个计划])
        ├─ setReviews([2个复盘])
        └─ Tab 列表渲染完成
            ├─ Tab 1: 我的计划 (2)
            │   ├─ "布局新能源板块" (进行中)
            │   └─ "关注白酒板块回调" (已完成)
            └─ Tab 2: 我的复盘 (2)
                ├─ "本周交易复盘" (已完成)
                └─ "1月投资总结" (进行中)

6.2 用户交互场景详解

场景 1: 点击"刷新数据"按钮

用户点击右上角 "刷新数据" 按钮
    ↓
onClick={loadData}
    ├─ refreshing = true
    ├─ 按钮显示 "刷新中" + 加载动画
    ├─ 按钮禁用 (isLoading={refreshing})
    ├─ 🌐 重新发送 API 1-4 (并行)
    ├─ 🌐 重新发送 API 5 (如果有自选股)
    ├─ [等待响应完成]
    ├─ State 全部更新
    │   ├─ watchlist 更新
    │   ├─ followingEvents 更新
    │   ├─ eventComments 更新
    │   ├─ subscriptionInfo 更新
    │   └─ realtimeQuotes 更新
    └─ refreshing = false
        ↓
    按钮恢复为 "刷新数据" (可点击)
    UI 显示最新数据

场景 2: 点击自选股区域的刷新按钮

用户点击自选股卡片右上角的 🔄 图标
    ↓
onClick={loadRealtimeQuotes}
    ├─ quotesLoading = true
    ├─ 自选股标题旁显示小型 Spinner
    ├─ 按钮禁用 (isLoading={quotesLoading})
    ├─ 🌐 GET /api/account/watchlist/realtime
    ├─ [等待响应 ~300ms]
    ├─ ✅ 响应成功
    ├─ setRealtimeQuotes({ ... })
    └─ quotesLoading = false
        ↓
    Spinner 消失
    实时行情数据更新
        ├─ 价格变化: 1738.50 → 1740.00
        ├─ 涨跌幅变化: +2.15% → +2.24%
        ├─ 颜色变化: 红色 (涨) / 绿色 (跌)
        └─ 更新时间: "15:00:00"

场景 3: 点击订阅状态卡片

用户点击订阅状态统计卡片
    ↓
onClick={() => navigate('/home/pages/account/subscription')}
    ↓
路由跳转到订阅管理页面
    └─ URL 变为: /home/pages/account/subscription

场景 4: 添加自选股

用户在自选股卡片中点击 "+" 按钮
    ↓
onClick={() => navigate('/stock-analysis/overview')}
    ↓
路由跳转到股票分析页面
    └─ URL 变为: /stock-analysis/overview
        ↓
用户在该页面搜索并添加股票 (例如: 五粮液 000858)
    ├─ 🌐 POST /api/account/watchlist
    └─ ✅ 添加成功
        ↓
用户返回个人中心页面 (点击导航或浏览器返回)
    ↓
visibilitychange 事件触发 (页面从后台切回前台)
    ↓
loadData() 自动刷新
    ├─ 🌐 重新获取 API 1-4
    └─ 自选股列表更新
        └─ 新增: 五粮液 000858  188.50  +1.35% ↑

场景 5: 查看关注的事件详情

用户点击关注事件卡片中的某个事件标题
    ↓
<LinkOverlay as={Link} to={`/event-detail/${event.id}`}>
    ↓
路由跳转到事件详情页面
    └─ URL 变为: /event-detail/101
        ↓
    EventDetail 页面加载
        ├─ 🌐 GET /api/events/101
        ├─ 🌐 GET /api/events/101/comments
        └─ 显示完整事件内容 + 评论列表

场景 6: 添加投资计划 (日历)

用户点击投资日历卡片的 "添加计划" 按钮
    ↓
onClick={() => {
    if (!selectedDate) setSelectedDate(moment());
    onAddOpen();
}}
    ├─ selectedDate = moment() (当前日期)
    └─ isAddOpen = true
        ↓
    打开添加计划 Modal
        ├─ 标题: "添加投资计划"
        ├─ 表单字段:
        │   ├─ 标题 (必填) - Input
        │   ├─ 描述 - Textarea
        │   ├─ 类型 - Select (计划/提醒/分析)
        │   ├─ 重要度 - Select (1-5星)
        │   └─ 相关股票 - Input (逗号分隔)
        └─ 按钮: [取消] [添加]
            ↓
用户填写表单:
    ├─ 标题: "关注半导体板块"
    ├─ 描述: "重点关注龙头股走势"
    ├─ 类型: 投资计划
    ├─ 重要度: ⭐⭐⭐⭐ (4星)
    └─ 相关股票: "002415,600584"
        ↓
点击 "添加" 按钮
    ↓
handleAddEvent()
    ├─ 验证: title 不能为空 ✅
    ├─ 构建请求体:
    │   {
    │       title: "关注半导体板块",
    │       description: "重点关注龙头股走势",
    │       event_date: "2025-10-20",
    │       type: "plan",
    │       importance: 4,
    │       stocks: ["002415", "600584"]
    │   }
    ├─ 🌐 POST /api/account/calendar/events
    ├─ [等待响应 ~500ms]
    ├─ ✅ 响应成功: { success: true, data: {...} }
    ├─ Toast 提示: "添加成功" (绿色)
    ├─ isAddOpen = false (关闭 Modal)
    ├─ loadEvents() 重新加载日历数据
    │   └─ 🌐 GET /api/account/calendar/events
    └─ 表单重置: setNewEvent({ title: '', ... })
        ↓
    日历更新
        └─ 10月20日显示紫色事件标记: "关注半导体板块"

场景 7: 点击日历日期查看事件

用户点击日历上的 10月20日
    ↓
handleDateClick(info)
    ├─ selectedDate = moment('2025-10-20')
    ├─ 筛选当天事件:
    │   selectedDateEvents = events.filter(e =>
    │       moment(e.start).isSame('2025-10-20', 'day')
    │   )
    │   └─ 结果: [
    │       { title: "关注半导体板块", ... },
    │       { title: "某公司股东大会", ... }
    │   ]
    └─ isOpen = true
        ↓
    打开事件详情 Modal
        ├─ 标题: "2025年10月20日 的事件"
        ├─ 事件列表:
        │   ├─ 事件 1: 关注半导体板块
        │   │   ├─ Badge: "我的计划" (紫色)
        │   │   ├─ 重要度: ⭐⭐⭐⭐ (4/5)
        │   │   ├─ 描述: "重点关注龙头股走势"
        │   │   ├─ 相关股票: [002415] [600584]
        │   │   └─ 删除按钮 (仅用户自己创建的可删除)
        │   └─ 事件 2: 某公司股东大会
        │       ├─ Badge: "系统事件" (蓝色)
        │       ├─ 重要度: ⭐⭐⭐⭐⭐ (5/5)
        │       └─ 无删除按钮 (系统事件不可删除)
        └─ 按钮: [关闭]
            ↓
用户点击事件 1 的删除按钮
    ↓
handleDeleteEvent(eventId)
    ├─ 验证 eventId 存在 ✅
    ├─ 🌐 DELETE /api/account/calendar/events/1
    ├─ [等待响应 ~300ms]
    ├─ ✅ 响应成功
    ├─ Toast 提示: "删除成功" (绿色)
    └─ loadEvents() 重新加载日历
        ↓
    日历更新
        └─ 10月20日只剩 1 个事件: "某公司股东大会"

场景 8: 新建投资复盘

用户切换到 "我的复盘" Tab
    ↓
Tab 切换动画
    └─ 显示复盘列表 (2条记录)
        ↓
用户点击 "新建复盘" 按钮
    ↓
handleOpenModal(null, 'review')
    ├─ editingItem = null (表示新建)
    ├─ formData = {
    │       date: moment().format('YYYY-MM-DD'),  // 今天
    │       title: '',
    │       content: '',
    │       type: 'review',  // ⚠️ 类型是 review
    │       stocks: [],
    │       tags: [],
    │       status: 'active'
    │   }
    └─ isOpen = true
        ↓
    打开编辑 Modal
        ├─ 标题: "新建复盘记录"
        ├─ 表单字段:
        │   ├─ 日期 - Input (type="date") [默认今天]
        │   ├─ 标题 - Input (必填) [placeholder: "例如:本周交易复盘"]
        │   ├─ 内容 - Textarea [placeholder: "记录您的交易心得和经验教训..."]
        │   ├─ 相关股票 - 动态添加 Tag
        │   │   └─ [Input + 添加按钮] → 显示为蓝色 Tag (可删除)
        │   ├─ 标签 - 动态添加 Tag
        │   │   └─ [Input + 添加按钮] → 显示为紫色 Tag (可删除)
        │   └─ 状态 - Select (进行中/已完成/已取消)
        └─ 按钮: [取消] [保存]
            ↓
用户填写表单:
    ├─ 日期: 2025-01-19
    ├─ 标题: "本周交易复盘"
    ├─ 内容: "本周操作了3只股票总体盈利2.5%。贵州茅台..."
    ├─ 相关股票:
    │   ├─ 输入 "600519" → 点击添加 → Tag: [📈 600519]
    │   └─ 输入 "000858" → 点击添加 → Tag: [📈 000858]
    ├─ 标签:
    │   ├─ 输入 "周复盘" → 点击添加 → Tag: [# 周复盘]
    │   └─ 输入 "A股" → 点击添加 → Tag: [# A股]
    └─ 状态: 已完成
        ↓
点击 "保存" 按钮
    ↓
handleSave()
    ├─ 验证: title ✅ && date ✅
    ├─ 构建请求:
    │   ├─ URL: /api/account/investment-plans
    │   ├─ Method: POST
    │   └─ Body: {
    │           date: "2025-01-19",
    │           title: "本周交易复盘",
    │           content: "本周操作了3只股票...",
    │           type: "review",
    │           stocks: ["600519", "000858"],
    │           tags: ["周复盘", "A股"],
    │           status: "completed"
    │       }
    ├─ 🌐 POST /api/account/investment-plans
    ├─ [等待响应 ~600ms]
    ├─ ✅ 响应成功: { success: true, data: {...} }
    ├─ Toast 提示: "创建成功" (绿色)
    ├─ isOpen = false (关闭 Modal)
    └─ loadData() 重新加载数据
        └─ 🌐 GET /api/account/investment-plans
            ↓
    复盘列表更新
        ├─ 显示新增的复盘卡片:
        │   ├─ 标题: "本周交易复盘"
        │   ├─ 日期: 2025年01月19日
        │   ├─ 状态: Badge (绿色) "已完成" ✓
        │   ├─ 内容摘要: "本周操作了3只股票..." (最多显示3行)
        │   ├─ 相关股票: [📈 600519] [📈 000858]
        │   ├─ 标签: [# 周复盘] [# A股]
        │   ├─ 编辑按钮
        │   └─ 删除按钮
        └─ 复盘 Tab 数量更新: (2) → (3)

场景 9: 编辑投资计划

用户在 "我的计划" Tab 中点击某个计划卡片的编辑按钮
    ↓
handleOpenModal(item)
    ├─ editingItem = { id: 1, title: "布局新能源板块", ... }
    ├─ formData = {
    │       ...item,  // 复制所有字段
    │       date: moment(item.date).format('YYYY-MM-DD')
    │   }
    └─ isOpen = true
        ↓
    打开编辑 Modal
        ├─ 标题: "编辑投资计划"
        ├─ 表单字段预填充:
        │   ├─ 日期: 2025-01-15
        │   ├─ 标题: "布局新能源板块"
        │   ├─ 内容: "重点关注宁德时代和比亚迪..."
        │   ├─ 相关股票: [📈 300750] [📈 002594]
        │   ├─ 标签: [# 新能源] [# 长期]
        │   └─ 状态: 进行中
        └─ 按钮: [取消] [保存]
            ↓
用户修改状态: 进行中 → 已完成
    └─ formData.status = 'completed'
        ↓
点击 "保存" 按钮
    ↓
handleSave()
    ├─ editingItem 存在 → 更新模式
    ├─ 构建请求:
    │   ├─ URL: /api/account/investment-plans/1
    │   ├─ Method: PUT
    │   └─ Body: { ...formData }
    ├─ 🌐 PUT /api/account/investment-plans/1
    ├─ [等待响应 ~500ms]
    ├─ ✅ 响应成功
    ├─ Toast 提示: "更新成功" (绿色)
    ├─ isOpen = false
    └─ loadData()
        ↓
    计划卡片更新
        └─ 状态 Badge 变化: "进行中" (蓝色) → "已完成" (绿色) ✓

场景 10: 页面从后台切回前台

用户切换到其他浏览器标签页
    └─ document.visibilityState = 'hidden'
        └─ visibilitychange 事件触发 (但不执行 loadData)
            ↓
        [用户在其他标签页停留 5 分钟]
            ↓
        期间自选股行情可能变化
            ├─ 贵州茅台: 1738.50 → 1745.20 (+0.38%)
            └─ 平安银行: 12.50 → 12.48 (-0.16%)
            ↓
用户切换回个人中心标签页
    ↓
document.visibilityState = 'visible'
    ↓
visibilitychange 事件触发
    ↓
检测条件:
    ├─ document.visibilityState === 'visible' ✅
    └─ location.pathname.includes('/home/center') ✅
        ↓
    loadData() 自动执行
        ├─ 🌐 并行刷新 API 1-4
        ├─ 🌐 刷新 API 5 (实时行情)
        └─ [等待响应 ~1秒]
            ↓
    页面数据全部更新为最新状态
        ├─ 自选股行情更新
        │   ├─ 贵州茅台: 1745.20  +0.38% ↑
        │   └─ 平安银行: 12.48   -0.16% ↓
        ├─ 关注事件数量可能变化 (新增关注)
        ├─ 我的评论数量可能变化 (新发评论)
        └─ 订阅信息更新 (剩余天数递减)

七、UI 状态变化映射表

触发事件 State 变化 UI 表现 相关 API
页面加载 loading = true 显示全屏加载动画 (Spinner + 文字) -
数据加载完成 loading = false 隐藏加载动画,显示页面内容 API 1-4
点击刷新按钮 refreshing = true 按钮文字变为"刷新中",显示加载动画,按钮禁用 API 1-4
刷新完成 refreshing = false 按钮恢复为"刷新数据",可点击 -
自选股行情刷新 quotesLoading = true 自选股标题旁显示小 Spinner刷新按钮禁用 API 5
行情刷新完成 quotesLoading = false Spinner 消失,价格/涨跌幅更新,颜色变化 -
定时器触发 (每60s) 无 State 变化 实时行情自动更新 (静默刷新) API 5
点击订阅卡片 路由变化 页面跳转到订阅管理页 (/home/pages/account/subscription) -
点击自选股"+" 路由变化 页面跳转到股票分析页 (/stock-analysis/overview) -
点击事件标题 路由变化 页面跳转到事件详情页 (/event-detail/:id) -
点击日历日期 selectedDate 更新
selectedDateEvents 更新
isOpen = true
打开事件详情 Modal显示当天所有事件 -
点击"添加计划" isAddOpen = true
selectedDate 设置
打开添加计划 Modal表单为空 -
保存计划成功 isAddOpen = false 关闭 Modal显示 Toast 提示,日历刷新显示新事件 API 7
删除日历事件 确认对话框 → events 更新 二次确认Toast 提示,日历移除该事件标记 API 8
切换到"我的复盘" Tab Tab index 变化 显示复盘列表内容,隐藏计划列表 -
点击"新建复盘" isOpen = true
editingItem = null
formData.type = 'review'
打开编辑 Modal标题为"新建复盘记录" -
保存复盘成功 isOpen = false 关闭 ModalToast 提示,列表新增卡片 API 10
点击"编辑"按钮 isOpen = true
editingItem 更新
formData 预填充
打开编辑 Modal表单显示现有数据 -
更新成功 isOpen = false 关闭 ModalToast 提示,卡片内容更新 API 11
点击"删除"按钮 确认对话框 → plans/reviews 更新 二次确认Toast 提示,列表移除该卡片 API 12
页面从后台切回 visibilitychange 事件 自动调用 loadData(),全部数据静默刷新 API 1-4
自选股数量变化 watchlist.length 变化 定时器重新创建 (有股票时启动,无股票时停止) -
添加股票标签 formData.stocks 数组新增 表单中新增蓝色 Tag带关闭按钮 -
添加自定义标签 formData.tags 数组新增 表单中新增紫色 Tag带关闭按钮 -

八、关键设计亮点

1. 性能优化

1.1 并行请求

// 使用 Promise.all() 并行加载 4 个 API减少总等待时间
const [w, e, c, s] = await Promise.all([
    fetch('/api/account/watchlist'),
    fetch('/api/account/events/following'),
    fetch('/api/account/events/comments'),
    fetch('/api/subscription/current'),
]);

优势:

  • 串行请求总耗时: ~3.2秒 (800ms × 4)
  • 并行请求总耗时: ~800ms (仅等待最慢的一个)
  • 性能提升 75%

1.2 缓存控制

// 添加时间戳和缓存控制头,确保获取最新数据
fetch(`/api/account/watchlist?_=${Date.now()}`, {
    cache: 'no-store',
    headers: { 'Cache-Control': 'no-cache' }
});

作用: 防止浏览器缓存旧数据,确保数据新鲜度

1.3 条件加载

// 只有在有自选股时才加载实时行情
if (jw.data && jw.data.length > 0) {
    loadRealtimeQuotes();
}

优势: 避免不必要的 API 请求,节省服务器资源

1.4 定时刷新优化

// 仅在有自选股时启动定时器
if (watchlist.length > 0) {
    const interval = setInterval(() => {
        loadRealtimeQuotes();  // 60秒刷新一次
    }, 60000);
    return () => clearInterval(interval);
}

优势:

  • 自选股为空时不启动定时器
  • 组件卸载时自动清理,防止内存泄漏

1.5 页面可见性检测

// 页面从后台切回前台时自动刷新数据
document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') {
        loadData();
    }
});

用户体验:

  • 用户切回标签页时看到最新数据
  • 无需手动刷新

2. 用户体验优化

2.1 多层次加载状态

const [loading, setLoading] = useState(true);           // 初次加载
const [refreshing, setRefreshing] = useState(false);    // 手动刷新
const [quotesLoading, setQuotesLoading] = useState(false);  // 行情刷新

好处:

  • 用户清楚知道哪个部分正在加载
  • 不同操作有不同的视觉反馈

2.2 响应式布局

<Grid templateColumns={{ base: '1fr', lg: '1fr 2fr' }} gap={6}>
  {/* 桌面端: 左侧 1 列 + 右侧 2 列 */}
  {/* 移动端: 单列布局 */}
</Grid>

适配:

  • 桌面端 (≥992px): 两栏布局
  • 移动端 (<992px): 单列布局

2.3 空状态引导

{watchlist.length === 0 ? (
  <Center py={8}>
    <VStack spacing={3}>
      <Icon as={FiBarChart2} boxSize={12} color="gray.300" />
      <Text color={secondaryText}>暂无自选股</Text>
      <Button onClick={() => navigate('/stock-analysis/overview')}>
        添加自选股
      </Button>
    </VStack>
  </Center>
) : (
  // 自选股列表
)}

引导:

  • 提示用户当前状态
  • 提供明确的行动按钮

2.4 实时行情颜色编码

<Badge colorScheme={changePercent > 0 ? 'red' : 'green'}>
  {changePercent > 0 ? '+' : ''}{changePercent.toFixed(2)}%
</Badge>

符合习惯:

  • 国内市场: 红涨绿跌
  • 自动添加 "+" 号

2.5 相对时间显示

const formatDate = (dateString) => {
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    if (diffDays < 1) return `${diffHours}小时前`;
    if (diffDays < 7) return `${diffDays}天前`;
    return date.toLocaleDateString('zh-CN');
};

人性化:

  • "2小时前" 比 "2025-01-19 13:30" 更直观
  • 超过 7 天显示完整日期

3. 数据一致性

3.1 单一数据源

  • 每个组件通过各自的 API 获取数据
  • 避免复杂的 props drilling
  • 数据归属清晰

3.2 自动刷新机制

// 页面可见性变化时自动刷新
if (document.visibilityState === 'visible') {
    loadData();
}

保证: 用户看到的数据始终是最新的

3.3 乐观更新

// 删除/添加操作成功后立即重新加载列表
if (response.ok) {
    toast({ title: '删除成功' });
    loadEvents();  // 立即刷新
}

优势: UI 立即反映最新状态


4. 错误处理

4.1 静默失败 (数据加载)

try {
    const data = await loadData();
} catch (err) {
    logger.error('Center', 'loadData', err);  // 仅日志记录
    // ❌ 不显示 Toast避免干扰用户
}

理由:

  • 数据加载失败通常是网络问题
  • 避免频繁弹窗影响体验

4.2 明确反馈 (用户操作)

try {
    await handleDelete(id);
    toast({ title: '删除成功', status: 'success' });
} catch (error) {
    toast({ title: '删除失败', status: 'error' });  // ✅ 明确告知
}

理由:

  • 用户主动操作需要明确结果
  • 失败时需要提示原因

4.3 数据脱敏

logger.error('Center', 'loadData', err, {
    userId: user?.id,  // ✅ 只记录 ID
    // ❌ 不记录敏感信息 (手机号、邮箱等)
    timestamp: new Date().toISOString()
});

安全: 日志中不包含用户隐私数据


5. 组件解耦

5.1 子组件独立

  • InvestmentCalendarChakra: 自管理状态 + API
  • InvestmentPlansAndReviews: 自管理状态 + API
  • 无需父组件传递数据

5.2 无 Props 传递

// ✅ 好的设计
<InvestmentCalendarChakra />

// ❌ 避免的设计
<InvestmentCalendarChakra
  events={events}
  onAdd={handleAdd}
  onDelete={handleDelete}
/>

优势:

  • 组件更独立
  • 易于维护和测试

5.3 自包含 CRUD

每个子组件都实现完整的 CRUD 功能:

  • Create: 添加新记录
  • Read: 加载数据列表
  • Update: 编辑现有记录
  • Delete: 删除记录

九、数据流向图

用户打开 /home/center
    ↓
┌─────────────────────────────────────────────────────────────┐
│ Center.js 组件挂载                                            │
├─────────────────────────────────────────────────────────────┤
│ State 初始化:                                                 │
│   - loading = true                                           │
│   - watchlist = []                                           │
│   - followingEvents = []                                     │
│   - eventComments = []                                       │
│   - subscriptionInfo = { type: 'free', ... }                 │
│   - realtimeQuotes = {}                                      │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ useEffect 触发条件检测                                         │
├─────────────────────────────────────────────────────────────┤
│ if (user && location.pathname.includes('/home/center')) {   │
│     loadData(); ✅                                            │
│ }                                                            │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ Phase 1: loadData() 并行请求                                  │
├─────────────────────────────────────────────────────────────┤
│ refreshing = true                                            │
│                                                              │
│ 🌐 Promise.all([                                             │
│     GET /api/account/watchlist,              → API 1        │
│     GET /api/account/events/following,       → API 2        │
│     GET /api/account/events/comments,        → API 3        │
│     GET /api/subscription/current            → API 4        │
│ ])                                                           │
│                                                              │
│ [等待响应 ~800ms]                                             │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ API 1 响应: 自选股列表                                         │
├─────────────────────────────────────────────────────────────┤
│ { success: true, data: [                                    │
│   { id: 1, stock_code: "600519", stock_name: "贵州茅台", ... }, │
│   { id: 2, stock_code: "000001", stock_name: "平安银行", ... }, │
│   ...                                                        │
│ ]}                                                           │
│     ↓                                                        │
│ setWatchlist([...5只股票])                                    │
│     ↓                                                        │
│ if (watchlist.length > 0) {                                 │
│     loadRealtimeQuotes(); → 触发 Phase 2                     │
│ }                                                            │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ API 2 响应: 关注事件列表                                       │
├─────────────────────────────────────────────────────────────┤
│ { success: true, data: [                                    │
│   { id: 101, title: "茅台发布Q3财报", tags: [...], ... },      │
│   ...                                                        │
│ ]}                                                           │
│     ↓                                                        │
│ setFollowingEvents([...5个事件])                              │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ API 3 响应: 我的评论列表                                       │
├─────────────────────────────────────────────────────────────┤
│ { success: true, data: [                                    │
│   { id: 1, content: "这个分析很到位", event_title: "...", ... }, │
│   ...                                                        │
│ ]}                                                           │
│     ↓                                                        │
│ setEventComments([...5条评论])                                │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ API 4 响应: 订阅信息                                           │
├─────────────────────────────────────────────────────────────┤
│ { success: true, data: {                                    │
│   type: "pro",                                               │
│   status: "active",                                          │
│   days_left: 25,                                             │
│   is_active: true                                            │
│ }}                                                           │
│     ↓                                                        │
│ setSubscriptionInfo({...})                                   │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ Phase 2: loadRealtimeQuotes()                                │
├─────────────────────────────────────────────────────────────┤
│ quotesLoading = true                                         │
│                                                              │
│ 🌐 GET /api/account/watchlist/realtime      → API 5         │
│                                                              │
│ [等待响应 ~300ms]                                             │
│                                                              │
│ { success: true, data: [                                    │
│   { stock_code: "600519", current_price: 1738.50,           │
│     change_percent: 2.15, update_time: "15:00:00" },        │
│   ...                                                        │
│ ]}                                                           │
│     ↓                                                        │
│ 转换为 Map: {                                                 │
│   "600519": { current_price: 1738.50, ... },                │
│   "000001": { current_price: 12.50, ... }                   │
│ }                                                            │
│     ↓                                                        │
│ setRealtimeQuotes({...})                                     │
│ quotesLoading = false                                        │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ Phase 1 结束                                                  │
├─────────────────────────────────────────────────────────────┤
│ refreshing = false                                           │
│ loading = false                                              │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ 主组件 UI 渲染完成                                             │
├─────────────────────────────────────────────────────────────┤
│ - 统计卡片: 自选股(5) 关注事件(5) 评论(5) 订阅(Pro 25天)          │
│ - 自选股列表: 显示 5 只股票 + 实时涨跌幅                         │
│ - 关注事件: 显示前 5 个事件                                     │
│ - 我的评论: 显示前 5 条评论                                     │
│ - 订阅管理: Pro版 剩余25天                                      │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ Phase 3: 定时刷新启动                                          │
├─────────────────────────────────────────────────────────────┤
│ useEffect(() => {                                            │
│   if (watchlist.length > 0) {                                │
│     const timer = setInterval(() => {                        │
│       loadRealtimeQuotes(); → 每 60 秒触发 API 5              │
│     }, 60000);                                               │
│   }                                                          │
│ }, [watchlist.length]);                                      │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ 子组件 1: InvestmentCalendarChakra 挂载                       │
├─────────────────────────────────────────────────────────────┤
│ useEffect(() => {                                            │
│     loadEvents();                                            │
│ }, []);                                                      │
│     ↓                                                        │
│ 🌐 GET /api/account/calendar/events         → API 6         │
│     ↓                                                        │
│ { success: true, data: [                                    │
│   { id: 1, title: "关注半导体板块", event_date: "2025-10-20", │
│     source: "user", ... },                                   │
│   { id: 201, title: "贵州茅台发布Q4财报", event_date: "...",   │
│     source: "future", ... },                                 │
│   ...                                                        │
│ ]}                                                           │
│     ↓                                                        │
│ 转换为 FullCalendar 格式:                                     │
│   - 用户事件: 紫色 (#8B5CF6)                                  │
│   - 系统事件: 蓝色 (#3182CE)                                  │
│     ↓                                                        │
│ setEvents([...7个事件])                                       │
│     ↓                                                        │
│ FullCalendar 渲染:                                           │
│   - 10月20日: 紫色标记 "关注半导体板块"                         │
│   - 10月25日: 蓝色标记 "贵州茅台发布Q4财报"                     │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ 子组件 2: InvestmentPlansAndReviews 挂载                      │
├─────────────────────────────────────────────────────────────┤
│ useEffect(() => {                                            │
│     loadData();                                              │
│ }, []);                                                      │
│     ↓                                                        │
│ 🌐 GET /api/account/investment-plans        → API 9         │
│     ↓                                                        │
│ { success: true, data: [                                    │
│   { id: 1, type: "plan", title: "布局新能源板块", ... },       │
│   { id: 2, type: "review", title: "本周交易复盘", ... },       │
│   ...                                                        │
│ ]}                                                           │
│     ↓                                                        │
│ 按类型分类:                                                   │
│   - setPlans([type==='plan' 的记录]) → 2 个计划               │
│   - setReviews([type==='review' 的记录]) → 2 个复盘          │
│     ↓                                                        │
│ Tab 列表渲染:                                                 │
│   - Tab 1: 我的计划 (2)                                       │
│   - Tab 2: 我的复盘 (2)                                       │
└─────────────────────────────────────────────────────────────┘
    ↓
┌─────────────────────────────────────────────────────────────┐
│ 所有数据加载完成,页面完全可交互                                │
└─────────────────────────────────────────────────────────────┘

十、Mock 数据对应关系

根据 src/mocks/data/account.jssrc/mocks/handlers/account.js,以下是 Mock 数据与页面显示的对应关系:

页面区域 Mock 数据变量 Mock Handler API 数据量 显示位置
自选股列表 mockWatchlist GET /api/account/watchlist 5只股票 左侧栏 - 自选股卡片
实时行情 mockRealtimeQuotes GET /api/account/watchlist/realtime 5个报价 自选股卡片 (价格叠加)
关注事件 mockFollowingEvents GET /api/account/events/following 5个事件 右侧栏 - 关注事件卡片
我的评论 mockEventComments GET /api/account/events/comments 5条评论 右侧栏 - 我的评论卡片
订阅信息 mockSubscriptionCurrent GET /api/subscription/current 1条记录 统计卡片 + 订阅管理卡片
投资日历 mockCalendarEvents GET /api/account/calendar/events 7个事件 投资日历组件 (FullCalendar)
投资计划 mockInvestmentPlans (type='plan') GET /api/account/investment-plans 2个计划 计划复盘组件 - Tab 1
投资复盘 mockInvestmentPlans (type='review') GET /api/account/investment-plans 2个复盘 计划复盘组件 - Tab 2

Mock 数据示例

mockWatchlist (5只股票)

[
  { id: 1, stock_code: "600519", stock_name: "贵州茅台", industry: "白酒" },
  { id: 2, stock_code: "000001", stock_name: "平安银行", industry: "银行" },
  { id: 3, stock_code: "000858", stock_name: "五粮液", industry: "白酒" },
  { id: 4, stock_code: "300750", stock_name: "宁德时代", industry: "新能源" },
  { id: 5, stock_code: "002594", stock_name: "比亚迪", industry: "新能源汽车" }
]

mockRealtimeQuotes (5个报价)

[
  { stock_code: "600519", current_price: 1738.50, change_percent: 2.15, update_time: "15:00:00" },
  { stock_code: "000001", current_price: 12.50, change_percent: -0.80, update_time: "15:00:00" },
  { stock_code: "000858", current_price: 188.50, change_percent: 1.35, update_time: "15:00:00" },
  { stock_code: "300750", current_price: 198.20, change_percent: 3.45, update_time: "15:00:00" },
  { stock_code: "002594", current_price: 252.80, change_percent: -1.20, update_time: "15:00:00" }
]

mockFollowingEvents (5个事件)

[
  {
    id: 101,
    title: "贵州茅台发布Q3财报营收超预期",
    tags: ["财报", "消费", "白酒"],
    view_count: 1520,
    comment_count: 48,
    upvote_count: 256,
    heat_score: 85,
    exceed_expectation_score: 78,
    creator: { username: "财报分析师", avatar_url: "..." },
    created_at: "2025-01-15T09:30:00Z"
  },
  // ... 4 more events
]

mockCalendarEvents (7个事件)

[
  {
    id: 1,
    title: "关注半导体板块",
    event_date: "2025-10-20",
    type: "plan",
    importance: 4,
    stocks: ["002415", "600584"],
    source: "user"
  },
  {
    id: 201,
    title: "贵州茅台发布Q4财报",
    event_date: "2025-10-25",
    importance: 5,
    stocks: ["600519"],
    source: "future"
  },
  // ... 5 more events
]

mockInvestmentPlans (4条记录: 2 plan + 2 review)

[
  {
    id: 1,
    date: "2025-01-15",
    title: "布局新能源板块",
    content: "重点关注宁德时代和比亚迪的估值变化...",
    type: "plan",
    stocks: ["300750", "002594"],
    tags: ["新能源", "长期"],
    status: "active"
  },
  {
    id: 2,
    date: "2025-01-12",
    title: "本周交易复盘",
    content: "本周操作了3只股票总体盈利2.5%...",
    type: "review",
    stocks: ["600519", "000858"],
    tags: ["周复盘", "A股"],
    status: "completed"
  },
  // ... 2 more items
]

附录

A. 相关文件清单

文件路径 说明
src/views/Dashboard/Center.js 个人中心主组件 (733行)
src/views/Dashboard/components/InvestmentCalendarChakra.js 投资日历组件 (490行)
src/views/Dashboard/components/InvestmentPlansAndReviews.js 投资计划与复盘组件 (586行)
src/mocks/data/account.js Mock 数据定义 (479行)
src/mocks/handlers/account.js Mock API Handlers (660行)
MOCK_DATA_CENTER_SUPPLEMENT.md Mock 数据补充文档 (800+行)

B. API 端点快速查找

主组件 (Center.js)

  • GET /api/account/watchlist - 自选股列表
  • GET /api/account/events/following - 关注事件
  • GET /api/account/events/comments - 我的评论
  • GET /api/subscription/current - 订阅信息
  • GET /api/account/watchlist/realtime - 实时行情

投资日历 (InvestmentCalendarChakra)

  • GET /api/account/calendar/events - 获取日历事件
  • POST /api/account/calendar/events - 添加投资计划
  • DELETE /api/account/calendar/events/:id - 删除事件

投资计划与复盘 (InvestmentPlansAndReviews)

  • GET /api/account/investment-plans - 获取计划+复盘
  • POST /api/account/investment-plans - 创建计划/复盘
  • PUT /api/account/investment-plans/:id - 更新计划/复盘
  • DELETE /api/account/investment-plans/:id - 删除计划/复盘

C. 关键代码位置

功能 文件 行号
State 初始化 Center.js 80-87
主数据加载 (loadData) Center.js 89-124
实时行情加载 Center.js 127-155
定时刷新机制 Center.js 213-221
页面可见性监听 Center.js 198-210
日历事件加载 InvestmentCalendarChakra.js 86-125
添加投资计划 InvestmentCalendarChakra.js 169-223
删除日历事件 InvestmentCalendarChakra.js 226-262
计划复盘数据加载 InvestmentPlansAndReviews.js 97-124
保存计划/复盘 InvestmentPlansAndReviews.js 154-201
删除计划/复盘 InvestmentPlansAndReviews.js 204-232

文档结束

如有问题或需要进一步说明,请参考源代码或联系开发团队。