diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index ef8c6a9b..9be67bb4 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -1,4 +1,4 @@ -import { Button, Card, Form, Select, Space, Table, Tabs, Tag, Toast } from '@douyinfe/semi-ui'; +import { Button, Card, Form, Select, Space, Table, Tabs, Tag, Toast, Typography } from '@douyinfe/semi-ui'; import { useEffect, useState } from 'react'; import { api } from '../api/client'; import type { VehicleRealtimeRow } from '../api/types'; @@ -27,6 +27,10 @@ function vehicleServiceStatus(row: VehicleRealtimeRow) { return { label: '服务正常', color: 'green' as const }; } +function sourceEvidenceText(row: VehicleRealtimeRow) { + return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; +} + export function Realtime({ onOpenVehicle }: { onOpenVehicle: (vin: string, protocol?: string) => void }) { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); @@ -55,7 +59,7 @@ export function Realtime({ onOpenVehicle }: { onOpenVehicle: (vin: string, proto return (
- +
{ const nextFilters = values as Record; @@ -104,10 +108,29 @@ export function Realtime({ onOpenVehicle }: { onOpenVehicle: (vin: string, proto onPageSizeChange: (pageSize) => load(filters, 1, pageSize) }} columns={[ - { title: 'VIN', dataIndex: 'vin' }, - { title: '车牌', dataIndex: 'plate' }, + { title: '车牌', dataIndex: 'plate', width: 120 }, + { title: 'VIN', dataIndex: 'vin', width: 190 }, { - title: '来源', + title: '车辆服务状态', + width: 130, + render: (_: unknown, row: VehicleRealtimeRow) => { + const status = vehicleServiceStatus(row); + return {status.label}; + } + }, + { + title: '车辆核心数据', + width: 230, + render: (_: unknown, row: VehicleRealtimeRow) => ( + + {row.speedKmh ?? '-'} km/h + SOC {row.socPercent ?? '-'}% + {row.totalMileageKm ?? '-'} km + + ) + }, + { + title: '来源证据', width: 260, render: (_: unknown, row: VehicleRealtimeRow) => ( @@ -117,21 +140,9 @@ export function Realtime({ onOpenVehicle }: { onOpenVehicle: (vin: string, proto ) }, - { title: '覆盖', width: 90, render: (_: unknown, row: VehicleRealtimeRow) => `${row.onlineSourceCount}/${row.sourceCount}` }, - { - title: '车辆服务状态', - width: 130, - render: (_: unknown, row: VehicleRealtimeRow) => { - const status = vehicleServiceStatus(row); - return {status.label}; - } - }, + { title: '证据覆盖', width: 120, render: (_: unknown, row: VehicleRealtimeRow) => sourceEvidenceText(row) }, { title: '在线', width: 90, render: (_: unknown, row: VehicleRealtimeRow) => }, - { title: '速度 km/h', dataIndex: 'speedKmh' }, - { title: 'SOC %', dataIndex: 'socPercent' }, - { title: '总里程 km', dataIndex: 'totalMileageKm' }, - { title: '最新来源', dataIndex: 'primaryProtocol' }, - { title: '最后时间', dataIndex: 'lastSeen' }, + { title: '最后时间', dataIndex: 'lastSeen', width: 170 }, { title: '操作', width: 110, 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 f174e757..27ccca7f 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -1484,6 +1484,68 @@ test('shows canonical service status in realtime vehicles', async () => { expect(screen.getByText('部分来源离线')).toBeInTheDocument(); }); +test('frames realtime page as one vehicle service with source evidence', async () => { + window.history.replaceState(null, '', '/#/realtime'); + vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { + const path = String(input); + if (path.includes('/api/realtime/vehicles')) { + return { + ok: true, + json: async () => ({ + data: { + items: [{ + vin: 'VIN-RT-SERVICE', + plate: '粤ART002', + phone: '', + oem: 'G7s', + protocols: ['GB32960', 'JT808'], + sourceCount: 2, + onlineSourceCount: 2, + online: true, + primaryProtocol: 'GB32960', + longitude: 113.2, + latitude: 23.1, + speedKmh: 30, + socPercent: 78, + totalMileageKm: 119925, + lastSeen: '2026-07-03 20:12:10', + bindingStatus: 'bound', + serviceStatus: { + status: 'healthy', + severity: 'ok', + title: '服务正常', + detail: '2/2 来源在线', + sourceCount: 2, + onlineSourceCount: 2 + } + }], + total: 1, + limit: 50, + offset: 0 + }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + return { + ok: true, + json: async () => ({ + data: { items: [], total: 0, limit: 10, offset: 0 }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + }); + + render(); + + expect(await screen.findByRole('heading', { name: '车辆服务实时态' })).toBeInTheDocument(); + expect(screen.getByText('车辆核心数据')).toBeInTheDocument(); + expect(screen.getByText('来源证据')).toBeInTheDocument(); + expect(screen.getByText('2/2 来源在线')).toBeInTheDocument(); +}); + test('shows vehicle service status in vehicle list', async () => { window.history.replaceState(null, '', '/#/vehicles'); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {