diff --git a/Memory.md b/Memory.md index f2b8c10..67f9d6f 100644 --- a/Memory.md +++ b/Memory.md @@ -172,4 +172,11 @@ ## 修复 getEffectivePrice 引擎中 Drizzle ORM 缺失 and(eq(assetId)) 的致命逻辑漏网 - 修复 `getEffectivePrice` 引擎中 Drizzle ORM 查询条件未使用 `and()` 复合操作符的致命逻辑漏网,确保历史断点结转价格精准匹配单一资产。 - 在 `src/actions/snapshots.ts` 顶部从 `drizzle-orm` 引入 `and` 操作符,将 `getEffectivePrice` 的 `where` 子句从两个独立的 `.where()` 链式调用重构为 `and(eq(assetPricesHistory.assetId, assetId), lte(assetPricesHistory.date, dateStr))` 的显式复合条件。 -- 修复后 `assetId` 条件与日期条件被绝对锁定在同一个 `AND` 逻辑块中,彻底杜绝了周末等非交易日历史节点价格跨资产串联的荒谬市值问题(如海尔被错误匹配 BTC 价格导致 3700 万市值)。 \ No newline at end of file +- 修复后 `assetId` 条件与日期条件被绝对锁定在同一个 `AND` 逻辑块中,彻底杜绝了周末等非交易日历史节点价格跨资产串联的荒谬市值问题(如海尔被错误匹配 BTC 价格导致 3700 万市值)。 + +## 通过前端运行时覆盖策略完美对齐图表今日快照与实时总资产的汇率时差 (Task 54) +- 修复了 Dashboard 图表末端(Today 节点)与页面顶部超大号"总资产"数字之间存在时差误差的问题:图表依赖数据库里几小时前的快照,而概览数字基于实时计算,导致两者不完全一致。 +- 在 `app/dashboard/page.tsx` 的 `loadSnapshots` 中引入前端运行时覆盖策略:先调用 `getPortfolioSummary()` 获取实时总览数据,再调用 `getSnapshots()` 获取历史快照,动态替换或追加今天的节点,确保图表末端与大数字 100% 严丝合缝。 +- 具体逻辑:如果今天已有快照记录(`lastSnapshot.date === todayStr`),则将 `totalValueCny` 和 `totalCostCny` 覆盖为实时值;如果今天尚无快照,则直接追加一个实时点。 +- 移除 `getSnapshots` 查询的视口限制:将 `src/actions/snapshots.ts` 中 `getSnapshots` 的默认 `limit` 从 365 改为无默认值,仅在显式传入 `limit` 参数时才应用 `.limit()`,前端调用处移除 `{ limit: 30 }` 参数,实现从第一笔交易至今的全量净值走势渲染。 +- 前端 Dashboard 页面中两处 `getSnapshots` 调用(初始加载与重构历史后刷新)均已移除 `limit` 参数。 \ No newline at end of file diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index e91a346..d2d69f3 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -109,7 +109,20 @@ export default function DashboardPage() { useEffect(() => { async function loadSnapshots() { await recordDailySnapshot(); - const data = await getSnapshots({ limit: 30 }); + const summary = await getPortfolioSummary(); + const data = await getSnapshots(); + const todayStr = new Date().toISOString().slice(0, 10); + const lastSnapshot = data[data.length - 1]; + if (lastSnapshot && lastSnapshot.date === todayStr) { + lastSnapshot.totalValueCny = summary.totalCnyValue; + lastSnapshot.totalCostCny = summary.totalCnyValue; + } else { + data.push({ + date: todayStr, + totalValueCny: summary.totalCnyValue, + totalCostCny: summary.totalCnyValue, + }); + } setSnapshots(data); } loadSnapshots(); @@ -212,7 +225,7 @@ export default function DashboardPage() { toast.dismiss(); if (result.success) { toast.success(`重构成功,已填充 ${result.daysReconstructed} 天历史数据`); - setSnapshots(await getSnapshots({ limit: 30 })); + setSnapshots(await getSnapshots()); } else if (result.message) { toast.info(result.message); } diff --git a/src/actions/snapshots.ts b/src/actions/snapshots.ts index afb1b0f..af98448 100644 --- a/src/actions/snapshots.ts +++ b/src/actions/snapshots.ts @@ -88,7 +88,7 @@ export async function getSnapshots(params?: { startDate?: string; endDate?: string; }) { - const { limit = 365, startDate, endDate } = params || {}; + const { limit, startDate, endDate } = params || {}; let query = db .select() @@ -105,7 +105,7 @@ export async function getSnapshots(params?: { ); } - const snapshots = await query.limit(limit); + const snapshots = limit ? await query.limit(limit) : await query; return snapshots.reverse(); }