From 695cecc07b1f6a9df50e4bc5b2ef614fbdff0316 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 14:12:58 +0800 Subject: [PATCH] feat(platform): add mileage next actions --- .../apps/web/src/pages/Mileage.tsx | 84 +++++++++++++++++++ .../apps/web/src/styles/global.css | 74 ++++++++++++++++ .../apps/web/src/test/App.test.tsx | 7 ++ 3 files changed, 165 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx index 2809c7b6..228506b8 100644 --- a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx @@ -1175,6 +1175,58 @@ export function Mileage({ onSecondary: copyStatisticsSummary } ]; + const mileageNextActions = [ + { + title: publishAuditConclusion === '阻断发布' ? '先解除发布阻断' : publishAuditConclusion === '复核后发布' ? '先完成发布复核' : '可交付里程报告', + value: publishAuditConclusion, + detail: publishAuditConclusion === '可发布' + ? '闭合、覆盖和异常检查通过后,可复制交付包或导出明细给客户。' + : '先处理闭合、覆盖、异常和在线影响,再进入客户交付。', + color: publishAuditColor, + action: publishAuditConclusion === '可发布' ? '复制交付包' : '发布审计', + disabled: false, + onClick: publishAuditConclusion === '可发布' ? copyMileageDeliveryPackage : copyPublishAudit + }, + { + title: anomalyRows.length > 0 ? '优先复核异常日' : '按时间窗核对轨迹', + value: anomalyRows.length > 0 ? `${anomalyRows.length.toLocaleString()} 条异常` : mileageRangeText, + detail: anomalyRows[0] + ? `${anomalyRows[0].plate || anomalyRows[0].vin} / ${anomalyRows[0].date} 建议先回放轨迹。` + : '用同一个车辆和时间窗进入轨迹回放,核对位置、速度和里程变化。', + color: anomalyRows.length > 0 ? 'orange' as const : 'blue' as const, + action: '轨迹回放', + disabled: !currentVehicleKeyword || !onOpenHistory, + onClick: () => { + if (anomalyRows[0]) { + openMileageEvidence(anomalyRows[0]); + return; + } + onOpenHistory?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '' }); + } + }, + { + title: pageCoverageRate >= 100 ? '明细覆盖完整' : '拉齐全量明细', + value: formatPercent(pageCoverageRate), + detail: pageCoverageRate >= 100 + ? '当前页已经覆盖筛选范围内全部明细,可直接导出复核。' + : `${rows.length.toLocaleString()} / ${summary.recordCount.toLocaleString()} 条记录在当前页,导出前建议拉齐。`, + color: pageCoverageRate >= 100 ? 'green' as const : 'orange' as const, + action: '导出明细', + disabled: rows.length === 0, + onClick: exportMileage + }, + { + title: offlineVehicleCount > 0 ? '关注离线影响' : '在线状态正常', + value: `${offlineVehicleCount.toLocaleString()} 离线`, + detail: offlineVehicleCount > 0 + ? '离线车辆会影响实时位置、当日里程和告警触达,建议同步运营处理。' + : '当前筛选范围内在线状态未形成主要统计风险。', + color: offlineVehicleCount > 0 ? 'orange' as const : 'green' as const, + action: '在线态势', + disabled: false, + onClick: copyOnlineOperationsReport + } + ]; useEffect(() => { const nextFilters = mergeInitialFilters(initialVin, initialProtocol, initialFilters); @@ -1237,6 +1289,38 @@ export function Mileage({ ))} + +
+
+ + {publishAuditConclusion} + {mileageDeliveryState} + {confidenceStatus} + + 从车辆服务视角安排下一步 + + 里程页不再让客户先理解 GB32960、JT808、MQTT 的差异,而是直接给出交付、轨迹回放、历史查询、导出和在线影响的下一步动作。 + +
+
+ {mileageNextActions.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 e1670a42..351bcd82 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -3687,6 +3687,78 @@ button.vp-realtime-command-item:focus-visible { font-weight: 700; } +.vp-mileage-next-actions { + display: grid; + grid-template-columns: minmax(280px, 0.62fr) minmax(0, 1.38fr); + gap: 16px; +} + +.vp-mileage-next-summary { + min-height: 178px; + padding: 14px; + border: 1px solid rgba(22, 100, 255, 0.18); + border-radius: var(--vp-radius); + background: #f7faff; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-mileage-next-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.vp-mileage-next-action { + min-width: 0; + min-height: 178px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: var(--vp-radius); + background: #fbfcff; + color: inherit; + text-align: left; + font: inherit; + cursor: pointer; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-mileage-next-action:hover, +.vp-mileage-next-action:focus-visible { + border-color: rgba(22, 100, 255, 0.42); + box-shadow: 0 10px 24px rgba(24, 39, 75, 0.08); + outline: none; +} + +.vp-mileage-next-action:disabled { + cursor: not-allowed; + opacity: 0.62; +} + +.vp-mileage-next-action strong { + color: var(--vp-text); + font-size: 20px; + line-height: 26px; + font-weight: 700; + word-break: break-word; +} + +.vp-mileage-next-action span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + +.vp-mileage-next-action em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; +} + .vp-mileage-console-main { min-width: 0; display: grid; @@ -6005,6 +6077,8 @@ button.vp-realtime-command-item:focus-visible { .vp-online-ops-board, .vp-online-ops-grid, .vp-stat-insight-grid, + .vp-mileage-next-actions, + .vp-mileage-next-grid, .vp-mileage-decision-board, .vp-mileage-decision-grid, .vp-stat-definition-grid, 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 8f6da17f..5db6b283 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -8054,6 +8054,13 @@ test('shows vehicle-first statistics domains for one vehicle service', async () expect(screen.getByRole('button', { name: '里程交付 闭合校验 闭合摘要' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '里程交付 异常记录 异常明细' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '里程交付 在线影响 在线态势' })).toBeInTheDocument(); + expect(screen.getByText('里程报告建议')).toBeInTheDocument(); + expect(screen.getByText('从车辆服务视角安排下一步')).toBeInTheDocument(); + expect(screen.getByText('里程页不再让客户先理解 GB32960、JT808、MQTT 的差异,而是直接给出交付、轨迹回放、历史查询、导出和在线影响的下一步动作。')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '里程报告建议 先完成发布复核 发布审计' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '里程报告建议 优先复核异常日 轨迹回放' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '里程报告建议 拉齐全量明细 导出明细' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '里程报告建议 关注离线影响 在线态势' })).toBeInTheDocument(); expect(screen.getByText('统计影响范围')).toBeInTheDocument(); expect(screen.getByRole('button', { name: '复制影响范围' })).toBeInTheDocument(); expect(screen.getByText('影响待复核')).toBeInTheDocument();