From 2c6fca90d2c415bb3ede292fdec64998a01705ed Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 17:29:29 +0800 Subject: [PATCH] feat(platform): add live map service action rail --- .../apps/web/src/pages/Realtime.tsx | 85 +++++++++++++++++++ .../apps/web/src/styles/global.css | 78 +++++++++++++++++ .../apps/web/src/test/App.test.tsx | 9 +- 3 files changed, 171 insertions(+), 1 deletion(-) diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index e24013e5..4e98d81b 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -1070,6 +1070,60 @@ export function Realtime({ secondaryDisabled: !selectedMapRow || !canOpenVehicle(selectedMapRow.vin) } ]; + const mapServiceActionItems = [ + { + title: '在线车辆', + value: `${onlineCount.toLocaleString()} 在线`, + detail: `在线率 ${formatPercent(onlineRate)},先确认客户当前能看到哪些车。`, + action: '只看在线', + color: onlineCount > 0 ? 'green' as const : 'orange' as const, + disabled: false, + onClick: () => applyFilters({ ...filters, online: 'online' }) + }, + { + title: '关注车辆', + value: `${mapAttentionRows.length.toLocaleString()} 关注`, + detail: mapAttentionRows[0] ? `${mapAttentionRows[0].plate || mapAttentionRows[0].vin}:${realtimeIssueLabels(mapAttentionRows[0]).join(';')}` : '当前没有明显离线、超时或坐标异常车辆。', + action: '异常优先', + color: mapAttentionRows.length > 0 ? 'orange' as const : 'green' as const, + disabled: false, + onClick: () => applyFilters({ ...filters, serviceStatus: 'degraded' }) + }, + { + title: '路线回放', + value: selectedMapRow?.plate || selectedMapRow?.vin || '先选车', + detail: selectedMapRow ? `${dataFreshness(selectedMapRow).detail},回放位置、速度和里程变化。` : '从地图或车辆列表选择一辆车后进入轨迹回放。', + action: '轨迹回放', + color: selectedMapRow ? 'blue' as const : 'grey' as const, + disabled: !selectedMapRow || !canOpenVehicle(selectedMapRow.vin), + onClick: () => selectedMapRow && onOpenHistory?.(selectedMapRow.vin, selectedVehicleProtocol) + }, + { + title: '统计查询', + value: selectedMapRow?.totalMileageKm != null ? `${selectedMapRow.totalMileageKm} km` : '待选车', + detail: '按选中车辆进入统计查询,核对区间里程和日报闭合。', + action: '统计查询', + color: selectedMapRow?.totalMileageKm != null ? 'blue' as const : 'grey' as const, + disabled: !selectedMapRow || !canOpenVehicle(selectedMapRow.vin), + onClick: () => { + if (!selectedMapRow) return; + window.location.hash = buildAppHash({ + page: 'mileage', + keyword: selectedMapRow.vin, + protocol: selectedVehicleProtocol + }); + } + }, + { + title: '证据导出', + value: `${rows.length.toLocaleString()} 当前页`, + detail: '导出当前地图车辆清单,作为客户问询和运营交接证据。', + action: '导出CSV', + color: rows.length > 0 ? 'blue' as const : 'grey' as const, + disabled: rows.length === 0, + onClick: exportRealtime + } + ]; const mapFieldCommandItems = [ { title: '全域定位', @@ -1470,6 +1524,37 @@ export function Realtime({ +
+
+ + 地图服务行动条 + {amapConfigured ? 'Live Map 可用' : '坐标预览'} + 0 ? 'orange' : 'green'}>{mapAttentionRows.length.toLocaleString()} 辆关注 + + 把 Live Map 当作客户服务入口:先看在线车辆和关注车辆,再进入路线回放、统计查询和证据导出。 + + 地图页的第一动作要围绕车辆服务,而不是围绕协议来源;客户从地图进入监控、复盘、统计和交付。 + +
+
+ {mapServiceActionItems.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 a468b0a5..9266b433 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -2891,6 +2891,82 @@ button.vp-realtime-command-item:focus-visible { align-items: center; } +.vp-map-service-rail { + display: grid; + grid-template-columns: minmax(300px, 0.64fr) minmax(0, 1.36fr); + gap: 12px; +} + +.vp-map-service-rail-summary { + min-height: 174px; + padding: 14px; + border: 1px solid rgba(22, 100, 255, 0.22); + border-radius: var(--vp-radius); + background: #f5f9ff; + display: grid; + align-content: start; + gap: 10px; +} + +.vp-map-service-rail-summary .semi-typography { + line-height: 21px; +} + +.vp-map-service-rail-actions { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 10px; +} + +.vp-map-service-rail-action { + min-height: 174px; + 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; + align-content: start; + gap: 8px; +} + +.vp-map-service-rail-action:disabled { + cursor: not-allowed; + opacity: 0.58; +} + +.vp-map-service-rail-action:not(:disabled):hover, +.vp-map-service-rail-action:not(:disabled):focus-visible { + border-color: rgba(22, 100, 255, 0.42); + background: #f5f9ff; + outline: none; +} + +.vp-map-service-rail-action strong { + color: var(--vp-text); + font-size: 17px; + line-height: 23px; + font-weight: 700; + word-break: break-word; +} + +.vp-map-service-rail-action span { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + +.vp-map-service-rail-action em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 18px; +} + .vp-map-decision-board { display: grid; grid-template-columns: minmax(300px, 0.72fr) minmax(0, 1.28fr); @@ -7728,6 +7804,8 @@ button.vp-realtime-command-item:focus-visible { .vp-realtime-impact-board, .vp-realtime-impact-grid, .vp-source-coverage-grid, + .vp-map-service-rail, + .vp-map-service-rail-actions, .vp-map-decision-board, .vp-map-decision-grid, .vp-map-kpi-strip, 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 8fcbd2ca..89a348be 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -9484,6 +9484,13 @@ test('shows realtime freshness status for recently updated and stale vehicles', render(); expect(await screen.findByRole('heading', { name: '车辆实时地图' })).toBeInTheDocument(); + expect(screen.getByText('地图服务行动条')).toBeInTheDocument(); + expect(screen.getByText('把 Live Map 当作客户服务入口:先看在线车辆和关注车辆,再进入路线回放、统计查询和证据导出。')).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: '地图服务行动条 证据导出 导出CSV' })).toBeInTheDocument(); expect(screen.getAllByText('数据新鲜').length).toBeGreaterThan(0); expect(screen.getAllByText('更新超时').length).toBeGreaterThan(0); expect(screen.getByText('地图客户决策')).toBeInTheDocument(); @@ -9505,7 +9512,7 @@ test('shows realtime freshness status for recently updated and stale vehicles', expect(screen.getByRole('button', { name: '地图现场指挥 轨迹复盘 打开轨迹' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '地图现场指挥 通知证据 告警事件' })).toBeInTheDocument(); expect(screen.getByText('实时盯车')).toBeInTheDocument(); - expect(screen.getByText('异常优先')).toBeInTheDocument(); + expect(screen.getAllByText('异常优先').length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText('轨迹复盘').length).toBeGreaterThanOrEqual(1); expect(screen.getAllByText('查询导出').length).toBeGreaterThanOrEqual(1); expect(screen.getByRole('button', { name: '地图监控任务 实时盯车 只看在线' })).toBeInTheDocument();