From 2e20ec5c81b6e225d38487cd201b8ea57d5a66b5 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 17:23:12 +0800 Subject: [PATCH] feat(platform): add custom time window monitor --- .../apps/web/src/pages/History.tsx | 82 +++++++++++++++++++ .../apps/web/src/styles/global.css | 80 ++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 6 ++ 3 files changed, 168 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/History.tsx b/vehicle-data-platform/apps/web/src/pages/History.tsx index 0ac7bb13..531811d3 100644 --- a/vehicle-data-platform/apps/web/src/pages/History.tsx +++ b/vehicle-data-platform/apps/web/src/pages/History.tsx @@ -1006,6 +1006,54 @@ export function History({ onClick: () => copyDeliveryPackage() } ]; + const timeWindowMonitorItems = [ + { + title: '轨迹复盘', + value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '待查询', + detail: `${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'},先确认位置覆盖和断点。`, + action: '位置历史', + color: validLocations.length > 0 ? 'green' as const : 'orange' as const, + disabled: false, + onClick: () => openQueryTab('location') + }, + { + title: '统计核对', + value: formatNumber(mileageDelta, ' km'), + detail: '把同一时间窗带到统计查询,核对区间里程和日报闭合。', + action: '统计查询', + color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const, + disabled: !onOpenMileage, + onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) }) + }, + { + title: '历史证据', + value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`, + detail: selectedFieldCount > 0 ? `包含 ${selectedFieldCount.toLocaleString()} 个字段裁剪。` : '保留原始明细和解析字段,解释位置、里程和字段来源。', + action: '明细证据', + color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const, + disabled: false, + onClick: () => openQueryTab('raw') + }, + { + title: '告警复盘', + value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项提示` : '同步说明', + detail: '用同一车辆和时间窗打开告警事件,解释断链、字段缺失和质量影响。', + action: '告警说明', + color: anomalyTotal > 0 ? 'orange' as const : 'blue' as const, + disabled: false, + onClick: () => { + window.location.hash = buildAppHash({ + page: 'alert-events', + keyword: currentVehicleKeyword, + protocol: currentProtocol, + filters: { + ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), + ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) + } + }); + } + } + ]; const historyReportTemplates = [ { title: '轨迹报告', @@ -1442,6 +1490,40 @@ export function History({ 数据通道:{currentProtocol || '全部数据通道'} {scopeDescription} + {mode === 'query' ? ( + +
+
+ + 自定义时间窗 + {deliveryState} + {currentVehicleKeyword || '全部车辆'} + + 客户选定车辆和时间范围后,直接判断这个窗口能否用于轨迹复盘、统计核对、历史证据导出和告警说明。 + + 时间窗是客户查询的主线:同一组筛选条件必须贯穿轨迹、里程、历史明细、字段裁剪和告警复盘,避免各页面口径不一致。 + +
+
+ {timeWindowMonitorItems.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 623c4d84..a468b0a5 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -5665,6 +5665,84 @@ button.vp-realtime-command-item:focus-visible { word-break: break-word; } +.vp-history-time-window-board { + display: grid; + grid-template-columns: minmax(280px, 0.68fr) minmax(0, 1.32fr); + gap: 16px; +} + +.vp-history-time-window-summary { + min-height: 176px; + padding: 14px; + border: 1px solid rgba(22, 100, 255, 0.2); + border-radius: var(--vp-radius); + background: #f5f9ff; + display: grid; + gap: 12px; + align-content: start; +} + +.vp-history-time-window-summary .semi-typography { + font-size: 13px; + line-height: 20px; +} + +.vp-history-time-window-grid { + min-width: 0; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.vp-history-time-window-item { + min-height: 176px; + 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; + gap: 8px; + align-content: start; +} + +.vp-history-time-window-item:disabled { + cursor: not-allowed; + opacity: 0.58; +} + +.vp-history-time-window-item:not(:disabled):hover, +.vp-history-time-window-item:not(:disabled):focus-visible { + border-color: rgba(22, 100, 255, 0.42); + background: #f5f9ff; + outline: none; +} + +.vp-history-time-window-item strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + font-weight: 700; + word-break: break-word; +} + +.vp-history-time-window-item span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + +.vp-history-time-window-item em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 18px; +} + .vp-history-customer-export-board { margin-top: 16px; overflow: hidden; @@ -7729,6 +7807,8 @@ button.vp-realtime-command-item:focus-visible { .vp-trajectory-anomaly-grid, .vp-trajectory-impact-board, .vp-trajectory-impact-grid, + .vp-history-time-window-board, + .vp-history-time-window-grid, .vp-history-customer-export-board .semi-card-body, .vp-history-customer-export-steps, .vp-history-report-templates, 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 d3453860..8fcbd2ca 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -6588,6 +6588,12 @@ test('shows parsed field history as a flattened evidence table', async () => { render(); expect(await screen.findByRole('heading', { name: '历史查询导出' })).toBeInTheDocument(); + expect(screen.getByText('自定义时间窗监控台')).toBeInTheDocument(); + expect(screen.getByText('客户选定车辆和时间范围后,直接判断这个窗口能否用于轨迹复盘、统计核对、历史证据导出和告警说明。')).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.getByText('轨迹回放工作台')).toBeInTheDocument(); expect(screen.getByText('按车辆和时间窗复盘每一段行程')).toBeInTheDocument();