diff --git a/vehicle-data-platform/apps/web/src/pages/History.tsx b/vehicle-data-platform/apps/web/src/pages/History.tsx index 819a868b..0ac7bb13 100644 --- a/vehicle-data-platform/apps/web/src/pages/History.tsx +++ b/vehicle-data-platform/apps/web/src/pages/History.tsx @@ -1044,6 +1044,44 @@ export function History({ onClick: () => copyScheduledHistoryReportPlan() } ]; + const customerReportCadenceItems = [ + { + title: '每日运营复盘', + value: `${locations.total.toLocaleString()} 位置`, + detail: '每天交付位置历史、轨迹覆盖、里程统计和质量提示,便于运营早会复盘。', + action: '日报导出', + color: locations.total > 0 ? 'green' as const : 'orange' as const, + disabled: locations.items.length === 0, + onClick: () => exportLocations() + }, + { + title: '每周客户对账', + value: '周报计划', + detail: '沉淀固定车辆范围、时间窗和交付物,按周给客户对账和复核。', + action: '周报计划', + color: 'blue' as const, + disabled: false, + onClick: () => copyScheduledHistoryReportPlan() + }, + { + title: '临时问题解释', + value: deliveryState, + detail: '遇到定位、里程或断链质疑时,复制交付说明并附上证据链接。', + action: '复制说明', + color: deliveryStateColor, + disabled: false, + onClick: () => copyDeliveryPackage() + }, + { + title: '字段模板复用', + value: selectedFieldCount > 0 ? `${selectedFieldCount.toLocaleString()} 字段` : '待配置', + detail: '复用字段裁剪模板,减少 CSV 体积并保证客户拿到稳定字段。', + action: '字段配置', + color: selectedFieldCount > 0 || rawFieldRows.length > 0 ? 'green' as const : 'orange' as const, + disabled: false, + onClick: () => openQueryTab('fields') + } + ]; const customerHistoryQuestions = [ { question: '这辆车这段时间在哪里?', @@ -1532,6 +1570,40 @@ export function History({ ) : null} + {mode === 'query' ? ( + +
+
+ + 报表节奏 + {deliveryState} + {currentVehicleKeyword ? '单车交付' : '批量交付'} + + 参考 Fleetio、Samsara、Geotab 的报表能力,把导出从一次性下载升级为可复用的交付节奏。 + + 日报用于运营复盘,周报用于客户对账,临时导出用于问题解释,字段模板用于稳定复用。 + +
+
+ {customerReportCadenceItems.map((item) => ( + + ))} +
+
+
+ ) : null} {mode === 'query' ? (
diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index 7c139004..bc8334c2 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -5735,6 +5735,76 @@ button.vp-realtime-command-item:focus-visible { font-weight: 700; } +.vp-history-report-cadence { + display: grid; + grid-template-columns: minmax(300px, 0.7fr) minmax(0, 1.3fr); + gap: 14px; +} + +.vp-history-report-cadence-summary { + 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-history-report-cadence-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.vp-history-report-cadence-item { + min-height: 158px; + 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: 8px; + align-content: start; +} + +.vp-history-report-cadence-item:hover, +.vp-history-report-cadence-item:focus-visible { + border-color: rgba(22, 100, 255, 0.42); + box-shadow: 0 10px 24px rgba(24, 39, 75, 0.08); + outline: none; +} + +.vp-history-report-cadence-item:disabled { + cursor: not-allowed; + opacity: 0.62; +} + +.vp-history-report-cadence-item strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + font-weight: 700; + word-break: break-word; +} + +.vp-history-report-cadence-item span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + +.vp-history-report-cadence-item em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; +} + .vp-history-question-board { display: grid; grid-template-columns: minmax(300px, 0.7fr) minmax(0, 1.3fr); @@ -7490,6 +7560,8 @@ button.vp-realtime-command-item:focus-visible { .vp-history-customer-export-steps, .vp-history-report-templates, .vp-history-report-template-grid, + .vp-history-report-cadence, + .vp-history-report-cadence-grid, .vp-history-question-board, .vp-history-question-grid, .vp-history-replay-nav, 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 3badb75e..5666c905 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -6606,6 +6606,12 @@ test('shows parsed field history as a flattened evidence table', 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('参考 Fleetio、Samsara、Geotab 的报表能力,把导出从一次性下载升级为可复用的交付节奏。')).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.getAllByRole('button', { name: /复制决策说明/ }).length).toBeGreaterThanOrEqual(1); expect(screen.getByRole('button', { name: '客户轨迹决策 明细复核 明细证据' })).toBeInTheDocument();