From 67ceb63b080d7cce327acf078cd9bcf7fa1110e9 Mon Sep 17 00:00:00 2001 From: kennethcheng Date: Tue, 28 Apr 2026 01:40:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E4=BF=AE=E5=A4=8D=E4=BB=B7=E6=A0=BC?= =?UTF-8?q?=E5=B0=BE=E9=9B=B6=E3=80=81=E5=A2=9E=E5=8A=A0=E5=B8=81=E7=A7=8D?= =?UTF-8?q?=E4=B8=8B=E6=8B=89=E6=A1=86=EF=BC=8C=E5=B9=B6=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=8C=81=E4=BB=93=E5=BC=95=E6=93=8E=E5=AE=9E=E7=8E=B0=E5=8E=9F?= =?UTF-8?q?=E5=B8=81=E7=A7=8D/=E6=9C=AC=E4=BD=8D=E5=B8=81=E5=8F=8C?= =?UTF-8?q?=E8=BD=A8=E5=88=B6=E7=9B=88=E4=BA=8F=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/dashboard/assets/page.tsx | 5 ++-- app/dashboard/page.tsx | 26 ++++++++++++++----- src/actions/portfolio.ts | 14 ++++++++++ .../transactions/add-transaction-dialog.tsx | 15 ++++++++--- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/app/dashboard/assets/page.tsx b/app/dashboard/assets/page.tsx index 27a3c06..fca5edb 100644 --- a/app/dashboard/assets/page.tsx +++ b/app/dashboard/assets/page.tsx @@ -10,6 +10,7 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; +import Big from 'big.js'; export default async function AssetsPage() { const assets = await getAssets(); @@ -53,14 +54,14 @@ export default async function AssetsPage() { {asset.symbol} {typeLabels[asset.type] || asset.type} {asset.baseCurrency} - {asset.latestPrice} + {asset.latestPrice ? new Big(asset.latestPrice).toString() : '-'} {asset.createdAt ? new Date(asset.createdAt).toLocaleString('zh-CN') : '-'} - + )) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index d97dd54..b30ec1d 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -90,21 +90,33 @@ export default async function DashboardPage() { {pos.baseCurrency}
- CNY 估值 - - ¥{formatAmount(pos.cnyValue)} + 持仓成本 ({pos.baseCurrency}) + + {formatAmount(pos.totalCostNative)}
-
+ {(() => { + const posPnlNative = new Big(pos.pnlNative); + const posPnlNativePositive = posPnlNative.gte(0); + return ( +
+ 当前盈亏 ({pos.baseCurrency}) + + {posPnlNativePositive ? '+' : ''}{formatAmount(pos.pnlNative)} + +
+ ); + })()} +
成本 (CNY) ¥{formatAmount(pos.totalCostCny)}
-
- 盈亏 +
+ 盈亏 (CNY) - {posPnlPositive ? '+' : ''}{formattedPosPnl} + {posPnlPositive ? '+' : ''}{formatAmount(pos.pnlCny)}
diff --git a/src/actions/portfolio.ts b/src/actions/portfolio.ts index 0279a82..7cc9741 100644 --- a/src/actions/portfolio.ts +++ b/src/actions/portfolio.ts @@ -14,6 +14,8 @@ interface Position { cnyValue: string; totalCostCny: string; pnlCny: string; + totalCostNative: string; + pnlNative: string; } interface RawRate { @@ -95,6 +97,7 @@ export async function getPortfolioPositions(): Promise { baseCurrency: string; latestPrice: string; totalCostCny: Big; + totalCostNative: Big; }>(); for (const tx of allTransactions) { @@ -110,6 +113,7 @@ export async function getPortfolioPositions(): Promise { baseCurrency: tx.assetBaseCurrency || '', latestPrice: tx.assetLatestPrice || '0', totalCostCny: new Big('0'), + totalCostNative: new Big('0'), }); } @@ -118,10 +122,15 @@ export async function getPortfolioPositions(): Promise { if (tx.txType === 'BUY') { holding.quantity = holding.quantity.plus(new Big(tx.quantity)); const costPerUnit = new Big(tx.quantity).times(new Big(tx.price)); + holding.totalCostNative = holding.totalCostNative.plus(costPerUnit); const costCny = costPerUnit.times(new Big(tx.exchangeRate || '1')); holding.totalCostCny = holding.totalCostCny.plus(costCny); } else if (tx.txType === 'SELL') { holding.quantity = holding.quantity.minus(new Big(tx.quantity)); + const sellCostPerUnit = new Big(tx.quantity).times(new Big(tx.price)); + holding.totalCostNative = holding.totalCostNative.minus(sellCostPerUnit); + const sellCostCny = sellCostPerUnit.times(new Big(tx.exchangeRate || '1')); + holding.totalCostCny = holding.totalCostCny.minus(sellCostCny); } else if (tx.txType === 'AIRDROP') { holding.quantity = holding.quantity.plus(new Big(tx.quantity)); } else if (tx.txType === 'DIVIDEND') { @@ -160,6 +169,9 @@ export async function getPortfolioPositions(): Promise { const pnlCny = cnyValue.minus(holding.totalCostCny); totalPnlCny = totalPnlCny.plus(pnlCny); + const currentNativeValue = new Big(holding.latestPrice).times(holding.quantity); + const pnlNative = currentNativeValue.minus(holding.totalCostNative); + result.push({ assetId: holding.assetId, symbol: holding.symbol, @@ -169,6 +181,8 @@ export async function getPortfolioPositions(): Promise { cnyValue: cnyValue.toString(), totalCostCny: holding.totalCostCny.toString(), pnlCny: pnlCny.toString(), + totalCostNative: holding.totalCostNative.toString(), + pnlNative: pnlNative.toString(), }); } diff --git a/src/components/transactions/add-transaction-dialog.tsx b/src/components/transactions/add-transaction-dialog.tsx index 44a960f..99dbe86 100644 --- a/src/components/transactions/add-transaction-dialog.tsx +++ b/src/components/transactions/add-transaction-dialog.tsx @@ -218,9 +218,18 @@ export function AddTransactionDialog({ assets }: AddTransactionDialogProps) { render={({ field }) => ( 交易币种 - - - + )}