diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index c54daf20..ad737317 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -850,6 +850,62 @@ export function Realtime({ onClick: openTimeWindowQuality } ]; + const timeWindowTaskItems = [ + { + step: '1', + title: '锁定车辆', + value: timeWindowRow?.plate || timeWindowKeyword || '未选择车辆', + detail: timeWindowReady ? `${timeWindow.dateFrom} 至 ${timeWindow.dateTo}` : '先从实时列表或地图选择车辆,再确认时间窗。', + action: timeWindowReady ? '已锁定' : '先选车辆', + color: timeWindowReady ? 'green' as const : 'orange' as const, + disabled: false, + onClick: () => { + if (!timeWindowReady) { + Toast.warning('请先选择车辆和时间窗'); + } + } + }, + { + step: '2', + title: '轨迹复盘', + value: timeWindowDuration, + detail: '回放位置、速度、里程断点和停留变化。', + action: '轨迹回放', + color: 'blue' as const, + disabled: !timeWindowReady, + onClick: openTimeWindowHistory + }, + { + step: '3', + title: '里程核对', + value: timeWindowRow?.totalMileageKm != null ? `${timeWindowRow.totalMileageKm} km` : '待核对', + detail: '用同一时间窗进入里程统计,核对区间差值和日统计闭合。', + action: '统计查询', + color: timeWindowRow?.totalMileageKm != null ? 'blue' as const : 'orange' as const, + disabled: !timeWindowReady, + onClick: openTimeWindowMileage + }, + { + step: '4', + title: '历史导出', + value: timeWindowProtocol || '全部通道', + detail: '导出位置、原始帧和字段证据,支撑客户问询。', + action: '导出证据', + color: 'blue' as const, + disabled: !timeWindowReady, + onClick: openTimeWindowRaw + }, + { + step: '5', + title: '告警闭环', + value: timeWindowRow ? vehicleServiceStatus(timeWindowRow).label : '待选择', + detail: '复盘断链、离线、定位异常和通知处理记录。', + action: '告警复盘', + color: timeWindowRow && hasSourceIssue(timeWindowRow) ? 'orange' as const : timeWindowReady ? 'green' as const : 'grey' as const, + disabled: !timeWindowReady || !onOpenQuality, + onClick: openTimeWindowQuality + } + ]; const mapPoints: VehicleMapPoint[] = rows.map((row, index) => ({ id: realtimeMapPointId(row, index), label: row.plate || row.vin || 'unknown', @@ -1763,6 +1819,33 @@ export function Realtime({ ))} +
+
+ + 时间窗监控任务台 + {timeWindowReady ? '可交付' : '待锁定'} + + 把同一辆车、同一时间窗贯穿到轨迹、里程、历史导出和告警说明,避免客户跨页面重复筛选。 +
+
+ {timeWindowTaskItems.map((item) => ( + + ))} +
+
); diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index 75826bba..3e3aca4b 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -7,7 +7,7 @@ body { margin: 0; - min-width: 1280px; + min-width: 0; background: var(--vp-bg); color: var(--vp-text); font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; @@ -3954,6 +3954,79 @@ body { align-self: end; } +.vp-time-window-task-board { + padding: 14px; + border-top: 1px solid var(--vp-border); + display: grid; + gap: 12px; +} + +.vp-time-window-task-summary { + display: grid; + gap: 8px; +} + +.vp-time-window-task-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 10px; +} + +.vp-time-window-task-item { + min-height: 164px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: var(--vp-radius); + background: #fbfcff; + color: inherit; + cursor: pointer; + font: inherit; + text-align: left; + display: grid; + align-content: start; + gap: 8px; +} + +.vp-time-window-task-item:not(:disabled):hover, +.vp-time-window-task-item:not(:disabled):focus-visible { + border-color: rgba(22, 100, 255, 0.42); + background: #f5f9ff; + outline: none; +} + +.vp-time-window-task-item:disabled { + cursor: not-allowed; + opacity: 0.62; +} + +.vp-time-window-task-item strong { + color: var(--vp-text); + font-size: 15px; + line-height: 21px; + font-weight: 700; +} + +.vp-time-window-task-item span { + color: var(--vp-text); + font-size: 16px; + line-height: 22px; + font-weight: 700; + word-break: break-word; +} + +.vp-time-window-task-item small { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + +.vp-time-window-task-item em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; +} + .vp-realtime-command-board { margin-bottom: 16px; padding: 14px; @@ -11065,6 +11138,22 @@ button.vp-realtime-command-item:focus-visible { } @media (max-width: 900px) { + .vp-shell { + grid-template-columns: minmax(0, 1fr); + } + + .vp-sidebar { + position: relative; + height: auto; + padding: 12px; + } + + .vp-topbar { + position: relative; + grid-template-columns: minmax(0, 1fr); + padding: 12px; + } + .vp-evidence-grid, .vp-action-grid, .vp-fleet-monitor .semi-card-body, @@ -11160,6 +11249,7 @@ button.vp-realtime-command-item:focus-visible { .vp-time-window-monitor .semi-card-body, .vp-time-window-controls, .vp-time-window-actions, + .vp-time-window-task-grid, .vp-realtime-command-board, .vp-realtime-command-grid, .vp-realtime-impact-board, 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 8d1494fb..5761e0fc 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -50,6 +50,7 @@ test('renders vehicle platform shell', () => { expect(screen.getByText('内部')).toBeInTheDocument(); expect(screen.getByText('客户主流程外查看链路质量')).toBeInTheDocument(); expect(screen.getAllByText('运营总览').length).toBeGreaterThanOrEqual(1); + expect(getComputedStyle(document.body).minWidth).not.toBe('1280px'); }); test('topbar frames customer work as a vehicle service delivery strip', () => { @@ -10420,6 +10421,13 @@ test('frames realtime page as one vehicle service with source evidence', async ( expect(writeText).toHaveBeenCalledWith(expect.stringContaining('地图能力:高德地图未配置,使用坐标预览')); expect(screen.getByText('自定义时间窗复盘')).toBeInTheDocument(); expect(screen.getByText('按车辆和时间窗复盘发生了什么')).toBeInTheDocument(); + expect(screen.getByText('时间窗监控任务台')).toBeInTheDocument(); + expect(screen.getByText('把同一辆车、同一时间窗贯穿到轨迹、里程、历史导出和告警说明,避免客户跨页面重复筛选。')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '时间窗监控任务台 1 锁定车辆 粤ART002 已锁定' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '时间窗监控任务台 2 轨迹复盘 1 天窗口 轨迹回放' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '时间窗监控任务台 3 里程核对 119925 km 统计查询' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '时间窗监控任务台 4 历史导出 GB32960 导出证据' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '时间窗监控任务台 5 告警闭环 服务正常 告警复盘' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '时间窗复盘 轨迹回放 打开轨迹' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '时间窗复盘 历史数据 查询历史' })).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /复制时间窗包/ }));