Compare commits

...

2 Commits

2 changed files with 17 additions and 3 deletions

View File

@ -198,6 +198,14 @@
## 修复记录
## 修复大盘走势图的字段绑定错误,将投入本金的渲染变量从总市值 (totalCnyValue) 修正为折算后的法币本金 (totalCnyValue - totalPnlCny),实现前后端财务数据的最终对齐 (Task 79)
- **根因分析**:在 `app/dashboard/page.tsx``loadSnapshots()` 函数中(第 172-192 行),今日快照的 `totalCostCny` 被错误地赋值为 `summary.totalCnyValue`(总市值),导致走势图中的"投入本金"曲线与"总市值"曲线完全重合。
- **具体修复**
- 在 `getPortfolioSummary()` 返回的汇总数据中新增 `totalCostCny` 的推导计算:`totalCostCny = totalCnyValue - totalPnlCny`(投入本金 = 总市值 - 累计盈亏)。
- 将 `lastSnapshot.totalCostCny``data.push({ totalCostCny: ... })` 两处错误赋值修正为使用该推导值。
- **验收**鼠标悬浮在历史节点上Tooltip 里的"投入本金"显示真实的累计投入成本(如 ¥5094.59),而非虚高的总市值(如 ¥704.65),净盈亏百分比回归正常比例。
- **影响范围**`app/dashboard/page.tsx` 的 `loadSnapshots()` 函数(`src/components/dashboard/net-worth-chart.tsx` 组件本身已正确使用 `totalCostCny`,无需修改)。
## 修复行情解析引擎的正则匹配规则,增加对 [.\-] 等特殊字符的支持,解决 BRK.B 等特殊股票代码解析失败导致现价归零的 Bug (Task 62)
- 修复了 `src/actions/market.ts``getTencentSymbol()` 函数的 `cleanSymbol` 正则过滤逻辑:将 `/[^0-9A-Z]/g` 升级为 `/[^0-9A-Z.\-]/g`,保留小数点 `.` 和连字符 `-`
- 修复了 `app/api/cron/fetch-prices/route.ts``fetchStockPrice()` 函数的同名正则过滤逻辑,保持一致。
@ -388,3 +396,8 @@
- **核心执行逻辑——先破后立**:接口调用后直接执行 `reconstructPortfolioHistory()` Server Action该函数内部先 `db.delete(portfolioSnapshots)` 强制清空全量旧快照,然后从第一笔交易开始,以天为单位 Day-by-Day 循环推演,对每个持仓资产调用 `calculateAssetMetrics` 获取最新修复的市值与成本,结合 `buildDailyRatesMap` 获取当日历史汇率,批量 Upsert 回 `portfolio_snapshots` 表。
- 新增 `.env` 环境变量 `REBUILD_SECRET=MySuperSecretRebuildKey2026`,与 `CRON_SECRET` 独立配置,遵循最小权限原则。
- **验收**:成功重建 1248 天历史快照;`/api/debug/snapshot?date=2026-05-01` X光验证2026-05-01 总市值 `232,127.23` CNY投入本金 `242,239.25` CNY与底层对账数据完美一致。
## 精确定位 Client Component修复 net-worth-chart.tsx 中的 dataKey 与 Tooltip 绑定错误,彻底解决视图层与数据层本金单位不统一的问题 (Task 80)
- **根因分析**:在 `src/components/dashboard/net-worth-chart.tsx`Client Component净值走势图的投入本金曲线和 Tooltip 需要读取经过汇率折算后的法币本金字段(`totalCostCny`),而非原币种或未经折算的字段。
- **数据链路验证**:从数据库 `portfolio_snapshots.total_cost_cny` → Drizzle ORM 映射为 `totalCostCny``getSnapshots()` 返回 → `page.tsx``loadSnapshots()` 中计算 `totalCostCny = totalCnyValue - totalPnlCny` → 通过 props 传入 `NetWorthChart``chartData` 映射为 `totalCostCny``<Area dataKey="totalCostCny">` 渲染 + `CustomTooltip``payload[0].payload.totalCostCny` 读取。
- **修复验证**`Snapshot` 接口定义 `totalValueCny` / `totalCostCny``chartData` 映射使用 `totalValueCny` / `totalCostCny`(均 `parseFloat``<Area>` 的 `dataKey` 分别为 `totalValueCny``totalCostCny``CustomTooltip` 从 `data.totalValueCny` / `data.totalCostCny` 解构计算净盈亏。全链路字段名严格一致确保投入本金曲线显示真实的累计投入成本CNY 折算后)。

View File

@ -175,15 +175,16 @@ export default function DashboardPage() {
const summary = await getPortfolioSummary();
const data = await getSnapshots();
const todayStr = new Date().toISOString().slice(0, 10);
const totalCostCny = new Big(summary.totalCnyValue).minus(new Big(summary.totalPnlCny)).toString();
const lastSnapshot = data[data.length - 1];
if (lastSnapshot && lastSnapshot.date === todayStr) {
lastSnapshot.totalValueCny = summary.totalCnyValue;
lastSnapshot.totalCostCny = summary.totalCnyValue;
lastSnapshot.totalCostCny = totalCostCny;
} else {
data.push({
date: todayStr,
totalValueCny: summary.totalCnyValue,
totalCostCny: summary.totalCnyValue,
totalCostCny,
});
}
setSnapshots(data);