diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index bf1e811a..daccaae4 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -36,6 +36,10 @@ function sourceEvidenceText(row: VehicleRealtimeRow) { return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; } +function formatPercent(value: number) { + return Number.isFinite(value) ? `${value.toLocaleString(undefined, { maximumFractionDigits: 1 })}%` : '0%'; +} + function serviceStatusWeight(row: VehicleRealtimeRow) { const severity = row.serviceStatus?.severity; if (severity === 'error') return 4; @@ -233,6 +237,10 @@ export function Realtime({ const onlineCount = rows.filter((row) => row.online).length; const locatedCount = rows.filter(isValidCoordinate).length; const degradedCount = rows.filter((row) => row.onlineSourceCount < row.sourceCount).length; + const onlineRate = rows.length > 0 ? (onlineCount / rows.length) * 100 : 0; + const locatedRate = rows.length > 0 ? (locatedCount / rows.length) * 100 : 0; + const degradedRate = rows.length > 0 ? (degradedCount / rows.length) * 100 : 0; + const pageCoverageRate = pagination.total > 0 ? (rows.length / pagination.total) * 100 : 0; const primaryProtocols = new Set(rows.map((row) => row.primaryProtocol).filter(Boolean)); const sourceIssueRows = rows .filter((row) => canOpenVehicle(row.vin) && hasSourceIssue(row)) @@ -417,6 +425,22 @@ export function Realtime({ + +
+ {[ + { label: '在线率', value: formatPercent(onlineRate), color: onlineRate >= 80 ? 'green' as const : 'orange' as const, detail: `${onlineCount.toLocaleString()} / ${rows.length.toLocaleString()} 辆在线。` }, + { label: '定位有效率', value: formatPercent(locatedRate), color: locatedRate >= 80 ? 'green' as const : 'orange' as const, detail: `${locatedCount.toLocaleString()} / ${rows.length.toLocaleString()} 辆坐标有效。` }, + { label: '降级率', value: formatPercent(degradedRate), color: degradedCount > 0 ? 'orange' as const : 'green' as const, detail: `${degradedCount.toLocaleString()} 辆存在来源不完整或离线。` }, + { label: '分页覆盖率', value: formatPercent(pageCoverageRate), color: pageCoverageRate >= 100 ? 'green' as const : 'grey' as const, detail: '当前页车辆数 / 当前筛选总车辆数。' } + ].map((item) => ( + + {item.label} +
{item.value}
+ {item.detail} +
+ ))} +
+
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 827de352..f83dde53 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -7056,6 +7056,13 @@ test('copies realtime operations summary from realtime page', async () => { render(); expect((await screen.findAllByText('VIN-RT-SUMMARY-001')).length).toBeGreaterThan(0); + expect(screen.getByText('实时覆盖判读')).toBeInTheDocument(); + expect(screen.getByText('在线率')).toBeInTheDocument(); + expect(screen.getAllByText('66.7%').length).toBeGreaterThanOrEqual(3); + expect(screen.getByText('定位有效率')).toBeInTheDocument(); + expect(screen.getByText('降级率')).toBeInTheDocument(); + expect(screen.getByText('分页覆盖率')).toBeInTheDocument(); + expect(screen.getByText('100%')).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: '复制实时摘要' })); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【实时监控摘要】'));