From fb4bcbaa5527c16fa56bc419f02d65f7f50335b2 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 4 Jul 2026 20:20:24 +0800 Subject: [PATCH] feat(platform): add mileage evidence package --- .../apps/web/src/pages/Mileage.tsx | 31 ++++++++++++++++++- .../apps/web/src/test/App.test.tsx | 17 ++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx index c9d50cc2..57dd2659 100644 --- a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx @@ -177,6 +177,31 @@ function mileageSummaryText({ ].join('\n'); } +function mileageEvidencePackageText(row: DailyMileageRow) { + const dateFrom = row.date?.trim() ?? ''; + const dateTo = nextDate(dateFrom); + const evidenceFilters = { + ...(dateFrom ? { dateFrom } : {}), + ...(dateTo ? { dateTo } : {}) + }; + const vehicle = row.plate?.trim() ? `${row.plate.trim()} / ${row.vin}` : row.vin; + return [ + '【里程异常证据包】', + `车辆:${vehicle}`, + `数据来源:${row.source || '未知来源'}`, + `统计日期:${row.date || '-'}`, + `起始里程:${formatKm(row.startMileageKm)} km`, + `结束里程:${formatKm(row.endMileageKm)} km`, + `日里程:${formatKm(row.dailyMileageKm)} km`, + `异常等级:${row.anomalySeverity || '正常'}`, + `处置建议:${mileageActionRecommendation(row).detail}`, + `车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: row.vin, protocol: row.source }))}`, + `轨迹证据:${appURL(buildAppHash({ page: 'history', keyword: row.vin, protocol: row.source, filters: evidenceFilters }))}`, + `RAW证据:${appURL(buildAppHash({ page: 'history-query', keyword: row.vin, protocol: row.source, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`, + `统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: row.vin, protocol: row.source, filters: evidenceFilters }))}` + ].join('\n'); +} + async function copyText(value: string, label: string) { const text = value.trim(); if (!text) { @@ -326,6 +351,9 @@ export function Mileage({ ...(dateTo ? { dateTo } : {}) }); }; + const copyMileageEvidence = (row: DailyMileageRow) => { + copyText(mileageEvidencePackageText(row), '里程证据包'); + }; const exportMileage = () => { if (rows.length === 0) { Toast.warning('当前没有可导出的里程明细'); @@ -683,9 +711,10 @@ export function Mileage({ }, { title: '操作', - width: 270, + width: 360, render: (_: unknown, row: DailyMileageRow) => ( + 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 5e83af27..87161d9f 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -6808,6 +6808,11 @@ test('opens same-day raw frame evidence from mileage row', async () => { test('shows mileage anomaly action guidance', async () => { window.history.replaceState(null, '', '/#/mileage?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960'); + 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/ops/health')) { @@ -6869,6 +6874,18 @@ test('shows mileage anomaly action guidance', async () => { expect(await screen.findByText('VIN-MILEAGE-ANOMALY')).toBeInTheDocument(); expect(screen.getByText('核对里程来源')).toBeInTheDocument(); expect(screen.getByText('日里程异常,优先核对当天首末总里程、来源断链和补传数据。')).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: '复制证据' })); + + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【里程异常证据包】')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:粤A异常1 / VIN-MILEAGE-ANOMALY')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据来源:GB32960')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('统计日期:2026-07-03')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('日里程:-3.2 km')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('异常等级:warning')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务:http://localhost:3000/#/detail?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹证据:http://localhost:3000/#/history?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960&dateFrom=2026-07-03&dateTo=2026-07-04')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('RAW证据:http://localhost:3000/#/history-query?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960&tab=raw&dateFrom=2026-07-03&dateTo=2026-07-04&includeFields=true')); + expect(writeText).toHaveBeenCalledWith(expect.stringContaining('统计查询:http://localhost:3000/#/mileage?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960&dateFrom=2026-07-03&dateTo=2026-07-04')); }); test('shows mileage statistics workspace with trend source and definition', async () => {