diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css
index 85cf5b2f..73b28532 100644
--- a/vehicle-data-platform/apps/web/src/styles/global.css
+++ b/vehicle-data-platform/apps/web/src/styles/global.css
@@ -10206,6 +10206,80 @@ button.vp-realtime-command-item:focus-visible {
align-self: end;
}
+.vp-mileage-report-journey {
+ margin-top: 16px;
+ border: 1px solid rgba(22, 100, 255, 0.18);
+ border-radius: var(--vp-radius);
+ background: #f8fbff;
+ display: grid;
+ grid-template-columns: minmax(280px, 0.68fr) minmax(0, 1.32fr);
+ gap: 14px;
+ padding: 16px;
+}
+
+.vp-mileage-report-journey-copy {
+ display: grid;
+ align-content: start;
+ gap: 10px;
+}
+
+.vp-mileage-report-journey-copy .semi-button {
+ width: fit-content;
+}
+
+.vp-mileage-report-journey-steps {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.vp-mileage-report-journey-step {
+ min-height: 138px;
+ padding: 12px;
+ border: 1px solid var(--vp-border);
+ border-radius: var(--vp-radius);
+ background: #fff;
+ color: inherit;
+ cursor: pointer;
+ font: inherit;
+ text-align: left;
+ display: grid;
+ align-content: start;
+ gap: 8px;
+ transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
+}
+
+.vp-mileage-report-journey-step:hover,
+.vp-mileage-report-journey-step:focus-visible {
+ border-color: rgba(22, 100, 255, 0.42);
+ box-shadow: 0 10px 24px rgba(24, 39, 75, 0.08);
+ outline: none;
+ transform: translateY(-1px);
+}
+
+.vp-mileage-report-journey-step:disabled {
+ cursor: not-allowed;
+ opacity: 0.62;
+ box-shadow: none;
+ transform: none;
+}
+
+.vp-mileage-report-journey-step strong {
+ color: var(--vp-text);
+ font-size: 17px;
+ line-height: 23px;
+ font-weight: 700;
+ word-break: break-word;
+}
+
+.vp-mileage-report-journey-step span,
+.vp-mileage-report-journey-step small {
+ color: var(--vp-text-muted);
+ font-size: 12px;
+ line-height: 18px;
+ word-break: break-word;
+}
+
.vp-mileage-customer-query-board {
margin-top: 16px;
overflow: hidden;
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 898f5a96..13c8ccc4 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -9900,6 +9900,13 @@ test('shows vehicle-first statistics domains for one vehicle service', async ()
expect(await screen.findByText('客户统计查询')).toBeInTheDocument();
expect(screen.getByText('客户里程对账总控')).toBeInTheDocument();
expect(screen.getByText('把区间里程、每日闭合、轨迹证明、异常通知和报表导出收敛成客户可验收的对账链路。')).toBeInTheDocument();
+ expect(screen.getByText('里程报表交付旅程')).toBeInTheDocument();
+ expect(screen.getByText('按客户报表习惯组织:先确认报表周期和车辆范围,再检查区间闭合、轨迹证明,最后导出或暂缓发布。')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '里程报表交付旅程 1 报表周期 全部日期' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '里程报表交付旅程 2 区间闭合 分页抽样' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '里程报表交付旅程 3 轨迹证明 粤A统计服务2' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '里程报表交付旅程 4 导出发布 复核后交付' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /复制报表旅程包/ })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户里程对账总控 区间里程 210 km 复制摘要' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户里程对账总控 每日闭合 分页抽样 闭合审计' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户里程对账总控 轨迹证明 2026-07-02 轨迹复核' })).toBeInTheDocument();
@@ -10038,6 +10045,13 @@ test('shows vehicle-first statistics domains for one vehicle service', async ()
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('区间里程:210 km'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('闭合状态:分页抽样'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('报表导出:http://localhost:3000/#/mileage?keyword=VIN-STATS-SERVICE&protocol=JT808'));
+ fireEvent.click(screen.getByRole('button', { name: /复制报表旅程包/ }));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【里程报表交付旅程包】'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('报表周期:全部日期'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆范围:VIN-STATS-SERVICE'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('区间闭合:分页抽样'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹证明:粤A统计服务2 / 2026-07-02 / 80 km'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('导出发布:复核后交付 / 复核后发布'));
fireEvent.click(screen.getAllByRole('button', { name: '复制决策说明' })[0]);
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户里程决策说明】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('决策结论:复核后交付'));