From 7657412b8c6f35c710de042567457abbfe6c2ed0 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 02:05:44 +0800 Subject: [PATCH] feat(platform): add single vehicle task board --- .../apps/web/src/pages/VehicleDetail.tsx | 93 +++++++++++++++++++ .../apps/web/src/styles/global.css | 36 +++++++ .../apps/web/src/test/App.test.tsx | 17 +++- 3 files changed, 143 insertions(+), 3 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx index 699ef2d4..6676c569 100644 --- a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx +++ b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx @@ -786,6 +786,63 @@ export function VehicleDetail({ ]; copyText(lines.join('\n'), '单车处理清单'); }; + const detailTaskBoard = [ + { + title: '看实时位置', + value: lastSeen, + detail: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 来源在线,先确认车辆当前是否仍在上报。` : '暂无来源在线证据,优先确认数据接入。', + color: online ? 'green' as const : 'orange' as const, + primaryAction: '实时监控', + secondaryAction: '复制摘要', + disabled: !hasResolvedVIN, + onPrimary: () => onOpenRealtime(resolvedVIN, activeProtocol), + onSecondary: () => copyUnifiedVehicleService() + }, + { + title: '回放轨迹证据', + value: `${formatCompactNumber(overview?.historyCount ?? detail?.history?.total ?? 0)} 条历史`, + detail: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个位置来源,可回放定位、速度和里程断点。` : '暂无有效位置来源,先检查实时坐标。', + color: evidenceLocatedSourceCount > 0 ? 'blue' as const : 'grey' as const, + primaryAction: '轨迹回放', + secondaryAction: '处理清单', + disabled: !hasResolvedVIN, + onPrimary: () => onOpenHistory(resolvedVIN, activeProtocol), + onSecondary: () => copyVehicleRunbook() + }, + { + title: '导出原始证据', + value: `${formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0)} 帧`, + detail: latestRaw?.frameType ? `最新原始记录类型 ${latestRaw.frameType},用于核对解析字段。` : '按车辆和来源导出 RAW 与解析字段。', + color: (overview?.rawCount ?? detail?.raw?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const, + primaryAction: '数据导出', + secondaryAction: '导出档案', + disabled: !hasResolvedVIN, + onPrimary: () => onOpenRaw(resolvedVIN, activeProtocol), + onSecondary: () => exportVehicleReport() + }, + { + title: '复核里程统计', + value: `${formatCompactNumber(overview?.mileageCount ?? detail?.mileage?.total ?? 0)} 条统计`, + detail: `来源里程差 ${formatCompactNumber(evidenceMileageDelta, ' km')},确认区间和日统计能闭合。`, + color: isFiniteNumber(evidenceMileageDelta) && Math.abs(evidenceMileageDelta) > 1 ? 'orange' as const : 'green' as const, + primaryAction: '里程统计', + secondaryAction: '诊断摘要', + disabled: !hasResolvedVIN, + onPrimary: () => onOpenMileage(resolvedVIN, activeProtocol), + onSecondary: () => copyVehicleDiagnosticSummary() + }, + { + title: '闭环告警事件', + value: `${qualityCount.toLocaleString()} 项`, + detail: priorityQualityIssue ? `${qualityIssueLabel(priorityQualityIssue.issueType)} / ${priorityQualityIssue.lastSeen || '-'}` : '暂无高优先级告警,保持观察。', + color: qualityCount > 0 ? 'orange' as const : 'green' as const, + primaryAction: '告警事件', + secondaryAction: '运营摘要', + disabled: !onOpenQuality || (!hasResolvedVIN && qualityCount <= 0), + onPrimary: () => onOpenQuality?.(qualityFiltersForCurrentVehicle()), + onSecondary: () => copyVehicleOperationsSummary() + } + ]; const qualityTable = ( + +
+ {detailTaskBoard.map((item) => ( +
+
+ {item.title} + {item.value} +
+ {item.detail} + + + + +
+ ))} +
+
+
{detailWorkbench.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 a7c3e816..7729acce 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -2472,6 +2472,41 @@ button.vp-realtime-command-item:focus-visible { margin-top: 12px; } +.vp-vehicle-task-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 12px; +} + +.vp-vehicle-task-item { + min-height: 172px; + padding: 14px; + border: 1px solid var(--vp-border); + border-radius: var(--vp-radius); + background: #fbfcff; + display: grid; + gap: 12px; + align-content: space-between; +} + +.vp-vehicle-task-head { + min-width: 0; + display: grid; + gap: 8px; +} + +.vp-vehicle-task-head strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + word-break: break-word; +} + +.vp-vehicle-task-item .semi-typography { + font-size: 13px; + line-height: 20px; +} + .vp-evidence-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); @@ -3134,6 +3169,7 @@ button.vp-realtime-command-item:focus-visible { .vp-unified-service-grid, .vp-service-dossier, .vp-service-dossier-assets, + .vp-vehicle-task-grid, .vp-source-readiness-grid, .vp-source-readiness-metrics, .vp-map-ops-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 780f1f22..b1d47e73 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -10896,6 +10896,12 @@ test('shows unified service overview on vehicle detail', async () => { expect(await screen.findByText('车辆服务概览')).toBeInTheDocument(); expect(screen.getByText('车辆服务档案总览')).toBeInTheDocument(); expect(screen.getByRole('button', { name: /复制档案/ })).toBeInTheDocument(); + expect(screen.getByText('今日单车服务任务板')).toBeInTheDocument(); + expect(screen.getByText('看实时位置')).toBeInTheDocument(); + expect(screen.getByText('回放轨迹证据')).toBeInTheDocument(); + expect(screen.getByText('导出原始证据')).toBeInTheDocument(); + expect(screen.getByText('复核里程统计')).toBeInTheDocument(); + expect(screen.getByText('闭环告警事件')).toBeInTheDocument(); expect(screen.getByText('单车运营工作台')).toBeInTheDocument(); expect(screen.getByText('单车服务处理清单')).toBeInTheDocument(); expect(screen.getAllByText('档案完整度').length).toBeGreaterThanOrEqual(1); @@ -10916,12 +10922,17 @@ test('shows unified service overview on vehicle detail', async () => { expect(screen.getByText('复核统计口径')).toBeInTheDocument(); expect(screen.getByText('车辆在线状态、最新时间、有效坐标均可解释。')).toBeInTheDocument(); expect(screen.getByText('2026-07-03 20:12:10')).toBeInTheDocument(); - expect(screen.getByText('12 条历史')).toBeInTheDocument(); - expect(screen.getByText('34 帧')).toBeInTheDocument(); + expect(screen.getAllByText('12 条历史').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('34 帧').length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText('1 项').length).toBeGreaterThan(0); - expect(screen.getByText('7 条统计')).toBeInTheDocument(); + expect(screen.getAllByText('7 条统计').length).toBeGreaterThanOrEqual(1); expect(screen.getByText('2/3')).toBeInTheDocument(); expect(screen.getByText('轨迹 12 / 原始记录 34 / 里程 7')).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.getByRole('button', { name: '单车任务 闭环告警事件 告警事件' })).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: '单车运营工作台 原始记录 查看原始记录' })); expect(window.location.hash).toBe('#/history-query?keyword=VIN001&tab=raw'); });