653 lines
14 KiB
Markdown
653 lines
14 KiB
Markdown
# localStorage 数据结构说明
|
||
|
||
本文档详细说明了 real-time-fund 项目中使用的 localStorage 数据结构。
|
||
|
||
## 概述
|
||
|
||
项目使用 localStorage 来持久化用户的基金数据、配置和状态。所有数据都以 JSON 字符串格式存储(除简单字符串外)。
|
||
|
||
---
|
||
|
||
## 数据键列表
|
||
|
||
### 1. funds
|
||
|
||
**类型**: `Array<Object>`
|
||
**默认值**: `[]`
|
||
**说明**: 存储用户添加的所有基金信息
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
[
|
||
{
|
||
code: string, // 基金代码(唯一标识)
|
||
name: string, // 基金名称
|
||
type: string, // 基金类型
|
||
dwjz: number, // 单位净值
|
||
gsz: number, // 估算净值
|
||
gszzl: number, // 估算涨跌幅
|
||
jzrq: string, // 净值日期
|
||
gztime: string, // 估值时间
|
||
// ... 其他基金字段
|
||
}
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 页面加载时恢复基金列表
|
||
- 添加/删除基金时更新
|
||
- 导入/导出配置时包含
|
||
- 云端同步时同步
|
||
|
||
---
|
||
|
||
### 2. favorites
|
||
|
||
**类型**: `Array<string>`
|
||
**默认值**: `[]`
|
||
**说明**: 存储用户标记为自选的基金代码列表
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
[
|
||
"000001", // 基金代码
|
||
"110022",
|
||
// ...
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 显示自选基金标签页
|
||
- 添加/移除自选时更新
|
||
- 导入/导出配置时包含
|
||
|
||
---
|
||
|
||
### 3. groups
|
||
|
||
**类型**: `Array<Object>`
|
||
**默认值**: `[]`
|
||
**说明**: 存储用户创建的基金分组信息
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
[
|
||
{
|
||
id: string, // 分组唯一标识
|
||
name: string, // 分组名称
|
||
codes: Array<string> // 分组内的基金代码列表
|
||
}
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 显示分组标签页
|
||
- 分组管理(添加、编辑、删除)
|
||
- 导入/导出配置时包含
|
||
|
||
---
|
||
|
||
### 4. collapsedCodes
|
||
|
||
**类型**: `Array<string>`
|
||
**默认值**: `[]`
|
||
**说明**: 存储用户收起的基金代码列表(用于折叠基金详情)
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
[
|
||
"000001", // 收起的基金代码
|
||
"110022",
|
||
// ...
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 记录用户折叠的基金卡片
|
||
- 页面刷新后保持折叠状态
|
||
|
||
---
|
||
|
||
### 5. collapsedTrends
|
||
|
||
**类型**: `Array<string>`
|
||
**默认值**: `[]`
|
||
**说明**: 存储用户收起的业绩走势图表的基金代码列表
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
[
|
||
"000001", // 收起走势图的基金代码
|
||
"110022",
|
||
// ...
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 记录用户折叠的业绩走势图表
|
||
- 页面刷新后保持折叠状态
|
||
|
||
---
|
||
|
||
### 6. viewMode
|
||
|
||
**类型**: `string`
|
||
**默认值**: `'card'`
|
||
**可选值**: `'card'` | `'list'`
|
||
**说明**: 存储用户选择的视图模式
|
||
**云端同步**: 否(仅通过 customSettings 同步)
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
'card' // 卡片视图
|
||
'list' // 列表视图
|
||
```
|
||
|
||
**使用场景**:
|
||
- 切换卡片/列表视图
|
||
- 页面刷新后保持视图模式
|
||
|
||
---
|
||
|
||
### 7. refreshMs
|
||
|
||
**类型**: `number` (字符串存储)
|
||
**默认值**: `30000` (30秒)
|
||
**最小值**: `5000` (5秒)
|
||
**说明**: 存储数据刷新间隔时间(毫秒)
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
'30000' // 30秒刷新一次
|
||
'60000' // 60秒刷新一次
|
||
```
|
||
|
||
**使用场景**:
|
||
- 控制基金数据自动刷新频率
|
||
- 用户设置刷新间隔时更新
|
||
|
||
---
|
||
|
||
### 8. holdings
|
||
|
||
**类型**: `Object`
|
||
**默认值**: `{}`
|
||
**说明**: 存储用户的持仓信息
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
{
|
||
"000001": {
|
||
share: number, // 持有份额
|
||
cost: number // 持仓成本价
|
||
},
|
||
"110022": {
|
||
share: number,
|
||
cost: number
|
||
}
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 计算持仓收益
|
||
- 买入/卖出操作时更新
|
||
- 导入/导出配置时包含
|
||
|
||
---
|
||
|
||
### 9. pendingTrades
|
||
|
||
**类型**: `Array<Object>`
|
||
**默认值**: `[]`
|
||
**说明**: 存储待处理的交易记录(当净值未更新时)
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
[
|
||
{
|
||
id: string, // 交易唯一标识
|
||
fundCode: string, // 基金代码
|
||
fundName: string, // 基金名称
|
||
type: string, // 交易类型 'buy' | 'sell'
|
||
share: number, // 交易份额
|
||
amount: number, // 交易金额
|
||
feeRate: number, // 手续费率
|
||
feeMode: string, // 手续费模式
|
||
feeValue: number, // 手续费金额
|
||
date: string, // 交易日期
|
||
isAfter3pm: boolean, // 是否下午3点后
|
||
timestamp: number // 时间戳
|
||
}
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 净值未更新时暂存交易
|
||
- 净值更新后自动处理待处理交易
|
||
- 导入/导出配置时包含
|
||
|
||
---
|
||
|
||
### 10. localUpdatedAt
|
||
|
||
**类型**: `string` (ISO 8601 格式)
|
||
**默认值**: `null`
|
||
**说明**: 存储本地数据最后更新时间戳,用于云端同步冲突检测
|
||
**云端同步**: 否(本地专用)
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
'2024-01-15T10:30:00.000Z'
|
||
```
|
||
|
||
**使用场景**:
|
||
- 云端同步时比较数据版本
|
||
- 检测本地和云端数据冲突
|
||
|
||
---
|
||
|
||
### 11. hasClosedAnnouncement_v19
|
||
|
||
**类型**: `string`
|
||
**默认值**: `null`
|
||
**可选值**: `'true'`
|
||
**说明**: 标记用户是否已关闭公告弹窗(版本号后缀用于控制不同版本的公告)
|
||
**云端同步**: 否
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
'true' // 用户已关闭公告
|
||
```
|
||
|
||
**使用场景**:
|
||
- 控制公告弹窗显示
|
||
- 版本号后缀(v19)用于控制公告版本
|
||
|
||
---
|
||
|
||
### 12. customSettings
|
||
|
||
**类型**: `Object`
|
||
**默认值**: `{}`
|
||
**说明**: 存储用户的高级设置和偏好
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
{
|
||
localSortRules: [ // 排序规则配置
|
||
{
|
||
id: string, // 规则唯一标识
|
||
field: string, // 排序字段
|
||
label: string, // 显示标签
|
||
direction: 'asc' | 'desc', // 排序方向
|
||
enabled: boolean // 是否启用
|
||
}
|
||
],
|
||
pcContainerWidth: number, // PC端容器宽度(桌面版)
|
||
marketIndexSelected: Array<string>, // 选中的市场指数代码
|
||
// ... 其他自定义设置
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 排序规则持久化
|
||
- PC端布局宽度设置
|
||
- 市场指数选择
|
||
- 云端同步所有自定义设置
|
||
|
||
---
|
||
|
||
### 13. localSortBy / localSortOrder
|
||
|
||
**类型**: `string`
|
||
**默认值**: `'default'` / `'asc'`
|
||
**说明**: 存储当前排序字段和排序方向
|
||
**云端同步**: 否(通过 customSettings 同步)
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
// localSortBy
|
||
'gszzl' // 按估算涨跌幅排序
|
||
'default' // 默认排序
|
||
|
||
// localSortOrder
|
||
'asc' // 升序
|
||
'desc' // 降序
|
||
```
|
||
|
||
**使用场景**:
|
||
- 快速访问当前排序状态
|
||
- 与 customSettings.localSortRules 保持同步
|
||
|
||
---
|
||
|
||
### 14. localSortRules (旧版)
|
||
|
||
**类型**: `Array<Object>`
|
||
**默认值**: `[]`
|
||
**说明**: 旧版排序规则存储,已迁移到 customSettings.localSortRules
|
||
**云端同步**: 否
|
||
|
||
**注意**: 该键已弃用,数据已迁移到 customSettings.localSortRules。代码中仍保留兼容性处理。
|
||
|
||
---
|
||
|
||
### 15. currentTab
|
||
|
||
**类型**: `string`
|
||
**默认值**: `'all'`
|
||
**说明**: 存储用户当前选中的标签页
|
||
**云端同步**: 否
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
'all' // 全部资产
|
||
'fav' // 自选
|
||
groupId // 分组ID,如 'group_xxx'
|
||
```
|
||
|
||
**使用场景**:
|
||
- 恢复用户上次查看的标签页
|
||
- 页面刷新后保持标签页状态
|
||
|
||
---
|
||
|
||
### 16. theme
|
||
|
||
**类型**: `string`
|
||
**默认值**: `'dark'`
|
||
**可选值**: `'light'` | `'dark'`
|
||
**说明**: 存储用户选择的主题模式
|
||
**云端同步**: 否
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
'dark' // 暗色主题
|
||
'light' // 亮色主题
|
||
```
|
||
|
||
**使用场景**:
|
||
- 控制应用整体配色
|
||
- 页面加载时立即应用(通过 layout.jsx 内联脚本)
|
||
|
||
---
|
||
|
||
### 17. fundValuationTimeseries
|
||
|
||
**类型**: `Object`
|
||
**默认值**: `{}`
|
||
**说明**: 存储基金估值分时数据,用于走势图展示
|
||
**云端同步**: 否(测试中功能,暂不同步)
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
{
|
||
"000001": [ // 按基金代码索引
|
||
{
|
||
time: string, // 时间点 "HH:mm"
|
||
value: number, // 估算净值
|
||
date: string // 日期 "YYYY-MM-DD"
|
||
}
|
||
],
|
||
"110022": [
|
||
// ...
|
||
]
|
||
}
|
||
```
|
||
|
||
**数据清理规则**:
|
||
- 当新数据日期大于已存储的最大日期时,清空该基金所有旧日期数据,只保留当日分时
|
||
- 同一日期内按时间顺序追加数据
|
||
|
||
**使用场景**:
|
||
- 基金详情页分时图展示
|
||
- 实时估值数据记录
|
||
|
||
---
|
||
|
||
### 18. transactions
|
||
|
||
**类型**: `Object`
|
||
**默认值**: `{}`
|
||
**说明**: 存储用户的交易历史记录
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
{
|
||
"000001": [ // 按基金代码索引的交易列表
|
||
{
|
||
id: string, // 交易唯一标识
|
||
type: 'buy' | 'sell', // 交易类型
|
||
amount: number, // 交易金额
|
||
share: number, // 交易份额
|
||
price: number, // 成交价格
|
||
date: string, // 交易日期
|
||
timestamp: number // 时间戳
|
||
}
|
||
],
|
||
"110022": [
|
||
// ...
|
||
]
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 交易历史查询
|
||
- 收益计算
|
||
- 买入/卖出操作记录
|
||
|
||
---
|
||
|
||
### 19. dcaPlans (定投计划)
|
||
|
||
**类型**: `Object`
|
||
**默认值**: `{}`
|
||
**说明**: 存储用户的定投计划配置
|
||
**云端同步**: 是
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
{
|
||
"000001": { // 按基金代码索引
|
||
amount: number, // 每次定投金额
|
||
feeRate: number, // 手续费率
|
||
cycle: string, // 定投周期
|
||
firstDate: string, // 首次定投日期
|
||
enabled: boolean // 是否启用
|
||
},
|
||
"110022": {
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 自动定投执行
|
||
- 定投计划管理
|
||
- 买入操作时设置
|
||
|
||
---
|
||
|
||
### 20. marketIndexSelected
|
||
|
||
**类型**: `Array<string>`
|
||
**默认值**: `[]`
|
||
**说明**: 存储用户选中的市场指数代码
|
||
**云端同步**: 否(通过 customSettings 同步)
|
||
|
||
**数据结构**:
|
||
```javascript
|
||
[
|
||
"sh000001", // 上证指数
|
||
"sz399001", // 深证成指
|
||
// ...
|
||
]
|
||
```
|
||
|
||
**使用场景**:
|
||
- 市场指数面板显示
|
||
- 指数选择管理
|
||
|
||
---
|
||
|
||
## 数据同步机制
|
||
|
||
### 云端同步
|
||
|
||
项目支持通过 Supabase 进行云端数据同步。以下键参与云端同步:
|
||
|
||
**参与云端同步的键**:
|
||
- funds
|
||
- favorites
|
||
- groups
|
||
- collapsedCodes
|
||
- collapsedTrends
|
||
- refreshMs
|
||
- holdings
|
||
- pendingTrades
|
||
- transactions
|
||
- dcaPlans
|
||
- customSettings
|
||
|
||
**不参与云端同步的键**:
|
||
- localUpdatedAt(本地专用)
|
||
- hasClosedAnnouncement_v19(本地专用)
|
||
- localSortBy / localSortOrder(通过 customSettings 同步)
|
||
- localSortRules(旧版兼容,通过 customSettings 同步)
|
||
- currentTab(本地会话状态)
|
||
- theme(本地主题偏好)
|
||
- fundValuationTimeseries(测试中功能)
|
||
- marketIndexSelected(通过 customSettings 同步)
|
||
- viewMode(通过 customSettings 同步)
|
||
|
||
**同步流程**:
|
||
1. 用户登录后,本地数据会自动上传到云端
|
||
2. 用户在其他设备登录时,会从云端下载数据
|
||
3. 当本地和云端数据不一致时,会提示用户选择使用哪份数据
|
||
|
||
### 导入/导出
|
||
|
||
用户可以导出配置到 JSON 文件,或从 JSON 文件导入配置:
|
||
|
||
**导出格式**:
|
||
```javascript
|
||
{
|
||
funds: [],
|
||
favorites: [],
|
||
groups: [],
|
||
collapsedCodes: [],
|
||
refreshMs: 30000,
|
||
holdings: {},
|
||
pendingTrades: [],
|
||
transactions: {},
|
||
dcaPlans: {},
|
||
customSettings: {},
|
||
exportedAt: '2024-01-15T10:30:00.000Z'
|
||
}
|
||
```
|
||
|
||
**导入逻辑**:
|
||
- 合并基金列表(去重)
|
||
- 合并自选、分组等配置
|
||
- 保留现有数据,避免覆盖
|
||
|
||
---
|
||
|
||
## 数据验证和清理
|
||
|
||
### 数据去重
|
||
|
||
基金列表使用 `dedupeByCode` 函数进行去重,确保每个基金代码只出现一次。
|
||
|
||
```javascript
|
||
const dedupeByCode = (list) => {
|
||
const seen = new Set();
|
||
return list.filter(f => {
|
||
if (!f?.code) return false;
|
||
if (seen.has(f.code)) return false;
|
||
seen.add(f.code);
|
||
return true;
|
||
});
|
||
};
|
||
```
|
||
|
||
### 数据清理
|
||
|
||
在收集数据上传云端时,会进行数据验证和清理:
|
||
|
||
1. 清理无效的持仓数据(基金不存在的持仓)
|
||
2. 清理无效的自选、分组、收起状态
|
||
3. 清理无效的交易记录和定投计划
|
||
4. 确保数据类型正确
|
||
|
||
---
|
||
|
||
## 存储辅助工具
|
||
|
||
项目使用 `storageHelper` 对象来封装 localStorage 操作,提供统一的错误处理和云端同步触发。
|
||
|
||
```javascript
|
||
const storageHelper = {
|
||
setItem: (key, value) => {
|
||
// 1. 写入 localStorage
|
||
// 2. 触发云端同步(如果是同步键)
|
||
// 3. 更新 localUpdatedAt 时间戳
|
||
},
|
||
getItem: (key) => {
|
||
// 从 localStorage 读取
|
||
},
|
||
removeItem: (key) => {
|
||
// 从 localStorage 删除
|
||
// 触发云端同步
|
||
},
|
||
clear: () => {
|
||
// 清空所有 localStorage
|
||
}
|
||
};
|
||
```
|
||
|
||
**特性**:
|
||
- 自动触发云端同步(对于参与同步的键)
|
||
- 自动更新 localUpdatedAt 时间戳
|
||
- funds 变更时比较签名,避免无意义同步
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
1. **数据大小限制**: localStorage 有约 5-10MB 的存储限制,大量基金数据可能超出限制
|
||
2. **数据同步**: 修改数据后需要同时更新 localStorage 和 React state
|
||
3. **错误处理**: 所有 localStorage 操作都应包含 try-catch 错误处理
|
||
4. **数据格式**: 复杂数据必须使用 JSON.stringify/JSON.parse 进行序列化/反序列化
|
||
5. **版本控制**: 公告等配置使用版本号后缀,便于控制不同版本的显示
|
||
6. **fundValuationTimeseries**: 该数据不同步到云端,因为数据量较大且属于临时性数据
|
||
|
||
---
|
||
|
||
## 相关文件
|
||
|
||
- `app/page.jsx` - 主要页面组件,包含所有 localStorage 操作
|
||
- `app/components/Announcement.jsx` - 公告组件
|
||
- `app/components/PcFundTable.jsx` - PC端基金表格组件
|
||
- `app/components/MobileFundTable.jsx` - 移动端基金表格组件
|
||
- `app/components/MarketIndexAccordion.jsx` - 市场指数组件
|
||
- `app/lib/supabase.js` - Supabase 客户端配置
|
||
- `app/lib/valuationTimeseries.js` - 估值分时数据管理
|
||
|
||
---
|
||
|
||
## 更新日志
|
||
|
||
- **2026-03-18**: 全面更新文档,补充 transactions、dcaPlans、fundValuationTimeseries、customSettings 等键的详细说明,修正云端同步键列表
|
||
- **2026-02-19**: 初始文档创建
|