From a005dba2d4a262978ef13cd2f2e469aa0e373f8f Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 22:39:10 +0800 Subject: [PATCH] feat(platform): add realtime operations impact board --- .../apps/web/src/pages/Realtime.tsx | 115 ++++++++++++++++++ .../apps/web/src/styles/global.css | 44 +++++++ .../apps/web/src/test/App.test.tsx | 22 ++++ 3 files changed, 181 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index fb91b737..79e7dbe6 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -396,6 +396,46 @@ function realtimeDutyHandoffText({ return lines.join('\n'); } +function realtimeImpactReportText({ + filters, + rows, + total, + onlineCount, + locatedCount, + degradedCount, + staleCount, + pageCoverageRate, + amapConfigured, + runtimeRelease +}: { + filters: Record; + rows: VehicleRealtimeRow[]; + total: number; + onlineCount: number; + locatedCount: number; + degradedCount: number; + staleCount: number; + pageCoverageRate: number; + amapConfigured: boolean; + runtimeRelease?: string; +}) { + const offlineCount = Math.max(0, rows.length - onlineCount); + const impactLevel = degradedCount > 0 || staleCount > 0 || offlineCount > 0 ? '需要处置' : '实时稳定'; + return [ + '【实时运营影响】', + `当前筛选:${realtimeFilterSummary(filters).join(';') || '全部实时车辆'}`, + `运行版本:${runtimeRelease || '未标记'}`, + `影响等级:${impactLevel}`, + `车辆范围:当前页 ${rows.length.toLocaleString()} / 总计 ${total.toLocaleString()},覆盖率 ${formatPercent(pageCoverageRate)}`, + `在线状态:${onlineCount.toLocaleString()} 在线 / ${offlineCount.toLocaleString()} 离线`, + `定位影响:${locatedCount.toLocaleString()} 辆有有效坐标`, + `服务影响:${degradedCount.toLocaleString()} 辆降级 / ${staleCount.toLocaleString()} 辆超时`, + `地图能力:${amapConfigured ? '高德地图可用' : '高德地图未配置,使用坐标预览'}`, + `建议动作:${impactLevel === '实时稳定' ? '保持监控并关注告警队列' : '优先处理离线、超时和来源不完整车辆,并回到轨迹/RAW证据复核'}`, + `实时监控:${appURL(buildAppHash({ page: 'realtime', protocol: filters.protocol, filters }))}` + ].join('\n'); +} + function buildRealtimeSourceCoverage(rows: VehicleRealtimeRow[]) { const sourceMap = new Map(); rows.forEach((row) => { @@ -624,6 +664,52 @@ export function Realtime({ amapConfigured, runtimeRelease: runtime?.platformRelease }), '实时值班交接包'); + const realtimeImpactLevel = degradedCount > 0 || staleCount > 0 || rows.length - onlineCount > 0 ? '需要处置' : '实时稳定'; + const realtimeImpactColor = realtimeImpactLevel === '实时稳定' ? 'green' as const : degradedCount > 0 || staleCount > 0 ? 'orange' as const : 'blue' as const; + const realtimeImpactItems = [ + { + label: '车辆范围', + value: `${rows.length.toLocaleString()} / ${pagination.total.toLocaleString()}`, + detail: `当前页覆盖 ${formatPercent(pageCoverageRate)}`, + color: pageCoverageRate >= 100 ? 'green' as const : 'grey' as const + }, + { + label: '在线影响', + value: `${onlineCount.toLocaleString()} 在线`, + detail: `${(rows.length - onlineCount).toLocaleString()} 辆离线`, + color: rows.length - onlineCount > 0 ? 'orange' as const : 'green' as const + }, + { + label: '定位影响', + value: `${locatedCount.toLocaleString()} 辆`, + detail: `有效定位率 ${formatPercent(locatedRate)}`, + color: locatedCount > 0 ? 'blue' as const : 'orange' as const + }, + { + label: '服务影响', + value: `${degradedCount.toLocaleString()} 降级`, + detail: `${staleCount.toLocaleString()} 辆更新超时`, + color: degradedCount > 0 || staleCount > 0 ? 'orange' as const : 'green' as const + }, + { + label: '地图能力', + value: amapConfigured ? '可用' : '坐标预览', + detail: amapConfigured ? '高德地图配置就绪' : '高德未配置,页面降级展示坐标。', + color: amapConfigured ? 'green' as const : 'orange' as const + } + ]; + const copyRealtimeImpact = () => copyText(realtimeImpactReportText({ + filters, + rows, + total: pagination.total, + onlineCount, + locatedCount, + degradedCount, + staleCount, + pageCoverageRate, + amapConfigured, + runtimeRelease: runtime?.platformRelease + }), '实时运营影响'); const selectRealtimeRow = (row: VehicleRealtimeRow) => { const index = rows.indexOf(row); if (index < 0) return; @@ -931,6 +1017,35 @@ export function Realtime({ + 实时运营影响} + style={{ marginBottom: 16 }} + > +
+
+ {realtimeImpactLevel} +
{pagination.total.toLocaleString()} 辆
+ + 当前页 {rows.length.toLocaleString()} 辆,在线 {onlineCount.toLocaleString()} 辆,定位有效 {locatedCount.toLocaleString()} 辆,降级 {degradedCount.toLocaleString()} 辆,超时 {staleCount.toLocaleString()} 辆。 + + + = 100 ? 'green' : 'grey'}>覆盖 {formatPercent(pageCoverageRate)} + {amapConfigured ? '高德可用' : '坐标预览'} + 0 ? 'orange' : 'green'}>{(rows.length - onlineCount).toLocaleString()} 辆离线 + +
+
+ {realtimeImpactItems.map((item) => ( +
+ {item.label} + {item.value} + {item.detail} +
+ ))} +
+
+
{[ diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index ccdb976f..be7fdc36 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -344,6 +344,48 @@ button.vp-realtime-command-item:focus-visible { line-height: 28px; } +.vp-realtime-impact-board { + display: grid; + grid-template-columns: minmax(260px, 0.72fr) minmax(0, 1.28fr); + gap: 16px; +} + +.vp-realtime-impact-summary { + min-height: 156px; + padding: 12px; + border: 1px solid rgba(22, 100, 255, 0.28); + border-radius: var(--vp-radius); + background: #f5f9ff; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-realtime-impact-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 12px; +} + +.vp-realtime-impact-item { + min-height: 156px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: var(--vp-radius); + background: #fbfcff; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-realtime-impact-item strong { + color: var(--vp-text); + font-size: 20px; + line-height: 26px; + font-weight: 700; + word-break: break-word; +} + .vp-source-coverage-board { margin-bottom: 16px; padding: 14px; @@ -2056,6 +2098,8 @@ button.vp-realtime-command-item:focus-visible { .vp-current-service-grid, .vp-realtime-command-board, .vp-realtime-command-grid, + .vp-realtime-impact-board, + .vp-realtime-impact-grid, .vp-source-coverage-grid, .vp-runbook-grid, .vp-workbench-grid, 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 eae70440..e493f847 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -8332,6 +8332,11 @@ test('shows canonical service status in realtime vehicles', async () => { test('frames realtime page as one vehicle service with source evidence', async () => { window.history.replaceState(null, '', '/#/realtime'); + const writeText = vi.fn(() => Promise.resolve()); + Object.defineProperty(navigator, 'clipboard', { + configurable: true, + value: { writeText } + }); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { const path = String(input); if (path.includes('/api/realtime/vehicles')) { @@ -8400,6 +8405,23 @@ test('frames realtime page as one vehicle service with source evidence', async ( expect(screen.getByText('JT808 在线')).toBeInTheDocument(); expect(screen.getByText('YUTONG_MQTT 未接入')).toBeInTheDocument(); expect(screen.getAllByText('2/2 来源在线').length).toBeGreaterThan(0); + expect(screen.getByText('实时运营影响')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /复制影响报告/ })).toBeInTheDocument(); + expect(screen.getByText('需要处置')).toBeInTheDocument(); + expect(screen.getByText('车辆范围')).toBeInTheDocument(); + expect(screen.getByText('在线影响')).toBeInTheDocument(); + expect(screen.getByText('定位影响')).toBeInTheDocument(); + expect(screen.getByText('服务影响')).toBeInTheDocument(); + expect(screen.getByText('地图能力')).toBeInTheDocument(); + expect(screen.getByText('当前页 1 辆,在线 1 辆,定位有效 1 辆,降级 0 辆,超时 1 辆。')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: /复制影响报告/ })); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【实时运营影响】')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('影响等级:需要处置')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆范围:当前页 1 / 总计 1,覆盖率 100%')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('在线状态:1 在线 / 0 离线')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('定位影响:1 辆有有效坐标')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('服务影响:0 辆降级 / 1 辆超时')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('地图能力:高德地图未配置,使用坐标预览')); }); test('refreshes realtime vehicle data from the monitoring workspace', async () => {