From 635a906c27308473c7c484427f6e0ee5b98af588 Mon Sep 17 00:00:00 2001 From: CsJsss <764527108@qq.com> Date: Mon, 1 Jun 2026 22:31:16 +0800 Subject: [PATCH 1/3] feat(inventory): add market total column with backend sort support Add marketTotal sort key in Go backend and a new sortable column showing marketPrice * totalQuantity in the inventory table. --- frontend/src/pages/InventoryPage.tsx | 27 ++++++++++++++++++++++++++- pkg/service/inventory/service.go | 7 +++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/InventoryPage.tsx b/frontend/src/pages/InventoryPage.tsx index df13322..94d3f89 100644 --- a/frontend/src/pages/InventoryPage.tsx +++ b/frontend/src/pages/InventoryPage.tsx @@ -35,7 +35,6 @@ import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import Tooltip from '@mui/material/Tooltip'; import type { model } from '../lib/wails'; - declare module '@tanstack/react-table' { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface ColumnMeta { @@ -133,6 +132,25 @@ const groupedColumns: ColumnDef[] = [ ), }, + { + id: 'marketTotal', + header: '市场总价', + meta: { align: 'right' }, + cell: ({ row }) => { + const { marketPrice, totalQuantity } = row.original; + if (marketPrice == null) + return ( + + -- + + ); + return ( + + {formatCNY(marketPrice * totalQuantity)} + + ); + }, + }, { id: 'priceUpdatedAt', header: '行情时间', @@ -444,6 +462,13 @@ export default function InventoryPage() { {group.marketPrice != null ? formatCNY(group.marketPrice) : '--'} + + + {group.marketPrice != null + ? formatCNY(group.marketPrice * group.totalQuantity) + : '--'} + + {group.marketPriceUpdatedAt diff --git a/pkg/service/inventory/service.go b/pkg/service/inventory/service.go index 48325d8..764c35b 100644 --- a/pkg/service/inventory/service.go +++ b/pkg/service/inventory/service.go @@ -36,6 +36,7 @@ var appSortFields = map[string]bool{ "totalBuyPrice": true, "avgBuyPrice": true, "marketPrice": true, + "marketTotal": true, "unrealizedPl": true, "plPercent": true, } @@ -276,6 +277,8 @@ func (s *service) sortGroups(groups []InventoryGroup, sortBy, sortDir string) { less = a.TotalQuantity < b.TotalQuantity case "marketPrice": less = ptrValue(a.MarketPrice) < ptrValue(b.MarketPrice) + case "marketTotal": + less = marketTotal(a) < marketTotal(b) case "unrealizedPl": less = ptrValue(a.UnrealizedPl) < ptrValue(b.UnrealizedPl) case "plPercent": @@ -344,3 +347,7 @@ func plPercent(g InventoryGroup) float64 { } return float64(*g.MarketPrice-g.AvgBuyPrice) / float64(g.AvgBuyPrice) } + +func marketTotal(g InventoryGroup) int64 { + return ptrValue(g.MarketPrice) * g.TotalQuantity +} From 9e53cfc4a8a04e423f4bac96ad1b247051813669 Mon Sep 17 00:00:00 2001 From: CsJsss <764527108@qq.com> Date: Mon, 1 Jun 2026 22:31:21 +0800 Subject: [PATCH 2/3] feat(dashboard): add market value distribution doughnut chart Add a collapsible doughnut chart showing market value breakdown by weapon type across all accounts, with click selection on slices. Repositioned with cost/P&L cards stacked on the left. --- frontend/src/pages/DashboardPage.tsx | 179 ++++++++++++++++++++++----- 1 file changed, 145 insertions(+), 34 deletions(-) diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index e152b0e..5bbfe85 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -14,22 +14,31 @@ import InventoryIcon from '@mui/icons-material/Inventory'; import ReceiptIcon from '@mui/icons-material/Receipt'; import RedeemIcon from '@mui/icons-material/Redeem'; import PaymentsIcon from '@mui/icons-material/Payments'; -import ShowChartIcon from '@mui/icons-material/ShowChart'; import DashboardIcon from '@mui/icons-material/Dashboard'; import ErrorBanner from '../components/ErrorBanner'; import EmptyState from '../components/EmptyState'; import { useDashboard } from '../hooks/useDashboard'; import { useMonthlyBreakdown } from '../hooks/useMonthlyBreakdown'; +import { useInventory } from '../hooks/useInventory'; import { useUIStore } from '../store/uiStore'; import { formatCNY, plColor, plHexColor } from '../lib/format'; import { priceSourceLabel } from '../lib/constants'; +import ShowChartIcon from '@mui/icons-material/ShowChart'; import ReactEChartsCore from 'echarts-for-react/lib/core'; import * as echarts from 'echarts/core'; -import { BarChart, LineChart } from 'echarts/charts'; -import { GridComponent, TooltipComponent } from 'echarts/components'; +import { BarChart, LineChart, PieChart } from 'echarts/charts'; +import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'; import { CanvasRenderer } from 'echarts/renderers'; -echarts.use([BarChart, LineChart, GridComponent, TooltipComponent, CanvasRenderer]); +echarts.use([ + BarChart, + LineChart, + PieChart, + GridComponent, + TooltipComponent, + LegendComponent, + CanvasRenderer, +]); const POS_COLOR = '#22c55e'; const NEG_COLOR = '#ef4444'; @@ -93,6 +102,86 @@ export default function DashboardPage() { const currentYear = new Date().getFullYear(); const { data: monthly = [] } = useMonthlyBreakdown(selectedAccountId, currentYear); + const { data: chartData } = useInventory(null, { + page: 1, + pageSize: 200, + weaponType: '', + sortBy: 'itemName', + sortDir: 'asc', + }); + + const marketChartOption = useMemo(() => { + const allGroups = chartData?.groups ?? []; + if (allGroups.length === 0) return null; + + const typeMap = new Map(); + let grandTotal = 0; + for (const g of allGroups) { + if (g.marketPrice == null) continue; + const wt = g.weaponType || '其他'; + const mv = g.marketPrice * g.totalQuantity; + typeMap.set(wt, (typeMap.get(wt) ?? 0) + mv); + grandTotal += mv; + } + if (grandTotal === 0) return null; + + const types = Array.from(typeMap.keys()); + const colors = [ + '#f97316', + '#14b8a6', + '#3b82f6', + '#a855f7', + '#eab308', + '#ec4899', + '#06b6d4', + '#84cc16', + '#f43f5e', + '#8b5cf6', + '#22d3ee', + '#f59e0b', + ]; + + return { + totalCents: grandTotal, + option: { + color: colors, + backgroundColor: 'transparent', + tooltip: { + trigger: 'item' as const, + backgroundColor: '#18181b', + borderColor: '#27272a', + textStyle: { color: '#d4d4d8', fontSize: 13, fontFamily: "'Geist Variable', sans-serif" }, + formatter: (p: { name: string; value: number; percent: number }) => + `${p.name}
¥${p.value.toLocaleString()} ${p.percent}%`, + }, + series: [ + { + type: 'pie', + radius: ['55%', '78%'], + center: ['50%', '50%'], + selectedMode: 'single', + selectedOffset: 4, + itemStyle: { borderColor: '#09090b', borderWidth: 2, borderRadius: 2 }, + data: types.map((t) => ({ name: t, value: typeMap.get(t)! / 100 })), + label: { + show: true, + position: 'outside' as const, + formatter: '{b} {d}%', + fontFamily: "'Geist Variable', sans-serif", + fontSize: 12, + color: '#a1a1aa', + }, + labelLine: { lineStyle: { color: '#3f3f46' } }, + emphasis: { + label: { show: true, fontSize: 14, fontWeight: 600 }, + scaleSize: 6, + }, + }, + ], + }, + }; + }, [chartData?.groups]); + const netWorth = data ? data.totalAvailableBalance + data.totalFrozenBalance + @@ -308,39 +397,61 @@ export default function DashboardPage() { - {/* Cost + market value row */} + {/* Cost / Market Value / P&L + Chart row */} - } - /> - - - } - /> + + } + /> + } + /> + = 0 ? '#22c55e' : '#ef4444' + } + icon={ + + } + /> + - - = 0 ? '#22c55e' : '#ef4444' - } - icon={ - - } - /> + + {marketChartOption && ( + + + 持仓市值分布 + + + {formatCNY(marketChartOption.totalCents)} + + + + + + )} From 347733c096629f7d6cbd5be3c2bb7a2c3ab494c2 Mon Sep 17 00:00:00 2001 From: CsJsss <764527108@qq.com> Date: Mon, 1 Jun 2026 22:31:24 +0800 Subject: [PATCH 3/3] fix(pnl): translate chart legend labels to Chinese MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Net P/L → 月度净盈亏, Cumulative P/L → 累计盈亏 --- frontend/src/pages/PnLPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/PnLPage.tsx b/frontend/src/pages/PnLPage.tsx index 1adc5de..476c16e 100644 --- a/frontend/src/pages/PnLPage.tsx +++ b/frontend/src/pages/PnLPage.tsx @@ -129,7 +129,7 @@ export default function PnLPage() { }, series: [ { - name: 'Net P/L', + name: '月度净盈亏', type: 'bar', data: barValues, barWidth: '55%', @@ -139,7 +139,7 @@ export default function PnLPage() { }, }, { - name: 'Cumulative P/L', + name: '累计盈亏', type: 'line', data: cumulative, smooth: true,