diff --git a/app/globals.css b/app/globals.css index 7eddfa1..71249a6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1435,8 +1435,7 @@ input[type="number"] { .user-menu-dropdown { position: fixed; - top: 76px; - right: 16px; + right: 0; min-width: 200px; padding: 8px; z-index: 100; diff --git a/app/page.jsx b/app/page.jsx index a503d8f..d443c38 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -445,7 +445,7 @@ export default function HomePage() { }; checkUpdate(); - const interval = setInterval(checkUpdate, 10 * 60 * 1000); // 10 minutes + const interval = setInterval(checkUpdate, 30 * 60 * 1000); // 30 minutes return () => clearInterval(interval); }, []); @@ -2794,7 +2794,7 @@ export default function HomePage() { animate={{ opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, y: -10, scale: 0.95 }} transition={{ duration: 0.15 }} - style={{ transformOrigin: 'top right' }} + style={{ transformOrigin: 'top right', top: navbarHeight + (isMobile ? -20 : 10) }} > {user ? ( <> diff --git a/doc/localStorage 数据结构.md b/doc/localStorage 数据结构.md new file mode 100644 index 0000000..b80d075 --- /dev/null +++ b/doc/localStorage 数据结构.md @@ -0,0 +1,376 @@ +# localStorage 数据结构说明 + +本文档详细说明了 real-time-fund 项目中使用的 localStorage 数据结构。 + +## 概述 + +项目使用 localStorage 来持久化用户的基金数据、配置和状态。所有数据都以 JSON 字符串格式存储(除简单字符串外)。 + +--- + +## 数据键列表 + +### 1. funds + +**类型**: `Array` +**默认值**: `[]` +**说明**: 存储用户添加的所有基金信息 + +**数据结构**: +```javascript +[ + { + code: string, // 基金代码(唯一标识) + name: string, // 基金名称 + type: string, // 基金类型 + dwjz: number, // 单位净值 + gsz: number, // 估算净值 + gszzl: number, // 估算涨跌幅 + jzrq: string, // 净值日期 + gztime: string, // 估值时间 + // ... 其他基金字段 + } +] +``` + +**使用场景**: +- 页面加载时恢复基金列表 +- 添加/删除基金时更新 +- 导入/导出配置时包含 +- 云端同步时同步 + +--- + +### 2. favorites + +**类型**: `Array` +**默认值**: `[]` +**说明**: 存储用户标记为自选的基金代码列表 + +**数据结构**: +```javascript +[ + "000001", // 基金代码 + "110022", + // ... +] +``` + +**使用场景**: +- 显示自选基金标签页 +- 添加/移除自选时更新 +- 导入/导出配置时包含 + +--- + +### 3. groups + +**类型**: `Array` +**默认值**: `[]` +**说明**: 存储用户创建的基金分组信息 + +**数据结构**: +```javascript +[ + { + id: string, // 分组唯一标识 + name: string, // 分组名称 + codes: Array // 分组内的基金代码列表 + } +] +``` + +**使用场景**: +- 显示分组标签页 +- 分组管理(添加、编辑、删除) +- 导入/导出配置时包含 + +--- + +### 4. collapsedCodes + +**类型**: `Array` +**默认值**: `[]` +**说明**: 存储用户收起的基金代码列表(用于折叠基金详情) + +**数据结构**: +```javascript +[ + "000001", // 收起的基金代码 + "110022", + // ... +] +``` + +**使用场景**: +- 记录用户折叠的基金卡片 +- 页面刷新后保持折叠状态 + +--- + +### 5. collapsedTrends + +**类型**: `Array` +**默认值**: `[]` +**说明**: 存储用户收起的业绩走势图表的基金代码列表 + +**数据结构**: +```javascript +[ + "000001", // 收起走势图的基金代码 + "110022", + // ... +] +``` + +**使用场景**: +- 记录用户折叠的业绩走势图表 +- 页面刷新后保持折叠状态 + +--- + +### 6. viewMode + +**类型**: `string` +**默认值**: `'card'` +**可选值**: `'card'` | `'list'` +**说明**: 存储用户选择的视图模式 + +**数据结构**: +```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` +**默认值**: `[]` +**说明**: 存储待处理的交易记录(当净值未更新时) + +**数据结构**: +```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点后 + isAfter3pm: boolean, // 是否下午3点后 + timestamp: number // 时间戳 + } +] +``` + +**使用场景**: +- 净值未更新时暂存交易 +- 净值更新后自动处理待处理交易 +- 导入/导出配置时包含 + +--- + +### 10. localUpdatedAt + +**类型**: `string` (ISO 8601 格式) +**默认值**: `null` +**说明**: 存储本地数据最后更新时间戳,用于云端同步冲突检测 + +**数据结构**: +```javascript +'2024-01-15T10:30:00.000Z' +``` + +**使用场景**: +- 云端同步时比较数据版本 +- 检测本地和云端数据冲突 + +--- + +### 11. hasClosedAnnouncement_v7 + +**类型**: `string` +**默认值**: `null` +**可选值**: `'true'` +**说明**: 标记用户是否已关闭公告弹窗 + +**数据结构**: +```javascript +'true' // 用户已关闭公告 +``` + +**使用场景**: +- 控制公告弹窗显示 +- 版本号后缀(v7)用于控制公告版本 + +--- + +## 数据同步机制 + +### 云端同步 + +项目支持通过 Supabase 进行云端数据同步: + +1. **上传到云端**: 用户登录后,本地数据会自动上传到云端 +2. **从云端下载**: 用户在其他设备登录时,会从云端下载数据 +3. **冲突处理**: 当本地和云端数据不一致时,会提示用户选择使用哪份数据 + +**同步的数据字段**: +- funds +- favorites +- groups +- collapsedCodes +- collapsedTrends +- viewMode +- refreshMs +- holdings +- pendingTrades + +### 导入/导出 + +用户可以导出配置到 JSON 文件,或从 JSON 文件导入配置: + +**导出格式**: +```javascript +{ + funds: [], + favorites: [], + groups: [], + collapsedCodes: [], + refreshMs: 30000, + viewMode: 'card', + holdings: {}, + pendingTrades: [], + 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. 确保数据类型正确 + +--- + +## 存储辅助工具 + +项目使用 `storageHelper` 对象来封装 localStorage 操作,提供统一的错误处理和日志记录。 + +```javascript +const storageHelper = { + setItem: (key, value) => { /* ... */ }, + getItem: (key) => { /* ... */ }, + removeItem: (key) => { /* ... */ }, + clear: () => { /* ... */ } +}; +``` + +--- + +## 注意事项 + +1. **数据大小限制**: localStorage 有约 5-10MB 的存储限制,大量基金数据可能超出限制 +2. **数据同步**: 修改数据后需要同时更新 localStorage 和 React state +3. **错误处理**: 所有 localStorage 操作都应包含 try-catch 错误处理 +4. **数据格式**: 复杂数据必须使用 JSON.stringify/JSON.parse 进行序列化/反序列化 +5. **版本控制**: 公告等配置使用版本号后缀,便于控制不同版本的显示 + +--- + +## 相关文件 + +- `app/page.jsx` - 主要页面组件,包含所有 localStorage 操作 +- `app/components/Announcement.jsx` - 公告组件 +- `app/lib/supabase.js` - Supabase 客户端配置 + +--- + +## 更新日志 + +- **2026-02-19**: 初始文档创建 diff --git a/supabase.sql b/doc/supabase.sql similarity index 100% rename from supabase.sql rename to doc/supabase.sql