diff --git a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx index e4a060d2..94153ed6 100644 --- a/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Vehicles.tsx @@ -33,6 +33,17 @@ export function Vehicles({ onOpenVehicle, initialFilters = {} }: { onOpenVehicle const [loading, setLoading] = useState(true); const [filters, setFilters] = useState>(initialFilters); const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 20, total: 0 }); + const resultSummary = useMemo(() => { + const onlineRows = rows.filter((row) => row.online).length; + const multiSourceRows = rows.filter((row) => row.sourceCount > 1).length; + const unboundRows = rows.filter((row) => row.bindingStatus !== 'bound').length; + return [ + { label: '过滤车辆', value: pagination.total.toLocaleString() }, + { label: '本页在线', value: onlineRows.toLocaleString() }, + { label: '本页多源', value: multiSourceRows.toLocaleString() }, + { label: '本页待绑定', value: unboundRows.toLocaleString() } + ]; + }, [pagination.total, rows]); const load = (values: Record = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => { setLoading(true); @@ -131,6 +142,16 @@ export function Vehicles({ onOpenVehicle, initialFilters = {} }: { onOpenVehicle + +
+ {resultSummary.map((item) => ( +
+
{item.value}
+
{item.label}
+
+ ))} +
+
{rows.length === 0 && !loading ? ( diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index 375a974b..a722548d 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -199,6 +199,34 @@ body { font-size: 13px; } +.vp-result-summary-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.vp-result-summary-item { + min-height: 72px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: var(--vp-radius); + background: #fbfcff; +} + +.vp-result-summary-value { + color: var(--vp-text); + font-size: 22px; + font-weight: 700; + line-height: 28px; +} + +.vp-result-summary-label { + margin-top: 4px; + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + .vp-map { position: relative; height: 360px; 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 af36a63f..e64c717a 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -177,6 +177,74 @@ test('opens vehicle list filtered by service summary KPI', async () => { expect(window.location.hash).toBe('#/vehicles?coverage=multi'); }); +test('shows vehicle service result summary on vehicle list filters', async () => { + window.history.replaceState(null, '', '/#/vehicles?coverage=multi'); + vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { + const path = String(input); + if (path.includes('/api/ops/health')) { + return { + ok: true, + json: async () => ({ + data: { linkHealth: [], kafkaLag: 0, redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/vehicles/coverage')) { + return { + ok: true, + json: async () => ({ + data: { + items: [{ + vin: 'VIN-MULTI-001', + plate: '粤A服务1', + phone: '13307795425', + oem: 'G7s', + protocols: ['GB32960', 'JT808'], + sourceCount: 2, + onlineSourceCount: 1, + online: true, + lastSeen: '2026-07-03 20:12:10', + bindingStatus: 'bound', + serviceStatus: { + status: 'degraded', + severity: 'warning', + title: '部分来源离线', + detail: '1/2 个来源在线', + sourceCount: 2, + onlineSourceCount: 1 + } + }], + total: 181, + limit: 20, + 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.findByText('当前车辆结果')).toBeInTheDocument(); + expect(screen.getByText('181')).toBeInTheDocument(); + expect(screen.getByText('过滤车辆')).toBeInTheDocument(); + expect(screen.getByText('本页在线')).toBeInTheDocument(); + expect(screen.getByText('本页多源')).toBeInTheDocument(); + expect(screen.getByText('VIN-MULTI-001')).toBeInTheDocument(); +}); + test('shows resolved vehicle service status after topbar search', async () => { window.history.replaceState(null, '', '/#/dashboard'); const fetchMock = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {