diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css
index 33afb693..9328197c 100644
--- a/vehicle-data-platform/apps/web/src/styles/global.css
+++ b/vehicle-data-platform/apps/web/src/styles/global.css
@@ -11806,6 +11806,128 @@ button.vp-realtime-command-item:focus-visible {
line-height: 20px;
}
+.vp-single-vehicle-time-window {
+ margin-top: 16px;
+ overflow: hidden;
+}
+
+.vp-single-vehicle-time-window .semi-card-body {
+ padding: 0;
+}
+
+.vp-single-vehicle-time-window-main {
+ display: grid;
+ grid-template-columns: minmax(300px, 0.72fr) minmax(0, 1.28fr);
+ min-height: 236px;
+}
+
+.vp-single-vehicle-time-window-copy {
+ padding: 16px;
+ border-right: 1px solid var(--vp-border);
+ background: #f7fbff;
+ display: grid;
+ gap: 12px;
+ align-content: start;
+}
+
+.vp-single-vehicle-time-window-copy .semi-typography {
+ line-height: 21px;
+}
+
+.vp-single-vehicle-time-window-controls {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.vp-single-vehicle-time-window-controls label {
+ min-height: 66px;
+ padding: 9px 11px;
+ border: 1px solid var(--vp-border);
+ border-radius: 8px;
+ background: #fff;
+ display: grid;
+ gap: 4px;
+}
+
+.vp-single-vehicle-time-window-controls label span {
+ color: var(--vp-text-muted);
+ font-size: 12px;
+ line-height: 16px;
+}
+
+.vp-single-vehicle-time-window-controls input {
+ width: 100%;
+ border: 0;
+ outline: none;
+ background: transparent;
+ color: var(--vp-text);
+ font: inherit;
+ font-size: 14px;
+ line-height: 20px;
+}
+
+.vp-single-vehicle-time-window-controls input:focus {
+ color: #1664ff;
+}
+
+.vp-single-vehicle-time-window-actions {
+ padding: 16px;
+ background: #fff;
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.vp-single-vehicle-time-window-action {
+ min-height: 178px;
+ padding: 14px;
+ border: 1px solid var(--vp-border);
+ border-radius: var(--vp-radius);
+ background: #fbfcff;
+ display: grid;
+ gap: 10px;
+ align-content: start;
+ text-align: left;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+}
+
+.vp-single-vehicle-time-window-action:disabled {
+ cursor: not-allowed;
+ opacity: 0.62;
+}
+
+.vp-single-vehicle-time-window-action:not(:disabled):hover,
+.vp-single-vehicle-time-window-action:not(:disabled):focus-visible {
+ border-color: rgba(22, 100, 255, 0.42);
+ background: #f6fbff;
+ box-shadow: var(--vp-shadow-sm);
+ outline: none;
+}
+
+.vp-single-vehicle-time-window-action strong {
+ color: var(--vp-text);
+ font-size: 18px;
+ line-height: 24px;
+ word-break: break-word;
+}
+
+.vp-single-vehicle-time-window-action span {
+ color: var(--vp-text-muted);
+ font-size: 13px;
+ line-height: 20px;
+}
+
+.vp-single-vehicle-time-window-action em {
+ color: #1664ff;
+ font-size: 13px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 20px;
+}
+
.vp-vehicle-task-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
@@ -12608,6 +12730,11 @@ button.vp-realtime-command-item:focus-visible {
width: min(100%, 132px);
}
+ .vp-single-vehicle-time-window-copy {
+ border-right: 0;
+ border-bottom: 1px solid var(--vp-border);
+ }
+
.vp-evidence-grid,
.vp-action-grid,
.vp-fleet-monitor .semi-card-body,
@@ -12619,6 +12746,9 @@ button.vp-realtime-command-item:focus-visible {
.vp-vehicle-live-view .semi-card-body,
.vp-vehicle-live-main,
.vp-single-vehicle-action-grid,
+ .vp-single-vehicle-time-window-main,
+ .vp-single-vehicle-time-window-controls,
+ .vp-single-vehicle-time-window-actions,
.vp-vehicle-live-facts,
.vp-vehicle-archive-summary-main,
.vp-vehicle-archive-summary-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 e92d657c..7d3a1d95 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -13320,6 +13320,8 @@ test('copies vehicle service diagnostic summary', async () => {
expect(screen.getByRole('button', { name: '客户服务交付 历史数据查询 查询历史' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户服务交付 告警通知 告警闭环' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '客户服务交付 统计查询 统计复核' })).toBeInTheDocument();
+ expect(screen.getAllByText('单车时间窗交付').length).toBeGreaterThan(0);
+ expect(screen.getByText('把同一辆车和同一段时间带入轨迹回放、里程统计、历史导出和告警通知,客户复盘时不需要跨页面重复输入。')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /复制归一摘要/ }));
await waitFor(() => {
expect(writeText.mock.calls.some((call) => String(call[0]).includes('【统一车辆服务摘要】'))).toBe(true);
@@ -13423,6 +13425,18 @@ test('copies vehicle service diagnostic summary', async () => {
expect(runbookCopied).toContain('验收:车辆在线状态、最新时间、有效坐标均可解释。');
expect(runbookCopied).toContain('告警事件:http://localhost:3000/#/alert-events?keyword=VIN-SUMMARY-001&protocol=JT808');
expect(runbookCopied).toContain('统计查询:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
+
+ fireEvent.change(screen.getByPlaceholderText('单车开始日期'), { target: { value: '2026-07-01' } });
+ fireEvent.change(screen.getByPlaceholderText('单车结束日期'), { target: { value: '2026-07-03' } });
+ fireEvent.click(screen.getByRole('button', { name: '单车时间窗交付 历史数据导出 2026-07-01 至 2026-07-03' }));
+ expect(window.location.hash.startsWith('#/history-query?')).toBe(true);
+ const windowParams = new URLSearchParams(window.location.hash.split('?')[1] ?? '');
+ expect(windowParams.get('keyword')).toBe('VIN-SUMMARY-001');
+ expect(windowParams.get('protocol')).toBe('JT808');
+ expect(windowParams.get('dateFrom')).toBe('2026-07-01');
+ expect(windowParams.get('dateTo')).toBe('2026-07-03');
+ expect(windowParams.get('tab')).toBe('raw');
+ expect(windowParams.get('includeFields')).toBe('true');
});
test('exports vehicle detail service dossier as csv', async () => {