From 680006e36f041209e4b876256543c4ee3440aa32 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 17:53:50 +0800 Subject: [PATCH] feat(platform): add customer vehicle monitor desk --- .../apps/web/src/pages/Dashboard.tsx | 69 ++++++++++++++ .../apps/web/src/styles/global.css | 92 +++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 7 ++ 3 files changed, 168 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx index b647ff62..f3370217 100644 --- a/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Dashboard.tsx @@ -1660,6 +1660,48 @@ export function Dashboard({ onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}) } ]; + const customerVehicleMonitorItems = [ + { + label: '车辆在哪里', + value: `${commandLocatedCount.toLocaleString()} 辆有定位`, + detail: '打开地图查看在线车辆、最后上报、速度和坐标可信度。', + action: '打开地图', + color: commandLocatedCount > 0 ? 'green' as const : 'orange' as const, + onClick: () => onOpenMap({ online: 'online' }) + }, + { + label: '回放这段时间', + value: `${formatCount(summary?.activeToday)} 今日活跃`, + detail: '用同一时间窗回放轨迹、速度、停驶和里程断点。', + action: '轨迹回放', + color: 'blue' as const, + onClick: openTimeMonitorHistory + }, + { + label: '里程统计', + value: `${formatCount(serviceSummary?.totalVehicles)} 辆可统计`, + detail: '按车辆口径核对区间里程、日里程和异常记录。', + action: '查里程', + color: 'blue' as const, + onClick: openTimeMonitorMileage + }, + { + label: '导出客户数据', + value: `${formatCount(summary?.frameToday)} 今日数据`, + detail: '按车辆、时间和字段裁剪导出历史位置与原始证据。', + action: '导出数据', + color: 'blue' as const, + onClick: openTimeMonitorRaw + }, + { + label: '告警通知', + value: `${formatCount(summary?.issueVehicles)} 告警车辆`, + detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无高优先级告警。', + action: '通知闭环', + color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const, + onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}) + } + ]; return (
@@ -1691,6 +1733,33 @@ export function Dashboard({ ))}
+
+
+ + 客户车辆监控台 + {amapConfigured ? '地图可用' : '坐标预览'} + 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} 告警 + + 先回答客户最常问的车辆问题:车在哪里、这段时间怎么跑、里程怎么算、数据怎么导出、异常谁处理。 + 32960、808、MQTT 只在证据层出现;首页默认按车辆、时间窗和业务动作组织。 +
+
+ {customerVehicleMonitorItems.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 41fa2ac5..3d433952 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -969,6 +969,91 @@ body { line-height: 22px; } +.vp-customer-vehicle-monitor { + margin-bottom: 16px; + border: 1px solid rgba(11, 159, 119, 0.18); + border-radius: var(--vp-radius); + background: #ffffff; + display: grid; + grid-template-columns: minmax(300px, 0.66fr) minmax(0, 1.34fr); + overflow: hidden; +} + +.vp-customer-vehicle-monitor-copy { + padding: 18px; + border-right: 1px solid var(--vp-border); + background: #f6fbf9; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-customer-vehicle-monitor-copy strong { + color: var(--vp-text); + font-size: 18px; + line-height: 26px; +} + +.vp-customer-vehicle-monitor-copy span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-customer-vehicle-monitor-grid { + padding: 14px; + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 10px; +} + +.vp-customer-vehicle-monitor-item { + min-height: 136px; + padding: 12px; + border: 1px solid rgba(22, 100, 255, 0.14); + border-radius: 8px; + background: #fbfcff; + color: inherit; + cursor: pointer; + font: inherit; + text-align: left; + display: grid; + gap: 8px; + align-content: start; + transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease; +} + +.vp-customer-vehicle-monitor-item:hover, +.vp-customer-vehicle-monitor-item:focus-visible { + border-color: rgba(11, 159, 119, 0.38); + background: #f5fbf9; + box-shadow: var(--vp-shadow-sm); + outline: none; +} + +.vp-customer-vehicle-monitor-item strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + word-break: break-word; +} + +.vp-customer-vehicle-monitor-item span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; + word-break: break-word; +} + +.vp-customer-vehicle-monitor-item em { + align-self: end; + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 18px; +} + .vp-customer-cockpit .semi-card-body { display: grid; grid-template-columns: minmax(0, 1.08fr) minmax(420px, 0.92fr); @@ -8007,6 +8092,8 @@ button.vp-realtime-command-item:focus-visible { .vp-map-ops-work, .vp-customer-cockpit, .vp-customer-cockpit-actions, + .vp-customer-vehicle-monitor, + .vp-customer-vehicle-monitor-grid, .vp-customer-cockpit .semi-card-body, .vp-customer-cockpit-metrics, .vp-customer-cockpit-trust, @@ -8173,6 +8260,11 @@ button.vp-realtime-command-item:focus-visible { border-bottom: 1px solid var(--vp-border); } + .vp-customer-vehicle-monitor-copy { + border-right: 0; + border-bottom: 1px solid var(--vp-border); + } + .vp-vehicle-asset-console-summary { border-right: 0; border-bottom: 1px solid var(--vp-border); 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 6858d840..a33211cc 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -1237,6 +1237,13 @@ test('dashboard exposes vehicle command center map actions', async () => { render(); expect(await screen.findByText('实时态势地图')).toBeInTheDocument(); + expect(screen.getByText('客户车辆监控台')).toBeInTheDocument(); + expect(screen.getByText('先回答客户最常问的车辆问题:车在哪里、这段时间怎么跑、里程怎么算、数据怎么导出、异常谁处理。')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户车辆监控台 车辆在哪里 1 辆有定位 打开地图' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户车辆监控台 回放这段时间 4 今日活跃 轨迹回放' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户车辆监控台 里程统计 1,033 辆可统计 查里程' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户车辆监控台 导出客户数据 1,286,320 今日数据 导出数据' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: '客户车辆监控台 告警通知 7 告警车辆 通知闭环' })).toBeInTheDocument(); expect(screen.getByText('在线 1 / 2')).toBeInTheDocument(); expect(screen.getAllByText('有效定位 1').length).toBeGreaterThanOrEqual(1); expect(screen.getByText('降级/离线 2')).toBeInTheDocument();