From 91b60ed63abddefe9d047e93810ea6083dff2096 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 18:13:57 +0800 Subject: [PATCH] feat(platform): add single vehicle action desk --- .../apps/web/src/pages/VehicleDetail.tsx | 73 +++++++++++++++ .../apps/web/src/styles/global.css | 89 +++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 7 ++ 3 files changed, 169 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx index 5f381cf9..8a49d0c9 100644 --- a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx +++ b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx @@ -728,6 +728,53 @@ export function VehicleDetail({ ] } ]; + const customerVehicleActionBar = [ + { + title: '看当前位置', + value: formatLocation(latest), + detail: '确认车辆当前位置、在线状态、最后上报和地图可用性。', + action: '实时地图', + color: online ? 'green' as const : hasResolvedVIN ? 'orange' as const : 'grey' as const, + disabled: !hasResolvedVIN, + onClick: () => onOpenRealtime(resolvedVIN, activeProtocol) + }, + { + title: '回放轨迹', + value: `${formatCompactNumber(overview?.historyCount ?? detail?.history?.total ?? 0)} 条`, + detail: '按同一车辆进入轨迹回放,复核位置、速度和里程断点。', + action: '轨迹回放', + color: (overview?.historyCount ?? detail?.history?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const, + disabled: !hasResolvedVIN, + onClick: () => onOpenHistory(resolvedVIN, activeProtocol) + }, + { + title: '核对里程', + value: `${formatCompactNumber(overview?.mileageCount ?? detail?.mileage?.total ?? 0)} 条`, + detail: '进入统计查询,核对区间里程、日里程和统计闭合。', + action: '统计查询', + color: (overview?.mileageCount ?? detail?.mileage?.total ?? 0) > 0 ? 'green' as const : 'grey' as const, + disabled: !hasResolvedVIN, + onClick: () => onOpenMileage(resolvedVIN, activeProtocol) + }, + { + title: '导出证据', + value: `${formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0)} 条`, + detail: '查询历史数据和字段证据,作为客户复盘与导出依据。', + action: '历史导出', + color: (overview?.rawCount ?? detail?.raw?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const, + disabled: !hasResolvedVIN, + onClick: () => onOpenRaw(resolvedVIN, activeProtocol) + }, + { + title: '告警通知', + value: `${qualityCount.toLocaleString()} 项`, + detail: priorityQualityIssue ? `${qualityIssueLabel(priorityQualityIssue.issueType)} / ${priorityQualityIssue.lastSeen || '-'}` : '暂无待通知告警。', + action: '告警闭环', + color: qualityCount > 0 ? 'orange' as const : 'green' as const, + disabled: !hasResolvedVIN && qualityCount <= 0, + onClick: () => onOpenQuality?.(qualityFiltersForCurrentVehicle()) + } + ]; const serviceRunbookSteps = [ { title: '确认实时状态', @@ -1188,6 +1235,32 @@ export function VehicleDetail({ 客户打开一辆车时,先看当前位置、最后上报、速度、SOC、总里程和告警,再进入轨迹回放、统计查询、数据导出和告警闭环。 +
+
+
+ 客户单车操作台 + 围绕这辆车直接完成看车、回放、统计、导出和告警通知。 +
+ 单车服务 +
+
+ {customerVehicleActionBar.map((item) => ( + + ))} +
+
{[ { label: '当前位置', value: formatLocation(latest) }, diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index 2ca162cb..eb878001 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -7162,6 +7162,94 @@ button.vp-realtime-command-item:focus-visible { line-height: 22px; } +.vp-single-vehicle-action-bar { + padding: 12px; + border: 1px solid rgba(22, 100, 255, 0.16); + border-radius: var(--vp-radius); + background: rgba(255, 255, 255, 0.86); + display: grid; + gap: 10px; +} + +.vp-single-vehicle-action-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.vp-single-vehicle-action-head > div { + display: grid; + gap: 2px; +} + +.vp-single-vehicle-action-head strong { + color: var(--vp-text); + font-size: 15px; + line-height: 21px; +} + +.vp-single-vehicle-action-head span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + +.vp-single-vehicle-action-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 8px; +} + +.vp-single-vehicle-action-item { + min-height: 132px; + padding: 10px; + border: 1px solid var(--vp-border); + border-radius: 8px; + background: #fbfcff; + color: inherit; + cursor: pointer; + font: inherit; + text-align: left; + display: grid; + gap: 7px; + align-content: start; +} + +.vp-single-vehicle-action-item:hover, +.vp-single-vehicle-action-item:focus-visible { + border-color: rgba(22, 100, 255, 0.45); + box-shadow: 0 0 0 3px rgba(22, 100, 255, 0.06); + outline: none; +} + +.vp-single-vehicle-action-item:disabled { + cursor: not-allowed; + opacity: 0.62; +} + +.vp-single-vehicle-action-item strong { + color: var(--vp-text); + font-size: 15px; + line-height: 20px; + font-weight: 700; + word-break: break-word; +} + +.vp-single-vehicle-action-item span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + +.vp-single-vehicle-action-item em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 18px; +} + .vp-vehicle-live-facts { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); @@ -8293,6 +8381,7 @@ button.vp-realtime-command-item:focus-visible { .vp-customer-delivery-grid, .vp-vehicle-live-view .semi-card-body, .vp-vehicle-live-main, + .vp-single-vehicle-action-grid, .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 f44a9ddc..a6dce00d 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -12029,6 +12029,13 @@ test('copies vehicle service diagnostic summary', async () => { expect(await screen.findByText('车辆服务结论')).toBeInTheDocument(); expect(screen.getByText('单车 Live View')).toBeInTheDocument(); expect(screen.getByText('客户打开一辆车时,先看当前位置、最后上报、速度、SOC、总里程和告警,再进入轨迹回放、统计查询、数据导出和告警闭环。')).toBeInTheDocument(); + expect(screen.getByText('客户单车操作台')).toBeInTheDocument(); + expect(screen.getByText('围绕这辆车直接完成看车、回放、统计、导出和告警通知。')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户单车操作台 看当前位置 113.2, 23.1 实时地图' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户单车操作台 回放轨迹 12 条 轨迹回放' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户单车操作台 核对里程 3 条 统计查询' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户单车操作台 导出证据 34 条 历史导出' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户单车操作台 告警通知 1 项 告警闭环' })).toBeInTheDocument(); expect(screen.getByText('当前位置')).toBeInTheDocument(); expect(screen.getByRole('button', { name: '单车实时视图 实时位置 查看实时' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '单车实时视图 数据导出 历史导出' })).toBeInTheDocument();