feat(dashboard): 优化资产分布图表,实现按市场维度的聚合展示与 Tooltip 交互增强

This commit is contained in:
2026-04-28 16:58:26 +08:00
parent e093b94157
commit 9342e46aad
4 changed files with 135 additions and 43 deletions
+70
View File
@@ -24,6 +24,7 @@ interface Position {
avgCost: string;
dilutedCost: string;
holdingDays: number;
exchange: string;
}
interface RawRate {
@@ -80,6 +81,30 @@ function calculateCnyValueFromPrice(
return new Big('0');
}
function getMarketFromExchange(exchange: string): string {
if (!exchange) return '未知';
const upper = exchange.toUpperCase();
if (upper === 'SSE' || upper === 'SZSE') return 'A股';
if (upper === 'HKEX') return '港股';
if (upper === 'CRYPTO') return '虚拟币';
return '美股';
}
const MARKET_COLORS: Record<string, string> = {
'A股': '#ef4444',
'港股': '#f59e0b',
'美股': '#3b82f6',
'虚拟币': '#10b981',
};
interface MarketAllocation {
market: string;
name: string;
totalCnyValue: number;
percentage: number;
fill: string;
}
function getTodayInShanghai(): Date {
const now = new Date();
const utcStr = now.toLocaleString('en-US', { timeZone: 'UTC' });
@@ -102,6 +127,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
assetType: assets.type,
assetBaseCurrency: assets.baseCurrency,
assetLatestPrice: assets.latestPrice,
assetExchange: assets.exchange,
executedAt: transactions.executedAt,
})
.from(transactions)
@@ -124,6 +150,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
quantity: Big;
baseCurrency: string;
latestPrice: string;
exchange: string;
// 累计买入指标
totalBuyCostCny: Big;
totalBuyCostNative: Big;
@@ -147,6 +174,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
quantity: new Big('0'),
baseCurrency: tx.assetBaseCurrency || '',
latestPrice: tx.assetLatestPrice || '0',
exchange: tx.assetExchange || 'US',
totalBuyCostCny: new Big('0'),
totalBuyCostNative: new Big('0'),
totalBuyQuantity: new Big('0'),
@@ -266,6 +294,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
avgCost: avgCost.toString(),
dilutedCost: dilutedCost.toString(),
holdingDays,
exchange: holding.exchange,
});
}
@@ -307,11 +336,52 @@ export async function getPortfolioSummary() {
][index % 6],
}));
// 按市场维度聚合资产分布
const marketMap = new Map<string, {
market: string;
totalCnyValue: Big;
}>();
for (const pos of positions) {
const market = getMarketFromExchange(pos.exchange);
const existing = marketMap.get(market);
if (existing) {
existing.totalCnyValue = existing.totalCnyValue.plus(new Big(pos.cnyValue));
} else {
marketMap.set(market, {
market,
totalCnyValue: new Big(pos.cnyValue),
});
}
}
const marketAllocation: MarketAllocation[] = [];
let grandTotal = new Big('0');
for (const [, data] of marketMap) {
grandTotal = grandTotal.plus(data.totalCnyValue);
}
for (const [, data] of marketMap) {
const percentage = grandTotal.gt(0)
? data.totalCnyValue.div(grandTotal).times(100)
: new Big('0');
marketAllocation.push({
market: data.market,
name: data.market,
totalCnyValue: Number(data.totalCnyValue.toString()),
percentage: Number(percentage.toString()),
fill: MARKET_COLORS[data.market] || '#6b7280',
});
}
marketAllocation.sort((a, b) => b.totalCnyValue - a.totalCnyValue);
return {
positions,
totalCnyValue: totalCnyValue.toString(),
totalPnlCny: totalPnlCny.toString(),
unrealizedPnlCny: unrealizedPnlCny.toString(),
chartData,
marketAllocation,
};
}