diff --git a/vehicle-data-platform/apps/web/src/App.tsx b/vehicle-data-platform/apps/web/src/App.tsx index 5987d5df..b901098c 100644 --- a/vehicle-data-platform/apps/web/src/App.tsx +++ b/vehicle-data-platform/apps/web/src/App.tsx @@ -23,6 +23,18 @@ export default function App() { const [currentVehicleStatus, setCurrentVehicleStatus] = useState(); const [currentVehicleLabel, setCurrentVehicleLabel] = useState(''); + const loadVehicleContext = useCallback(async (keyword: string) => { + const lookupKey = keyword.trim(); + 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 refreshOpsHealth = useCallback((showError = true) => { return api.opsHealth() .then((health) => { @@ -34,7 +46,16 @@ export default function App() { Toast.error(error.message); } }); - }, []); + }, [loadVehicleContext]); + + useEffect(() => { + if (initialRoute.page !== 'detail' || !initialRoute.keyword) { + return; + } + loadVehicleContext(initialRoute.keyword).catch((error) => { + Toast.error(error instanceof Error ? error.message : '车辆查询失败'); + }); + }, [initialRoute.keyword, initialRoute.page, loadVehicleContext]); useEffect(() => { refreshOpsHealth(); @@ -92,15 +113,14 @@ export default function App() { return; } try { - 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); + const context = await loadVehicleContext(lookupKey); + const resolved = context?.resolved; + const nextKey = context?.nextKey ?? lookupKey; setActiveVin(nextKey); setActiveProtocol(nextProtocol); setActivePage('detail'); replaceHash('detail', nextKey, nextProtocol); - if (!resolved.resolved) { + if (!resolved?.resolved) { Toast.warning('未匹配到车辆身份,已打开问题排查视图'); } } catch (error) { 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 d6c7d917..712730e1 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -393,6 +393,77 @@ test('opens vehicle detail from shareable hash', async () => { expect(screen.getAllByText('车辆服务').length).toBeGreaterThanOrEqual(1); }); +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')) { + return { + ok: true, + json: async () => ({ + data: { + lookupKey: 'VIN001', + resolved: true, + vin: 'VIN001', + plate: '粤AG18312', + phone: '13307795425', + oem: 'G7s', + protocols: ['GB32960', 'JT808'], + online: true, + lastSeen: '2026-07-03 20:12:10', + serviceStatus: { + status: 'degraded', + severity: 'warning', + title: '部分来源离线', + detail: '1/2 个来源在线', + sourceCount: 2, + onlineSourceCount: 1 + } + }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/vehicle-service')) { + return { + ok: true, + json: async () => ({ + data: { + vin: 'VIN001', + lookupKey: 'VIN001', + lookupResolved: true, + sources: ['GB32960', 'JT808'], + sourceStatus: [], + realtime: [], + history: { items: [], total: 0, limit: 20, offset: 0 }, + raw: { items: [], total: 0, limit: 10, offset: 0 }, + mileage: { items: [], total: 0, limit: 20, offset: 0 }, + quality: { items: [], total: 0, limit: 20, offset: 0 } + }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + return { + ok: true, + json: async () => ({ + data: path.includes('/api/ops/health') + ? { linkHealth: [], kafkaLag: 0, redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } } + : { items: [], total: 0, limit: 10, offset: 0 }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + }); + + render(); + + expect(await screen.findByText('粤AG18312 / VIN001')).toBeInTheDocument(); + expect(screen.getByText('当前车辆:部分来源离线')).toBeInTheDocument(); +}); + test('applies protocol from shareable history hash to API requests', async () => { window.history.replaceState(null, '', '/#/history?keyword=%E7%B2%A4AG18312&protocol=JT808'); const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {