diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index 8c700359..d41dabc3 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -765,6 +765,36 @@ export function Dashboard({ ].filter(Boolean); copyText(lines.join('\n'), '运营交接摘要'); }; + const copySourceReadinessHandoff = () => { + const plan = sourceReadiness; + if (!plan?.sources?.length) { + Toast.warning('暂无数据源就绪度数据'); + return; + } + const lines = [ + '【三源接入到车辆服务交接摘要】', + `车辆规模:${formatCount(plan.onlineVehicles)} / ${formatCount(plan.totalVehicles)} 在线`, + `接入运行:连接 ${formatCount(plan.activeConnections)};Redis 在线 Key ${formatCount(plan.redisOnlineKeys)};Kafka Lag ${formatLag(plan.kafkaLag)}`, + `平台版本:${plan.platformRelease || '-'}`, + '', + ...plan.sources.map((item, index) => [ + `${index + 1}. ${item.protocol} - ${item.status}`, + ` 角色:${item.role}`, + ` 在线:${formatCount(item.online)} / ${formatCount(item.total)},在线率 ${item.onlineRate.toLocaleString(undefined, { maximumFractionDigits: 1 })}%`, + ` 缺失车辆:${formatCount(item.missingVehicles)}`, + ` 证据:${item.evidence}`, + ` 动作:${item.action}`, + ` 验收:${item.acceptance}`, + ` 实时:${appURL(item.realtimeHash)}`, + ` 车辆:${appURL(item.vehiclesHash)}`, + ` RAW:${appURL(item.historyHash)}`, + ` 告警:${appURL(item.alertHash)}` + ].join('\n')), + '', + `驾驶舱:${appURL(buildAppHash({ page: 'dashboard' }))}` + ]; + copyText(lines.join('\n'), '三源接入交接摘要'); + }; const copyScenarioBlueprint = () => { const lines = [ '【车辆数据中台功能蓝图】', @@ -965,7 +995,11 @@ export function Dashboard({ - + 数据源到车辆服务就绪度} + style={{ marginBottom: 16 }} + >
{(sourceReadiness?.sources ?? []).map((item) => (
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 120319f9..85dab955 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -174,146 +174,6 @@ test('exposes AMap operations shortcuts when map key is configured', async () => }) } as Response; } - if (path.includes('/api/ops/source-readiness')) { - return { - ok: true, - json: async () => ({ - data: { - totalVehicles: 1033, - onlineVehicles: 208, - kafkaLag: 0, - activeConnections: 1234, - redisOnlineKeys: 368, - platformRelease: 'platform-dashboard-test', - sources: [ - { - protocol: 'GB32960', - role: '整车与氢能实时数据主来源', - online: 73, - total: 340, - onlineRate: 21.47, - missingVehicles: 693, - severity: 'warning', - status: '覆盖不足', - evidence: '在线 73/340,缺失车辆 693,Kafka Lag 0', - action: '核对现代和交投平台转发清单。', - acceptance: '缺失来源车辆下降,单源车辆可解释。', - vehiclesHash: '#/vehicles?protocol=GB32960&missingProtocol=GB32960', - realtimeHash: '#/realtime?protocol=GB32960&online=online', - historyHash: '#/history-query?protocol=GB32960&tab=raw&includeFields=true', - alertHash: '#/alert-events?protocol=GB32960' - }, - { - protocol: 'JT808', - role: '位置、总里程和设备在线主来源', - online: 174, - total: 424, - onlineRate: 41.04, - missingVehicles: 609, - severity: 'warning', - status: '覆盖不足', - evidence: '在线 174/424,缺失车辆 609,Kafka Lag 0', - action: '补齐手机号绑定和注册缺失车辆。', - acceptance: '未知 VIN 注册下降,位置与里程持续可查。', - vehiclesHash: '#/vehicles?protocol=JT808&missingProtocol=JT808', - realtimeHash: '#/realtime?protocol=JT808&online=online', - historyHash: '#/history-query?protocol=JT808&tab=raw&includeFields=true', - alertHash: '#/alert-events?protocol=JT808' - }, - { - protocol: 'YUTONG_MQTT', - role: '宇通派生工况与燃料电池补充来源', - online: 11, - total: 50, - onlineRate: 22, - missingVehicles: 983, - severity: 'ok', - status: '生产可用', - evidence: '在线 11/50,缺失车辆 983,Kafka Lag 0', - action: '保持实时、轨迹、RAW 和统计链路巡检。', - acceptance: '车辆服务可回查实时、轨迹和 RAW 证据。', - vehiclesHash: '#/vehicles?protocol=YUTONG_MQTT&missingProtocol=YUTONG_MQTT', - realtimeHash: '#/realtime?protocol=YUTONG_MQTT&online=online', - historyHash: '#/history-query?protocol=YUTONG_MQTT&tab=raw&includeFields=true', - alertHash: '#/alert-events?protocol=YUTONG_MQTT' - } - ] - }, - traceId: 'trace-test', - timestamp: 1783094400000 - }) - } as Response; - } - if (path.includes('/api/ops/source-readiness')) { - return { - ok: true, - json: async () => ({ - data: { - totalVehicles: 1033, - onlineVehicles: 208, - kafkaLag: 0, - activeConnections: 1234, - redisOnlineKeys: 368, - platformRelease: 'platform-dashboard-test', - sources: [ - { - protocol: 'GB32960', - role: '整车与氢能实时数据主来源', - online: 73, - total: 340, - onlineRate: 21.47, - missingVehicles: 693, - severity: 'warning', - status: '覆盖不足', - evidence: '在线 73/340,缺失车辆 693,Kafka Lag 0', - action: '核对现代和交投平台转发清单。', - acceptance: '缺失来源车辆下降,单源车辆可解释。', - vehiclesHash: '#/vehicles?protocol=GB32960&missingProtocol=GB32960', - realtimeHash: '#/realtime?protocol=GB32960&online=online', - historyHash: '#/history-query?protocol=GB32960&tab=raw&includeFields=true', - alertHash: '#/alert-events?protocol=GB32960' - }, - { - protocol: 'JT808', - role: '位置、总里程和设备在线主来源', - online: 174, - total: 424, - onlineRate: 41.04, - missingVehicles: 609, - severity: 'warning', - status: '覆盖不足', - evidence: '在线 174/424,缺失车辆 609,Kafka Lag 0', - action: '补齐手机号绑定和注册缺失车辆。', - acceptance: '未知 VIN 注册下降,位置与里程持续可查。', - vehiclesHash: '#/vehicles?protocol=JT808&missingProtocol=JT808', - realtimeHash: '#/realtime?protocol=JT808&online=online', - historyHash: '#/history-query?protocol=JT808&tab=raw&includeFields=true', - alertHash: '#/alert-events?protocol=JT808' - }, - { - protocol: 'YUTONG_MQTT', - role: '宇通派生工况与燃料电池补充来源', - online: 11, - total: 50, - onlineRate: 22, - missingVehicles: 983, - severity: 'ok', - status: '生产可用', - evidence: '在线 11/50,缺失车辆 983,Kafka Lag 0', - action: '保持实时、轨迹、RAW 和统计链路巡检。', - acceptance: '车辆服务可回查实时、轨迹和 RAW 证据。', - vehiclesHash: '#/vehicles?protocol=YUTONG_MQTT&missingProtocol=YUTONG_MQTT', - realtimeHash: '#/realtime?protocol=YUTONG_MQTT&online=online', - historyHash: '#/history-query?protocol=YUTONG_MQTT&tab=raw&includeFields=true', - alertHash: '#/alert-events?protocol=YUTONG_MQTT' - } - ] - }, - traceId: 'trace-test', - timestamp: 1783094400000 - }) - } as Response; - } return { ok: true, json: async () => ({ @@ -983,6 +843,10 @@ test('dashboard exposes vehicle data center capability matrix', async () => { fireEvent.click(screen.getByRole('button', { name: '复制流转图' })); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆数据中台数据流转图】')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('03. 实时投影 / Redis KV')); + fireEvent.click(screen.getByRole('button', { name: '复制接入交接' })); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【三源接入到车辆服务交接摘要】')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. GB32960 - 覆盖不足')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('RAW:http://localhost:3000/#/history-query?protocol=GB32960&tab=raw&includeFields=true')); fireEvent.click(screen.getAllByRole('button', { name: '实时' })[0]); expect(window.location.hash).toBe('#/realtime?protocol=GB32960&online=online');