diff --git a/src/routes/index.tsx b/src/routes/index.tsx index eb9fb29..d8165da 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -14,6 +14,7 @@ import { import type { NameType, ValueType } from 'recharts/types/component/DefaultTooltipContent' import { orpc } from '@/client/orpc' import type { DashboardSnapshot, DeviceStatus } from '@/domain/battery' +import { BATTERY_LIST_SORT, DEVICE_STATUS } from '@/domain/battery' export const Route = createFileRoute('/')({ component: Dashboard, @@ -57,9 +58,9 @@ function buildChartData(soh: DashboardSnapshot['soh']) { } const statusColorMap: Record = { - 健康: 'text-emerald-400', - 关注: 'text-amber-400', - 预警: 'text-red-400', + [DEVICE_STATUS.HEALTHY]: 'text-emerald-400', + [DEVICE_STATUS.WATCH]: 'text-amber-400', + [DEVICE_STATUS.WARNING]: 'text-red-400', } const severityColorMap: Record = { @@ -77,6 +78,23 @@ function formatChartTooltip(value: ValueType | undefined, name: NameType | undef ] } +function formatPercent(value: number | null) { + return value === null ? '—' : value.toFixed(1) +} + +function formatPercentWithUnit(value: number | null) { + return value === null ? '预测不可用' : `${value.toFixed(1)}%` +} + +function formatDelta(from: number | null, to: number | null) { + if (from === null || to === null) return '预测不可用' + return `${(from - to).toFixed(1)}% 衰减` +} + +function widthPercent(value: number | null) { + return `${Math.max(0, Math.min(100, value ?? 0))}%` +} + function Dashboard() { const { data, error, isPending } = useQuery(orpc.battery.dashboard.queryOptions()) @@ -147,7 +165,7 @@ function Dashboard() {

数据更新时间: {updatedAt}

设备电池实时状态 → @@ -168,13 +186,13 @@ function Dashboard() {

当前平均 SoH

-

{avgSoh.toFixed(1)}

- % +

{formatPercent(avgSoh)}

+ {avgSoh !== null && %}
- 基线健康 + {avgSoh === null ? 'AI预测不可用' : '基线健康'} | 共 {totalDevices} 台设备 @@ -185,24 +203,24 @@ function Dashboard() {

30 天预测均值

-

{avgSoh30d.toFixed(1)}

- % +

{formatPercent(avgSoh30d)}

+ {avgSoh30d !== null && %}
- {(avgSoh - avgSoh30d).toFixed(1)}% 衰减 + {formatDelta(avgSoh, avgSoh30d)}

90 天预测均值

-

{avgSoh90d.toFixed(1)}

- % +

{formatPercent(avgSoh90d)}

+ {avgSoh90d !== null && %}
- {(avgSoh - avgSoh90d).toFixed(1)}% 衰减 + {formatDelta(avgSoh, avgSoh90d)}
@@ -245,97 +263,103 @@ function Dashboard() {
- - - - - - - - - - - - - - - Math.floor(min) - 2, (max: number) => Math.ceil(max) + 2]} - tick={{ fill: '#71717A', fontSize: 11 }} - axisLine={false} - tickLine={false} - tickFormatter={(v: number) => `${v}%`} - width={48} - /> - - - - - - - - + {chartData.length > 0 ? ( + + + + + + + + + + + + + + + Math.floor(min) - 2, (max: number) => Math.ceil(max) + 2]} + tick={{ fill: '#71717A', fontSize: 11 }} + axisLine={false} + tickLine={false} + tickFormatter={(v: number) => `${v}%`} + width={48} + /> + + + + + + + + + ) : ( +
+ AI SoH 预测不可用,暂无可绘制的健康度趋势。 +
+ )}
@@ -412,10 +436,15 @@ function Dashboard() { {item.batch}
-
+
- {item.avgSoh.toFixed(1)}% + + {formatPercentWithUnit(item.avgSoh)} +
)) ) : ( @@ -511,9 +540,9 @@ function Dashboard() { {unit.id} {unit.batch} - {unit.soh.toFixed(1)}% - {unit.soh30d.toFixed(1)}% - {unit.soh90d.toFixed(1)}% + {formatPercentWithUnit(unit.soh)} + {formatPercentWithUnit(unit.soh30d)} + {formatPercentWithUnit(unit.soh90d)}
@@ -604,7 +633,7 @@ function Dashboard() {
{item.count} 台设备
-
{item.avgSoh.toFixed(1)}%
+
{formatPercentWithUnit(item.avgSoh)}
平均 SoH