diff --git a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx index 4600dec6..e7b4b601 100644 --- a/vehicle-data-platform/apps/web/src/pages/Mileage.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Mileage.tsx @@ -1163,6 +1163,58 @@ export function Mileage({ '在线统计运营态势' ); }; + const mileageDeliveryConsoleItems = [ + { + label: '交付状态', + value: mileageDeliveryState, + detail: mileageDeliveryState === '可交付' + ? '可进入客户查询、BI发布和运营日报。' + : mileageDeliveryState === '暂缓交付' + ? '存在阻断项,先完成闭合和异常复核。' + : '复核闭合、覆盖、异常或在线影响后再交付。', + color: mileageDeliveryColor, + action: '复制交付', + onClick: copyMileageDeliveryPackage + }, + { + label: '区间里程', + value: `${formatKm(summary.totalMileageKm)} km`, + detail: `范围 ${mileageRangeText},统计车辆 ${summary.vehicleCount.toLocaleString()} 辆。`, + color: 'blue' as const, + action: '复制摘要', + onClick: copyStatisticsSummary + }, + { + label: '闭合校验', + value: closureStatus, + detail: closureDeltaKm == null ? '当前页未覆盖全量记录,发布前需要导出或缩小范围。' : `闭合差值 ${formatKm(Math.abs(closureDeltaKm))} km。`, + color: closureStatusColor, + action: '发布审计', + onClick: copyPublishAudit + }, + { + label: '异常复核', + value: `${anomalyRows.length.toLocaleString()} 条`, + detail: anomalyRows[0] ? `${anomalyRows[0].plate || anomalyRows[0].vin} / ${anomalyRows[0].date} 需要复核。` : '当前明细没有异常标记。', + color: anomalyRows.length > 0 ? 'orange' as const : 'green' as const, + action: '轨迹复核', + onClick: () => { + if (anomalyRows[0]) { + openMileageEvidence(anomalyRows[0]); + return; + } + onOpenHistory?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '' }); + } + }, + { + label: '数据导出', + value: `${rows.length.toLocaleString()} 条明细`, + detail: `当前页覆盖 ${formatPercent(pageCoverageRate)},用于客户复盘和BI核对。`, + color: rows.length > 0 ? 'blue' as const : 'grey' as const, + action: '导出明细', + onClick: exportMileage + } + ]; const mileageServiceTasks = [ { title: '发布判断', @@ -1327,6 +1379,33 @@ export function Mileage({ )} /> +
+
+ + 客户里程交付台 + {mileageDeliveryState} + {closureStatus} + + 按自定义时间范围交付里程、闭合校验、异常复核和导出证据。 + 客户看统计页时先拿到可交付结论,再按同一车辆和时间窗进入轨迹、历史明细、发布审计和 CSV 导出。 +
+
+ {mileageDeliveryConsoleItems.map((item) => ( + + ))} +
+
当前车辆:{currentVehicleKeyword || '-'} 数据通道:{currentProtocol || '全部数据通道'} diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index ce5337e2..41fa2ac5 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -4780,6 +4780,92 @@ button.vp-realtime-command-item:focus-visible { gap: 16px; } +.vp-mileage-delivery-console { + margin-top: 16px; + margin-bottom: 16px; + border: 1px solid rgba(22, 100, 255, 0.16); + border-radius: var(--vp-radius); + background: #fff; + display: grid; + grid-template-columns: minmax(280px, 0.58fr) minmax(0, 1.42fr); + overflow: hidden; +} + +.vp-mileage-delivery-console-summary { + padding: 18px; + border-right: 1px solid var(--vp-border); + background: #f7fbff; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-mileage-delivery-console-summary strong { + color: var(--vp-text); + font-size: 18px; + line-height: 26px; +} + +.vp-mileage-delivery-console-summary span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-mileage-delivery-console-grid { + padding: 16px; + background: #fff; + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 10px; +} + +.vp-mileage-delivery-console-item { + min-height: 142px; + padding: 12px; + border: 1px solid rgba(22, 100, 255, 0.14); + border-radius: 8px; + background: #fbfcff; + color: inherit; + cursor: pointer; + font: inherit; + text-align: left; + display: grid; + gap: 8px; + align-content: start; +} + +.vp-mileage-delivery-console-item:hover, +.vp-mileage-delivery-console-item:focus-visible { + border-color: rgba(22, 100, 255, 0.42); + background: #f5f9ff; + box-shadow: var(--vp-shadow-sm); + outline: none; +} + +.vp-mileage-delivery-console-item strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + word-break: break-word; +} + +.vp-mileage-delivery-console-item span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; + word-break: break-word; +} + +.vp-mileage-delivery-console-item em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 18px; + align-self: end; +} + .vp-mileage-customer-query-board { margin-top: 16px; overflow: hidden; @@ -8023,6 +8109,8 @@ button.vp-realtime-command-item:focus-visible { .vp-online-ops-board, .vp-online-ops-grid, .vp-stat-insight-grid, + .vp-mileage-delivery-console, + .vp-mileage-delivery-console-grid, .vp-mileage-next-actions, .vp-mileage-next-grid, .vp-mileage-question-grid, @@ -8090,6 +8178,11 @@ button.vp-realtime-command-item:focus-visible { border-bottom: 1px solid var(--vp-border); } + .vp-mileage-delivery-console-summary { + border-right: 0; + border-bottom: 1px solid var(--vp-border); + } + .vp-customer-cockpit-workflow { grid-template-columns: 1fr; } 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 58d39e5c..6858d840 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -8035,6 +8035,13 @@ test('shows mileage statistics workspace with trend source and definition', asyn render(); expect(await screen.findByText('区间趋势')).toBeInTheDocument(); + expect(screen.getByText('客户里程交付台')).toBeInTheDocument(); + expect(screen.getByText('按自定义时间范围交付里程、闭合校验、异常复核和导出证据。')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户里程交付台 交付状态 复核后交付 复制交付' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户里程交付台 区间里程 140 km 复制摘要' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户里程交付台 闭合校验 闭合通过 发布审计' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户里程交付台 异常复核 1 条 轨迹复核' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户里程交付台 数据导出 4 条明细 导出明细' })).toBeInTheDocument(); expect(screen.getAllByText('来源贡献').length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText('2026-07-01').length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText('2026-07-02').length).toBeGreaterThanOrEqual(1);