feat(ui): 首页挂载重构历史触发按钮,打通历史净值回溯全链路

This commit is contained in:
kennethcheng 2026-04-30 11:26:21 +08:00
parent fd0ef345dd
commit c38d3fe30f
2 changed files with 32 additions and 3 deletions

View File

@ -147,3 +147,10 @@
- 循环体内调用 `getHistoricalPositions(currentDate)` 获取当天所有有持仓的资产(含持仓数量与累计本金),再调用 `getEffectivePrice(assetId, currentDate)` 获取各资产的有效价格(断点结转)。
- 引入汇率转换逻辑:预先加载 `assets` 表获取各资产的基础币种,加载 `exchangeRates` 表构建汇率映射,支持直接汇率与 USD 交叉换算,将各资产市值统一换算为 CNY。
- 使用 `Big.js` 确保所有金额计算的高精度,按天计算 `totalValueCny`(总市值)与 `totalCostCny`(总本金),并通过 Upsert 逻辑写入 `portfolioSnapshots` 表,确保每天仅存一条记录。
## Dashboard 首页实装"重构历史走势"功能按钮 (Task 50c)
- 在 `app/dashboard/page.tsx` 的"总资产概览"卡片右上角挂载"重构历史走势"按钮 (Button variant="outline")。
- 点击按钮后调用 `reconstructPortfolioHistory()` Server Action启动 Day-by-Day 历史净值回溯引擎。
- 集成 Sonner Toast 通知:点击时显示 `toast.loading('正在重构历史走势...')`,完成后显示 `toast.success('重构成功,已填充 N 天历史数据')`,并自动刷新 `snapshots` 状态以更新 AreaChart 走势图。
- 按钮启用 `isPending` 防重复点击,重构期间显示"重构中..."并禁用按钮。
- 打通历史净值回溯全链路:用户从 Dashboard 首页一键触发,底层引擎自动从最早交易日起逐天计算持仓与价格,填充 `portfolio_snapshots` 表,前端图表实时渲染历史波动曲线。

View File

@ -22,7 +22,7 @@ import {
import { toast } from 'sonner';
import { getPortfolioSummary } from '@/actions/portfolio';
import { getAssets } from '@/actions/asset';
import { recordDailySnapshot, getSnapshots } from '@/actions/snapshots';
import { recordDailySnapshot, getSnapshots, reconstructPortfolioHistory } from '@/actions/snapshots';
import { formatQuantity, formatAmount } from '@/lib/formatters';
import AllocationChart from '@/components/dashboard/allocation-chart';
import NetWorthChart from '@/components/dashboard/net-worth-chart';
@ -201,7 +201,29 @@ export default function DashboardPage() {
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<SyncButton />
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => {
startTransition(async () => {
toast.loading('正在重构历史走势...');
const result = await reconstructPortfolioHistory();
toast.dismiss();
if (result.success) {
toast.success(`重构成功,已填充 ${result.daysReconstructed} 天历史数据`);
setSnapshots(await getSnapshots({ limit: 30 }));
} else if (result.message) {
toast.info(result.message);
}
});
}}
disabled={isPending}
>
{isPending ? '重构中...' : '重构历史走势'}
</Button>
<SyncButton />
</div>
</CardHeader>
<CardContent className="pt-6 pb-6">
<div className="flex items-center gap-3">