From 934691e95670cb5b0a97801ade824a21402fbee2 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 22:55:15 +0800 Subject: [PATCH] feat(platform): add mileage reconciliation conclusion --- .../apps/web/src/pages/Mileage.tsx | 78 +++++++++++++++++++ .../apps/web/src/styles/global.css | 78 +++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 6 ++ 3 files changed, 162 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx index 49a05f31..62a6ec86 100644 --- a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx @@ -968,6 +968,56 @@ export function Mileage({ onClick: () => exportMileage() } ]; + const mileageReconciliationConclusionItems = [ + { + label: '区间总里程', + value: `${formatKm(summary.totalMileageKm)} km`, + detail: `${summary.vehicleCount.toLocaleString()} 辆车,${mileageRangeText}。`, + action: '复制摘要', + color: 'blue' as const, + disabled: false, + onClick: () => copyStatisticsSummary() + }, + { + label: '闭合状态', + value: closureStatus, + detail: closureActionText, + action: '发布审计', + color: closureStatusColor, + disabled: false, + onClick: () => copyPublishAudit() + }, + { + label: '异常记录', + value: `${anomalyRows.length.toLocaleString()} 条`, + detail: anomalyRows[0] + ? `${anomalyRows[0].plate || anomalyRows[0].vin} / ${anomalyRows[0].date}` + : '当前明细没有异常标记。', + action: '复制决策', + color: anomalyRows.length > 0 ? 'orange' as const : 'green' as const, + disabled: false, + onClick: () => copyMileageDecision() + }, + { + label: '下一步', + value: mileageDeliveryState, + detail: mileageDeliveryState === '可交付' ? '可以进入客户交付、BI复核或导出。' : '先补齐闭合、覆盖、异常或在线影响证据。', + action: '查看证据', + color: mileageDeliveryColor, + disabled: !currentVehicleKeyword || (!onOpenHistory && !onOpenRaw), + onClick: () => { + if (currentVehicleKeyword && onOpenHistory) { + onOpenHistory({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '' }); + return; + } + if (currentVehicleKeyword && onOpenRaw) { + onOpenRaw({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '', includeFields: 'true' }); + return; + } + copyMileageDeliveryPackage(); + } + } + ]; const mileageDeliveryNavigation = [ { title: '确认统计口径', @@ -1533,6 +1583,34 @@ export function Mileage({ ))} +
+
+ + 里程对账结论 + {mileageDeliveryState} + + + 先回答客户这段时间跑了多少、闭合是否通过、异常是否需要复核,再进入轨迹、历史和导出。 + +
+
+ {mileageReconciliationConclusionItems.map((item) => ( + + ))} +
+
diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index 8ec3b1ad..902452c1 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -7389,6 +7389,82 @@ button.vp-realtime-command-item:focus-visible { overflow: hidden; } +.vp-mileage-reconciliation-conclusion { + margin-top: 16px; + border: 1px solid rgba(37, 99, 235, 0.18); + border-radius: var(--vp-radius); + background: #fff; + display: grid; + grid-template-columns: minmax(280px, 0.48fr) minmax(0, 1.52fr); + overflow: hidden; +} + +.vp-mileage-reconciliation-conclusion-summary { + padding: 18px; + border-right: 1px solid var(--vp-border); + background: #f6f9ff; + display: grid; + gap: 11px; + align-content: center; +} + +.vp-mileage-reconciliation-conclusion-grid { + padding: 14px; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; +} + +.vp-mileage-reconciliation-conclusion-item { + min-height: 132px; + padding: 12px; + border: 1px solid rgba(37, 99, 235, 0.16); + border-radius: 8px; + background: #fbfcff; + color: inherit; + cursor: pointer; + font: inherit; + text-align: left; + display: grid; + gap: 8px; + align-content: start; +} + +.vp-mileage-reconciliation-conclusion-item:hover, +.vp-mileage-reconciliation-conclusion-item:focus-visible { + border-color: rgba(37, 99, 235, 0.42); + background: #f6f9ff; + box-shadow: var(--vp-shadow-sm); + outline: none; +} + +.vp-mileage-reconciliation-conclusion-item:disabled { + cursor: not-allowed; + opacity: 0.62; +} + +.vp-mileage-reconciliation-conclusion-item strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + word-break: break-word; +} + +.vp-mileage-reconciliation-conclusion-item span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; + word-break: break-word; +} + +.vp-mileage-reconciliation-conclusion-item em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 18px; +} + .vp-mileage-reconciliation-summary { padding: 18px; border-right: 1px solid var(--vp-border); @@ -11396,6 +11472,8 @@ button.vp-realtime-command-item:focus-visible { .vp-stat-insight-grid, .vp-mileage-customer-workbench, .vp-mileage-customer-workbench-grid, + .vp-mileage-reconciliation-conclusion, + .vp-mileage-reconciliation-conclusion-grid, .vp-mileage-reconciliation-desk, .vp-mileage-reconciliation-grid, .vp-mileage-delivery-console, 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 c268c320..c3c946e6 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -8376,6 +8376,12 @@ test('shows vehicle and source scope on mileage hash', async () => { expect(await screen.findByText('当前车辆:VIN-MILEAGE-001')).toBeInTheDocument(); expect(screen.getAllByText('数据通道:GB32960').length).toBeGreaterThan(0); + expect(screen.getByText('里程对账结论')).toBeInTheDocument(); + expect(screen.getByText('先回答客户这段时间跑了多少、闭合是否通过、异常是否需要复核,再进入轨迹、历史和导出。')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '里程对账结论 区间总里程 12.3 km 复制摘要' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '里程对账结论 闭合状态 分页抽样 发布审计' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '里程对账结论 异常记录 0 条 复制决策' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '里程对账结论 下一步 复核后交付 查看证据' })).toBeInTheDocument(); expect(screen.getByText('客户里程问题')).toBeInTheDocument(); expect(screen.getByRole('button', { name: '客户里程问题 这段时间总共跑了多少? 复制摘要' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '客户里程问题 这份里程能给客户或 BI 吗? 发布审计' })).toBeInTheDocument();