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);