- 移动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>
1813 lines
72 KiB
Markdown
1813 lines
72 KiB
Markdown
# 个人中心页面业务流程分析文档
|
||
|
||
> **文档创建日期**: 2025-01-19
|
||
> **分析范围**: `/home/center` 个人中心页面
|
||
> **相关文件**:
|
||
> - `src/views/Dashboard/Center.js` (主组件)
|
||
> - `src/views/Dashboard/components/InvestmentCalendarChakra.js` (日历组件)
|
||
> - `src/views/Dashboard/components/InvestmentPlansAndReviews.js` (计划复盘组件)
|
||
|
||
---
|
||
|
||
## 📋 目录
|
||
|
||
- [一、页面组件结构](#一页面组件结构)
|
||
- [二、组件初始化和挂载流程](#二组件初始化和挂载流程)
|
||
- [三、API 请求时间线和数据流](#三api-请求时间线和数据流)
|
||
- [四、子组件的 API 请求](#四子组件的-api-请求)
|
||
- [五、完整 API 请求汇总表](#五完整-api-请求汇总表)
|
||
- [六、UI 状态变化和交互流程](#六ui-状态变化和交互流程)
|
||
- [七、UI 状态变化映射表](#七ui-状态变化映射表)
|
||
- [八、关键设计亮点](#八关键设计亮点)
|
||
- [九、数据流向图](#九数据流向图)
|
||
- [十、Mock 数据对应关系](#十mock-数据对应关系)
|
||
|
||
---
|
||
|
||
## 一、页面组件结构
|
||
|
||
```
|
||
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 初始化
|
||
|
||
```javascript
|
||
// 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)
|
||
|
||
```javascript
|
||
// 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`
|
||
|
||
**触发时机**:
|
||
- 组件挂载
|
||
- 用户点击"刷新数据"按钮
|
||
- 页面从后台切回前台
|
||
|
||
**执行流程**:
|
||
|
||
```javascript
|
||
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)
|
||
|
||
**执行流程**:
|
||
|
||
```javascript
|
||
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 }] }` |
|
||
|
||
**数据合并逻辑**:
|
||
|
||
```javascript
|
||
// 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`
|
||
|
||
```javascript
|
||
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 自动加载事件数据
|
||
|
||
```javascript
|
||
// 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 响应数据结构**:
|
||
|
||
```json
|
||
{
|
||
"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)**:
|
||
|
||
```javascript
|
||
// 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 });
|
||
}
|
||
};
|
||
```
|
||
|
||
**请求体示例**:
|
||
|
||
```json
|
||
{
|
||
"title": "关注半导体板块",
|
||
"description": "重点关注龙头股走势",
|
||
"event_date": "2025-10-20",
|
||
"type": "plan",
|
||
"importance": 4,
|
||
"stocks": ["002415", "600584"]
|
||
}
|
||
```
|
||
|
||
**删除事件 (API 8)**:
|
||
|
||
```javascript
|
||
// 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 自动加载数据
|
||
|
||
```javascript
|
||
// 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 响应数据结构**:
|
||
|
||
```json
|
||
{
|
||
"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)**:
|
||
|
||
```javascript
|
||
// 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 });
|
||
}
|
||
};
|
||
```
|
||
|
||
**请求体示例 (创建计划)**:
|
||
|
||
```json
|
||
{
|
||
"date": "2025-01-20",
|
||
"title": "布局新能源板块",
|
||
"content": "重点关注宁德时代和比亚迪的估值变化...",
|
||
"type": "plan",
|
||
"stocks": ["300750", "002594"],
|
||
"tags": ["新能源", "长期"],
|
||
"status": "active"
|
||
}
|
||
```
|
||
|
||
**删除计划/复盘 (API 12)**:
|
||
|
||
```javascript
|
||
// 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` 更新<br>`selectedDateEvents` 更新<br>`isOpen = true` | 打开事件详情 Modal,显示当天所有事件 | - |
|
||
| **点击"添加计划"** | `isAddOpen = true`<br>`selectedDate` 设置 | 打开添加计划 Modal,表单为空 | - |
|
||
| **保存计划成功** | `isAddOpen = false` | 关闭 Modal,显示 Toast 提示,日历刷新显示新事件 | API 7 |
|
||
| **删除日历事件** | 确认对话框 → `events` 更新 | 二次确认,Toast 提示,日历移除该事件标记 | API 8 |
|
||
| **切换到"我的复盘" Tab** | Tab `index` 变化 | 显示复盘列表内容,隐藏计划列表 | - |
|
||
| **点击"新建复盘"** | `isOpen = true`<br>`editingItem = null`<br>`formData.type = 'review'` | 打开编辑 Modal,标题为"新建复盘记录" | - |
|
||
| **保存复盘成功** | `isOpen = false` | 关闭 Modal,Toast 提示,列表新增卡片 | API 10 |
|
||
| **点击"编辑"按钮** | `isOpen = true`<br>`editingItem` 更新<br>`formData` 预填充 | 打开编辑 Modal,表单显示现有数据 | - |
|
||
| **更新成功** | `isOpen = false` | 关闭 Modal,Toast 提示,卡片内容更新 | API 11 |
|
||
| **点击"删除"按钮** | 确认对话框 → `plans/reviews` 更新 | 二次确认,Toast 提示,列表移除该卡片 | API 12 |
|
||
| **页面从后台切回** | `visibilitychange` 事件 | 自动调用 `loadData()`,全部数据静默刷新 | API 1-4 |
|
||
| **自选股数量变化** | `watchlist.length` 变化 | 定时器重新创建 (有股票时启动,无股票时停止) | - |
|
||
| **添加股票标签** | `formData.stocks` 数组新增 | 表单中新增蓝色 Tag,带关闭按钮 | - |
|
||
| **添加自定义标签** | `formData.tags` 数组新增 | 表单中新增紫色 Tag,带关闭按钮 | - |
|
||
|
||
---
|
||
|
||
## 八、关键设计亮点
|
||
|
||
### 1. 性能优化
|
||
|
||
#### 1.1 并行请求
|
||
```javascript
|
||
// 使用 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 缓存控制
|
||
```javascript
|
||
// 添加时间戳和缓存控制头,确保获取最新数据
|
||
fetch(`/api/account/watchlist?_=${Date.now()}`, {
|
||
cache: 'no-store',
|
||
headers: { 'Cache-Control': 'no-cache' }
|
||
});
|
||
```
|
||
**作用**: 防止浏览器缓存旧数据,确保数据新鲜度
|
||
|
||
#### 1.3 条件加载
|
||
```javascript
|
||
// 只有在有自选股时才加载实时行情
|
||
if (jw.data && jw.data.length > 0) {
|
||
loadRealtimeQuotes();
|
||
}
|
||
```
|
||
**优势**: 避免不必要的 API 请求,节省服务器资源
|
||
|
||
#### 1.4 定时刷新优化
|
||
```javascript
|
||
// 仅在有自选股时启动定时器
|
||
if (watchlist.length > 0) {
|
||
const interval = setInterval(() => {
|
||
loadRealtimeQuotes(); // 60秒刷新一次
|
||
}, 60000);
|
||
return () => clearInterval(interval);
|
||
}
|
||
```
|
||
**优势**:
|
||
- 自选股为空时不启动定时器
|
||
- 组件卸载时自动清理,防止内存泄漏
|
||
|
||
#### 1.5 页面可见性检测
|
||
```javascript
|
||
// 页面从后台切回前台时自动刷新数据
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (document.visibilityState === 'visible') {
|
||
loadData();
|
||
}
|
||
});
|
||
```
|
||
**用户体验**:
|
||
- 用户切回标签页时看到最新数据
|
||
- 无需手动刷新
|
||
|
||
---
|
||
|
||
### 2. 用户体验优化
|
||
|
||
#### 2.1 多层次加载状态
|
||
```javascript
|
||
const [loading, setLoading] = useState(true); // 初次加载
|
||
const [refreshing, setRefreshing] = useState(false); // 手动刷新
|
||
const [quotesLoading, setQuotesLoading] = useState(false); // 行情刷新
|
||
```
|
||
**好处**:
|
||
- 用户清楚知道哪个部分正在加载
|
||
- 不同操作有不同的视觉反馈
|
||
|
||
#### 2.2 响应式布局
|
||
```javascript
|
||
<Grid templateColumns={{ base: '1fr', lg: '1fr 2fr' }} gap={6}>
|
||
{/* 桌面端: 左侧 1 列 + 右侧 2 列 */}
|
||
{/* 移动端: 单列布局 */}
|
||
</Grid>
|
||
```
|
||
**适配**:
|
||
- 桌面端 (≥992px): 两栏布局
|
||
- 移动端 (<992px): 单列布局
|
||
|
||
#### 2.3 空状态引导
|
||
```javascript
|
||
{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 实时行情颜色编码
|
||
```javascript
|
||
<Badge colorScheme={changePercent > 0 ? 'red' : 'green'}>
|
||
{changePercent > 0 ? '+' : ''}{changePercent.toFixed(2)}%
|
||
</Badge>
|
||
```
|
||
**符合习惯**:
|
||
- 国内市场: 红涨绿跌
|
||
- 自动添加 "+" 号
|
||
|
||
#### 2.5 相对时间显示
|
||
```javascript
|
||
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 自动刷新机制
|
||
```javascript
|
||
// 页面可见性变化时自动刷新
|
||
if (document.visibilityState === 'visible') {
|
||
loadData();
|
||
}
|
||
```
|
||
**保证**: 用户看到的数据始终是最新的
|
||
|
||
#### 3.3 乐观更新
|
||
```javascript
|
||
// 删除/添加操作成功后立即重新加载列表
|
||
if (response.ok) {
|
||
toast({ title: '删除成功' });
|
||
loadEvents(); // 立即刷新
|
||
}
|
||
```
|
||
**优势**: UI 立即反映最新状态
|
||
|
||
---
|
||
|
||
### 4. 错误处理
|
||
|
||
#### 4.1 静默失败 (数据加载)
|
||
```javascript
|
||
try {
|
||
const data = await loadData();
|
||
} catch (err) {
|
||
logger.error('Center', 'loadData', err); // 仅日志记录
|
||
// ❌ 不显示 Toast,避免干扰用户
|
||
}
|
||
```
|
||
**理由**:
|
||
- 数据加载失败通常是网络问题
|
||
- 避免频繁弹窗影响体验
|
||
|
||
#### 4.2 明确反馈 (用户操作)
|
||
```javascript
|
||
try {
|
||
await handleDelete(id);
|
||
toast({ title: '删除成功', status: 'success' });
|
||
} catch (error) {
|
||
toast({ title: '删除失败', status: 'error' }); // ✅ 明确告知
|
||
}
|
||
```
|
||
**理由**:
|
||
- 用户主动操作需要明确结果
|
||
- 失败时需要提示原因
|
||
|
||
#### 4.3 数据脱敏
|
||
```javascript
|
||
logger.error('Center', 'loadData', err, {
|
||
userId: user?.id, // ✅ 只记录 ID
|
||
// ❌ 不记录敏感信息 (手机号、邮箱等)
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
```
|
||
**安全**: 日志中不包含用户隐私数据
|
||
|
||
---
|
||
|
||
### 5. 组件解耦
|
||
|
||
#### 5.1 子组件独立
|
||
- `InvestmentCalendarChakra`: 自管理状态 + API
|
||
- `InvestmentPlansAndReviews`: 自管理状态 + API
|
||
- 无需父组件传递数据
|
||
|
||
#### 5.2 无 Props 传递
|
||
```javascript
|
||
// ✅ 好的设计
|
||
<InvestmentCalendarChakra />
|
||
|
||
// ❌ 避免的设计
|
||
<InvestmentCalendarChakra
|
||
events={events}
|
||
onAdd={handleAdd}
|
||
onDelete={handleDelete}
|
||
/>
|
||
```
|
||
**优势**:
|
||
- 组件更独立
|
||
- 易于维护和测试
|
||
|
||
#### 5.3 自包含 CRUD
|
||
每个子组件都实现完整的 CRUD 功能:
|
||
- **C**reate: 添加新记录
|
||
- **R**ead: 加载数据列表
|
||
- **U**pdate: 编辑现有记录
|
||
- **D**elete: 删除记录
|
||
|
||
---
|
||
|
||
## 九、数据流向图
|
||
|
||
```
|
||
用户打开 /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.js` 和 `src/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只股票)
|
||
```javascript
|
||
[
|
||
{ 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个报价)
|
||
```javascript
|
||
[
|
||
{ 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个事件)
|
||
```javascript
|
||
[
|
||
{
|
||
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个事件)
|
||
```javascript
|
||
[
|
||
{
|
||
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)
|
||
```javascript
|
||
[
|
||
{
|
||
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 |
|
||
|
||
---
|
||
|
||
**文档结束**
|
||
|
||
如有问题或需要进一步说明,请参考源代码或联系开发团队。
|