From ad3250012178740c10bb213572cca0e10e740aac Mon Sep 17 00:00:00 2001 From: imbytecat Date: Tue, 12 May 2026 00:56:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(dashboard):=20=E4=BD=BF=E7=94=A8=E7=A8=B3?= =?UTF-8?q?=E5=AE=9A=E8=AE=BE=E5=A4=87=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/battery.test.ts | 12 +++++++++++- src/domain/battery.ts | 4 +++- src/routes/index.tsx | 8 ++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/domain/battery.test.ts b/src/domain/battery.test.ts index fe8b770..d8f516c 100644 --- a/src/domain/battery.test.ts +++ b/src/domain/battery.test.ts @@ -3,6 +3,7 @@ import { createBatteriesResponse, createDashboardSnapshot, DEVICE_STATUS, + fromMysqlBoolean, getDeviceStatus, MYSQL_BOOLEAN, POWER_STATUS, @@ -27,7 +28,7 @@ const rows = [ userId: 7, mac: 'RING-B11', devModel: '2402-B', - devName: 'RING-B11', + devName: '', isLowPower: MYSQL_BOOLEAN.TRUE, powerStatus: POWER_STATUS.CHARGING, power: 84, @@ -43,6 +44,11 @@ describe('battery domain', () => { expect(getDeviceStatus(85)).toBe(DEVICE_STATUS.WARNING) }) + test('trims MySQL boolean strings before normalization', () => { + expect(fromMysqlBoolean(' true ')).toBe(true) + expect(fromMysqlBoolean(' false ')).toBe(false) + }) + test('builds batteries response counters from records', () => { const now = new Date('2026-05-11T00:00:00.000Z') const items = rows.map(toBatteryInfo) @@ -75,6 +81,10 @@ describe('battery domain', () => { const snapshot = createDashboardSnapshot(rows.map(toBatteryInfo), now) expect(snapshot.devices).toHaveLength(2) + expect(snapshot.devices[0]?.id).toBe('RING-A03') + expect(snapshot.devices[0]?.displayName).toBe('RING-A03') + expect(snapshot.devices[1]?.id).toBe('RING-B11') + expect(snapshot.devices[1]?.displayName).toBe('RING-B11') expect(snapshot.devices.every((device) => device.sohSource === 'unavailable')).toBe(true) expect(snapshot.devices.every((device) => device.soh === null)).toBe(true) expect(snapshot.devices.every((device) => device.soh30d === null)).toBe(true) diff --git a/src/domain/battery.ts b/src/domain/battery.ts index 041f5fd..c63ed0f 100644 --- a/src/domain/battery.ts +++ b/src/domain/battery.ts @@ -54,6 +54,7 @@ export type BatteryInfo = z.infer export const fleetUnitSchema = z.object({ id: z.string(), + displayName: z.string(), batch: z.string(), firmware: z.string(), cycles: z.number().int(), @@ -267,7 +268,8 @@ function toFleetUnit(item: BatteryInfo, prediction?: BatteryPrediction): FleetUn const riskScore = Math.round(clamp(prediction?.riskScore ?? fallbackRiskScore, 0, 100)) return { - id: item.devName || item.mac, + id: item.mac, + displayName: item.devName || item.mac, batch: item.devModel, firmware: item.remark ?? '未提供', cycles: prediction?.cyclesUsed ?? 0, diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 3934965..2b56269 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -93,7 +93,11 @@ function formatPercentWithUnit(value: number | null) { function formatDelta(from: number | null, to: number | null) { if (from === null || to === null) return '预测不可用' - return `${(from - to).toFixed(1)}% 衰减` + const delta = from - to + + if (delta < 0) return `${Math.abs(delta).toFixed(1)}% 改善` + if (delta === 0) return '0.0% 持平' + return `${delta.toFixed(1)}% 衰减` } function widthPercent(value: number | null) { @@ -540,7 +544,7 @@ function Dashboard() { .sort((a, b) => b.riskScore - a.riskScore) .map((unit) => ( - {unit.id} + {unit.displayName} {unit.batch} {formatPercentWithUnit(unit.soh)}