From e8f0ebff3592fff7cfa8d77505e32a6f1a79b122 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 20:44:07 +0800 Subject: [PATCH] feat(platform): add online status handoff --- .../apps/web/src/pages/Mileage.tsx | 50 +++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 12 +++++ 2 files changed, 62 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx index 57dd2659..31dec0ff 100644 --- a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx @@ -202,6 +202,44 @@ function mileageEvidencePackageText(row: DailyMileageRow) { ].join('\n'); } +function onlineStatusHandoffText({ + filters, + summary, + rows, + total +}: { + filters: Record; + summary: OnlineStatisticsSummary; + rows: OnlineVehicleStatusRow[]; + total: number; +}) { + const keyword = filters.keyword?.trim() || '全部车辆'; + const protocol = filters.protocol?.trim() || '全部来源'; + const offlineRows = rows.filter((row) => !row.online); + const protocolLines = (summary.protocolStats ?? []).length > 0 + ? summary.protocolStats.map((item) => `${item.protocol} ${item.online.toLocaleString()}/${item.total.toLocaleString()}`).join(';') + : '-'; + return [ + '【车辆在线状态交接】', + `车辆范围:${keyword}`, + `数据来源:${protocol}`, + `在线概览:${summary.onlineVehicleCount.toLocaleString()} 在线 / ${summary.offlineVehicleCount.toLocaleString()} 离线 / 在线率 ${formatPercent(summary.onlineRatePercent)}`, + `Redis 在线 Key:${summary.redisOnlineKeys == null ? '-' : summary.redisOnlineKeys.toLocaleString()}`, + `协议在线:${protocolLines}`, + `统计证据:${summary.evidence || '-'}`, + `当前页离线:${offlineRows.length.toLocaleString()} 辆 / 当前筛选 ${total.toLocaleString()} 辆`, + '', + '离线车辆:', + ...(offlineRows.length > 0 ? offlineRows.map((row, index) => [ + `${index + 1}. ${row.plate || '-'} / ${row.vin || '-'} / ${row.oem || '-'}`, + ` 离线时长:${formatOfflineDuration(row.offlineDurationMinutes)};最后上报:${row.lastSeen || '-'}`, + ` 来源覆盖:${row.onlineSourceCount}/${row.sourceCount};协议:${(row.protocols ?? []).join('、') || '-'}`, + ` 车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: row.vin, protocol: filters.protocol?.trim() || row.protocols?.[0] || '' }))}`, + ` 实时监控:${appURL(buildAppHash({ page: 'realtime', keyword: row.vin, protocol: filters.protocol?.trim() || row.protocols?.[0] || '' }))}` + ].join('\n')) : ['当前页暂无离线车辆']) + ].join('\n'); +} + async function copyText(value: string, label: string) { const text = value.trim(); if (!text) { @@ -379,6 +417,17 @@ export function Mileage({ '统计摘要' ); }; + const copyOnlineStatusHandoff = () => { + copyText( + onlineStatusHandoffText({ + filters, + summary: onlineSummary, + rows: onlineRows, + total: onlinePagination.total + }), + '在线状态交接' + ); + }; useEffect(() => { const nextFilters = mergeInitialFilters(initialVin, initialProtocol, initialFilters); @@ -592,6 +641,7 @@ export function Mileage({ 在线 {onlineVehicleCount.toLocaleString()} 车 离线 {offlineVehicleCount.toLocaleString()} 车 按车辆服务归并 VIN,展示最近上报时间、离线时长和来源覆盖。 + { window.history.replaceState(null, '', '/#/mileage?keyword=VIN-STATS-SERVICE&protocol=JT808'); + 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/mileage/summary')) { @@ -7145,6 +7150,13 @@ test('shows vehicle-first statistics domains for one vehicle service', async () expect(await screen.findByText('VIN-STATS-SERVICE-OFFLINE')).toBeInTheDocument(); expect(screen.getByText('粤A离线1')).toBeInTheDocument(); expect(screen.getByText('2 小时')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: '复制在线状态清单' })); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆在线状态交接】')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆范围:VIN-STATS-SERVICE')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('在线概览:3 在线 / 1 离线 / 在线率 75%')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('协议在线:JT808 2/3;GB32960 1/2')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. 粤A离线1 / VIN-STATS-SERVICE-OFFLINE / 广安车联')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('离线时长:2 小时;最后上报:2026-07-03 18:12:10')); }); test('copies mileage statistics summary for operations reporting', async () => {