From 8dfbdc83e5f46317a306adb92b4d265c95ae2b26 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 02:57:11 +0800 Subject: [PATCH] feat(platform-web): use lightweight overview for vehicle context --- vehicle-data-platform/apps/web/src/App.tsx | 68 ++++++++++++-- .../apps/web/src/test/App.test.tsx | 92 +++++++++---------- 2 files changed, 104 insertions(+), 56 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/App.tsx b/vehicle-data-platform/apps/web/src/App.tsx index b901098c..e462a006 100644 --- a/vehicle-data-platform/apps/web/src/App.tsx +++ b/vehicle-data-platform/apps/web/src/App.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { Toast } from '@douyinfe/semi-ui'; import { api } from './api/client'; -import type { VehicleServiceStatus } from './api/types'; +import type { VehicleServiceOverview, VehicleServiceStatus } from './api/types'; import { buildAppHash, parseAppHash } from './domain/appRoute'; import { AppShell, type PageKey } from './layout/AppShell'; import { Dashboard } from './pages/Dashboard'; @@ -28,11 +28,12 @@ export default function App() { if (!lookupKey) { return undefined; } - const resolved = await api.vehicleResolve(new URLSearchParams({ keyword: lookupKey })); - const nextKey = resolved.resolved && resolved.vin ? resolved.vin : lookupKey; - setCurrentVehicleStatus(resolved.serviceStatus); - setCurrentVehicleLabel(resolved.resolved ? [resolved.plate, resolved.vin || nextKey].filter(Boolean).join(' / ') : lookupKey); - return { resolved, nextKey }; + const overview = await api.vehicleServiceOverview(new URLSearchParams({ keyword: lookupKey })); + const nextKey = overview.vin || lookupKey; + const resolved = Boolean(overview.vin); + setCurrentVehicleStatus(serviceStatusFromOverview(overview)); + setCurrentVehicleLabel(resolved ? [overview.plate, overview.vin || nextKey].filter(Boolean).join(' / ') : lookupKey); + return { resolved, nextKey, overview }; }, []); const refreshOpsHealth = useCallback((showError = true) => { @@ -120,7 +121,7 @@ export default function App() { setActiveProtocol(nextProtocol); setActivePage('detail'); replaceHash('detail', nextKey, nextProtocol); - if (!resolved?.resolved) { + if (!resolved) { Toast.warning('未匹配到车辆身份,已打开问题排查视图'); } } catch (error) { @@ -179,3 +180,56 @@ export default function App() { ); } + +function serviceStatusFromOverview(overview: VehicleServiceOverview): VehicleServiceStatus { + const sourceCount = overview.sourceCount; + const onlineSourceCount = overview.onlineSourceCount; + if (!overview.vin) { + return { + status: 'identity_required', + severity: 'warning', + title: '身份未绑定', + detail: '车辆关键词暂未解析到 VIN,需先维护身份绑定后才能形成完整车辆服务。', + sourceCount, + onlineSourceCount + }; + } + if (sourceCount <= 0) { + return { + status: 'no_data', + severity: 'warning', + title: '暂无数据来源', + detail: '车辆已解析,但暂未查询到 32960、808 或 MQTT 数据来源。', + sourceCount, + onlineSourceCount + }; + } + if (onlineSourceCount <= 0) { + return { + status: 'offline', + severity: 'error', + title: '车辆离线', + detail: '所有已知数据来源均未在线,需要检查平台转发、终端上报或链路状态。', + sourceCount, + onlineSourceCount + }; + } + if (onlineSourceCount < sourceCount) { + return { + status: 'degraded', + severity: 'warning', + title: '部分来源离线', + detail: `${onlineSourceCount}/${sourceCount} 个来源在线,车辆服务可用但需要关注离线来源。`, + sourceCount, + onlineSourceCount + }; + } + return { + status: 'healthy', + severity: 'ok', + title: '服务正常', + detail: `${onlineSourceCount}/${sourceCount} 个来源在线,全部已知来源在线。`, + sourceCount, + onlineSourceCount + }; +} diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx index 0931c64d..79b42555 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -59,7 +59,7 @@ test('shows vehicle service status distribution on dashboard', async () => { test('shows resolved vehicle service status after topbar search', async () => { window.history.replaceState(null, '', '/#/dashboard'); - vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { + const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { const path = String(input); if (path.includes('/api/ops/health')) { return { @@ -81,28 +81,26 @@ test('shows resolved vehicle service status after topbar search', async () => { }) } as Response; } - if (path.includes('/api/vehicles/resolve')) { + if (path.includes('/api/vehicle-service/overview')) { return { ok: true, json: async () => ({ data: { - lookupKey: '粤AG18312', - resolved: true, vin: 'LB9A32A24R0LS1426', plate: '粤AG18312', phone: '13307795425', oem: 'G7s', protocols: ['GB32960', 'JT808'], - online: true, + primaryProtocol: 'JT808', + coverageStatus: 'partial', + sourceCount: 2, + onlineSourceCount: 1, lastSeen: '2026-07-03 20:12:10', - serviceStatus: { - status: 'degraded', - severity: 'warning', - title: '部分来源离线', - detail: '1/2 个来源在线', - sourceCount: 2, - onlineSourceCount: 1 - } + realtimeCount: 2, + historyCount: 0, + rawCount: 0, + mileageCount: 0, + qualityIssueCount: 0 }, traceId: 'trace-test', timestamp: 1783094400000 @@ -147,6 +145,8 @@ test('shows resolved vehicle service status after topbar search', async () => { expect(await screen.findByText('当前车辆:部分来源离线')).toBeInTheDocument(); expect(screen.getByText('粤AG18312 / LB9A32A24R0LS1426')).toBeInTheDocument(); + expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/vehicle-service/overview?keyword=%E7%B2%A4AG18312'), undefined); + expect(fetchMock).not.toHaveBeenCalledWith(expect.stringContaining('/api/vehicles/resolve'), undefined); }); test('shows row service status in dashboard vehicle previews', async () => { @@ -397,28 +397,26 @@ test('shows vehicle context when opening detail from shareable hash', async () = window.history.replaceState(null, '', '/#/detail?keyword=VIN001'); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { const path = String(input); - if (path.includes('/api/vehicles/resolve')) { + if (path.includes('/api/vehicle-service/overview')) { return { ok: true, json: async () => ({ data: { - lookupKey: 'VIN001', - resolved: true, vin: 'VIN001', plate: '粤AG18312', phone: '13307795425', oem: 'G7s', protocols: ['GB32960', 'JT808'], - online: true, + primaryProtocol: 'JT808', + coverageStatus: 'partial', + sourceCount: 2, + onlineSourceCount: 1, lastSeen: '2026-07-03 20:12:10', - serviceStatus: { - status: 'degraded', - severity: 'warning', - title: '部分来源离线', - detail: '1/2 个来源在线', - sourceCount: 2, - onlineSourceCount: 1 - } + realtimeCount: 2, + historyCount: 0, + rawCount: 0, + mileageCount: 0, + qualityIssueCount: 0 }, traceId: 'trace-test', timestamp: 1783094400000 @@ -468,28 +466,26 @@ test('refreshes vehicle context when hash changes to another detail vehicle', as window.history.replaceState(null, '', '/#/dashboard'); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { const path = String(input); - if (path.includes('/api/vehicles/resolve')) { + if (path.includes('/api/vehicle-service/overview')) { return { ok: true, json: async () => ({ data: { - lookupKey: 'VIN002', - resolved: true, vin: 'VIN002', plate: '粤B20002', phone: '', oem: 'G7s', protocols: ['JT808'], - online: false, + primaryProtocol: 'JT808', + coverageStatus: 'offline', + sourceCount: 1, + onlineSourceCount: 0, lastSeen: '2026-07-03 20:10:10', - serviceStatus: { - status: 'offline', - severity: 'error', - title: '车辆离线', - detail: '所有已知数据来源均未在线', - sourceCount: 1, - onlineSourceCount: 0 - } + realtimeCount: 1, + historyCount: 0, + rawCount: 0, + mileageCount: 0, + qualityIssueCount: 0 }, traceId: 'trace-test', timestamp: 1783094400000 @@ -552,28 +548,26 @@ test('shows vehicle context when opening detail from sidebar navigation', async window.history.replaceState(null, '', '/#/dashboard'); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { const path = String(input); - if (path.includes('/api/vehicles/resolve')) { + if (path.includes('/api/vehicle-service/overview')) { return { ok: true, json: async () => ({ data: { - lookupKey: 'LB9A32A24R0LS1426', - resolved: true, vin: 'LB9A32A24R0LS1426', plate: '粤AGQ8398', phone: '', oem: 'G7s', protocols: ['JT808'], - online: false, + primaryProtocol: 'JT808', + coverageStatus: 'offline', + sourceCount: 1, + onlineSourceCount: 0, lastSeen: '2026-07-03 20:10:10', - serviceStatus: { - status: 'offline', - severity: 'error', - title: '车辆离线', - detail: '所有已知数据来源均未在线', - sourceCount: 1, - onlineSourceCount: 0 - } + realtimeCount: 1, + historyCount: 0, + rawCount: 0, + mileageCount: 0, + qualityIssueCount: 0 }, traceId: 'trace-test', timestamp: 1783094400000