Compare commits

..

No commits in common. "5269d697b7e4d9877381c40a21e6958a6f31d752" and "91e748525945aae759acdc44323076f00d39bc6a" have entirely different histories.

3 changed files with 8 additions and 37 deletions

View File

@ -167,16 +167,4 @@
- 修复 `getHistoricalPositions``getEffectivePrice` 中的日期字符串生成逻辑:统一使用 `formatDateString(targetDate)`,确保 Drizzle 的 `lte` 查询中左右两边日期字符串格式一致(均为 `YYYY-MM-DD`),修复了 UTC 与本地时区混用导致的查询偏移。
- 修复 `reconstructPortfolioHistory` 主循环while 条件与 `dateStr` 生成均改用 `formatDateString(currentDate)`,确保与 `getTodayInShanghai()` 返回格式完全一致。
- 在资产遍历循环中增加兜底防御逻辑:当 `getEffectivePrice` 返回 null 时,使用 `assetLatestPriceMap` 中缓存的 `latestPrice` 作为兜底价格,避免价格变量为 undefined 或沿用上一资产的值;同时修正了 `totalCostCny``priceStr` 为空时不应累加的 Bug。
- 在 `allAssets` 查询中新增 `latestPrice` 字段,构建 `assetLatestPriceMap` 供兜底逻辑使用。
## 修复 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 万市值)。
## 通过前端运行时覆盖策略完美对齐图表今日快照与实时总资产的汇率时差 (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` 参数。
- 在 `allAssets` 查询中新增 `latestPrice` 字段,构建 `assetLatestPriceMap` 供兜底逻辑使用。

View File

@ -109,20 +109,7 @@ export default function DashboardPage() {
useEffect(() => {
async function loadSnapshots() {
await recordDailySnapshot();
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,
});
}
const data = await getSnapshots({ limit: 30 });
setSnapshots(data);
}
loadSnapshots();
@ -225,7 +212,7 @@ export default function DashboardPage() {
toast.dismiss();
if (result.success) {
toast.success(`重构成功,已填充 ${result.daysReconstructed} 天历史数据`);
setSnapshots(await getSnapshots());
setSnapshots(await getSnapshots({ limit: 30 }));
} else if (result.message) {
toast.info(result.message);
}

View File

@ -3,7 +3,7 @@
import { db } from '@/db';
import { portfolioSnapshots, transactions, assetPricesHistory, assets, exchangeRates } from '@/db/schema';
import { getPortfolioPositions } from './portfolio';
import { and, asc, desc, eq, gte, lte, sql } from 'drizzle-orm';
import { asc, desc, eq, gte, lte, sql } from 'drizzle-orm';
import Big from 'big.js';
function formatDateString(date: Date): string {
@ -88,7 +88,7 @@ export async function getSnapshots(params?: {
startDate?: string;
endDate?: string;
}) {
const { limit, startDate, endDate } = params || {};
const { limit = 365, startDate, endDate } = params || {};
let query = db
.select()
@ -105,7 +105,7 @@ export async function getSnapshots(params?: {
);
}
const snapshots = limit ? await query.limit(limit) : await query;
const snapshots = await query.limit(limit);
return snapshots.reverse();
}
@ -194,12 +194,8 @@ export async function getEffectivePrice(
price: assetPricesHistory.price,
})
.from(assetPricesHistory)
.where(
and(
eq(assetPricesHistory.assetId, assetId),
lte(assetPricesHistory.date, dateStr)
)
)
.where(eq(assetPricesHistory.assetId, assetId))
.where(lte(assetPricesHistory.date, dateStr))
.orderBy(desc(assetPricesHistory.date))
.limit(1);