From 5a133b0bcdf572554bfa630be262a815dcd03751 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 12:23:53 +0800 Subject: [PATCH] feat(platform): surface realtime source consistency --- .../apps/web/src/pages/Realtime.tsx | 59 ++++++++++ .../apps/web/src/styles/global.css | 8 ++ .../apps/web/src/test/App.test.tsx | 102 ++++++++++++++++++ 3 files changed, 169 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index 19212f95..55b50ef4 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -43,6 +43,20 @@ function serviceStatusWeight(row: VehicleRealtimeRow) { return 1; } +function sourceIssueTags(row: VehicleRealtimeRow) { + const tags = (row.sourceStatus ?? []) + .filter((source) => !source.online || !source.hasRealtime) + .map((source) => `${source.protocol} ${source.hasRealtime ? '离线' : '未接入'}`) ?? []; + if (tags.length > 0) return tags; + if (row.onlineSourceCount < row.sourceCount) return ['来源缺失']; + return []; +} + +function hasSourceIssue(row: VehicleRealtimeRow) { + const severity = row.serviceStatus?.severity; + return severity === 'warning' || severity === 'error' || row.onlineSourceCount < row.sourceCount || sourceIssueTags(row).length > 0; +} + function isValidCoordinate(row: VehicleRealtimeRow) { return Number.isFinite(row.longitude) && Number.isFinite(row.latitude) && row.longitude !== 0 && row.latitude !== 0; } @@ -126,6 +140,14 @@ export function Realtime({ const locatedCount = rows.filter(isValidCoordinate).length; const degradedCount = rows.filter((row) => row.onlineSourceCount < row.sourceCount).length; const primaryProtocols = new Set(rows.map((row) => row.primaryProtocol).filter(Boolean)); + const sourceIssueRows = rows + .filter((row) => canOpenVehicle(row.vin) && hasSourceIssue(row)) + .sort((a, b) => { + const statusDelta = serviceStatusWeight(b) - serviceStatusWeight(a); + if (statusDelta !== 0) return statusDelta; + return String(b.lastSeen ?? '').localeCompare(String(a.lastSeen ?? '')); + }) + .slice(0, 4); const mapServiceRows = rows .filter((row) => isValidCoordinate(row) && canOpenVehicle(row.vin)) .sort((a, b) => { @@ -217,6 +239,43 @@ export function Realtime({ 高德 Web JS Key 和安全密钥通过运行环境配置,前端只读取公开 Key;安全密钥后续走服务端代理,避免明文下发。 +
+
多来源一致性检查
+ {sourceIssueRows.length === 0 ? ( + 当前页车辆来源一致 + ) : ( + sourceIssueRows.map((row) => { + const status = vehicleServiceStatus(row); + return ( +
+
+
+ {row.plate || row.vin} + {row.vin} +
+ {status.label} +
+ + {sourceEvidenceText(row)} + {sourceIssueTags(row).map((item) => ( + 一致性:{item} + ))} + + + 一致性诊断:{row.serviceStatus?.detail || sourceEvidenceText(row)} + +
+ {row.lastSeen || '-'} + + + + +
+
+ ); + }) + )} +
地图车辆服务队列
{mapServiceRows.length === 0 ? ( diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index f8b1480a..938ed138 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -385,6 +385,14 @@ body { border-top: 1px solid var(--vp-border); } +.vp-source-consistency-panel { + display: grid; + gap: 10px; + margin-top: 4px; + padding-top: 12px; + border-top: 1px solid var(--vp-border); +} + .vp-map-service-queue-title { color: var(--vp-text); font-size: 13px; 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 cb93bb06..931cd9e5 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -5289,6 +5289,108 @@ test('opens amap coordinate and trajectory playback from realtime map service qu expect(window.location.hash).toBe('#/history?keyword=VIN-MAP-LINK&protocol=GB32960'); }); +test('surfaces multi-source realtime consistency issues with quality drilldown', 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-CONSISTENCY', + plate: '粤A一致监控', + phone: '13307795425', + oem: 'G7s', + protocols: ['GB32960', 'JT808', 'YUTONG_MQTT'], + sourceStatus: [ + { protocol: 'GB32960', online: true, lastSeen: '2026-07-03 20:12:10', hasRealtime: true, hasHistory: true, hasRaw: true, hasMileage: true }, + { protocol: 'JT808', online: true, lastSeen: '2026-07-03 20:12:05', hasRealtime: true, hasHistory: true, hasRaw: true, hasMileage: true }, + { protocol: 'YUTONG_MQTT', online: false, lastSeen: '2026-07-03 20:01:40', hasRealtime: true, hasHistory: false, hasRaw: true, hasMileage: false } + ], + sourceCount: 3, + 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: 'degraded', + severity: 'warning', + title: '来源不完整', + detail: 'YUTONG_MQTT 离线,GB32960/JT808 仍可支撑车辆服务', + sourceCount: 3, + onlineSourceCount: 2 + } + }], + total: 1, + limit: 50, + offset: 0 + }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/quality/summary')) { + return { + ok: true, + json: async () => ({ + data: { issueVehicleCount: 0, issueRecordCount: 0, errorCount: 0, warningCount: 0, protocols: [], issueTypes: [] }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + if (path.includes('/api/quality/issues')) { + return { + ok: true, + json: async () => ({ + data: { items: [], total: 0, limit: 50, offset: 0 }, + traceId: 'trace-test', + timestamp: 1783094400000 + }) + } as Response; + } + 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; + } + 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.getAllByText('粤A一致监控').length).toBeGreaterThan(0); + expect(screen.getByText('一致性诊断:YUTONG_MQTT 离线,GB32960/JT808 仍可支撑车辆服务')).toBeInTheDocument(); + expect(screen.getByText('一致性:YUTONG_MQTT 离线')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: '检查质量' })); + + await waitFor(() => { + expect(window.location.hash).toBe('#/quality?keyword=VIN-RT-CONSISTENCY&protocol=GB32960'); + }); +}); + test('opens quality issues from realtime vehicle row with source evidence', async () => { window.history.replaceState(null, '', '/#/realtime?protocol=JT808'); vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {