From 36dbc26ea7c92ec44f7b123555ffdcca417e10a9 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 04:36:57 +0800 Subject: [PATCH] feat(platform): add realtime customer monitoring path --- .../apps/web/src/pages/Realtime.tsx | 91 +++++++++++++++++++ .../apps/web/src/styles/global.css | 88 ++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 7 ++ 3 files changed, 186 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index 7677eeed..781019cd 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -1018,6 +1018,60 @@ export function Realtime({ selectedProtocol: selectedVehicleProtocol, amapConfigured }), '地图客户决策说明'); + const realtimeCustomerJourneyItems = [ + { + step: '01', + title: '只看在线', + value: `${onlineCount.toLocaleString()} 在线`, + detail: '先确认客户当前能看到哪些车辆仍在上报。', + action: '在线车辆', + color: onlineCount > 0 ? 'green' as const : 'orange' as const, + onClick: () => applyFilters({ ...filters, online: 'online' }), + disabled: false + }, + { + step: '02', + title: '地图态势', + value: `${locatedCount.toLocaleString()} 有定位`, + detail: '在地图上确认车辆位置、定位有效性和分布情况。', + action: '查看地图', + color: locatedCount > 0 ? 'blue' as const : 'orange' as const, + onClick: () => { + window.location.hash = buildAppHash({ page: 'map', protocol: filters.protocol, filters }); + }, + disabled: false + }, + { + step: '03', + title: '异常优先', + value: `${Math.max(degradedCount, sourceIssueRows.length).toLocaleString()} 关注`, + detail: '优先处理离线、更新超时、坐标无效或服务降级车辆。', + action: '关注车辆', + color: degradedCount > 0 || sourceIssueRows.length > 0 ? 'orange' as const : 'green' as const, + onClick: () => applyFilters({ ...filters, serviceStatus: 'degraded' }), + disabled: false + }, + { + step: '04', + title: '单车复盘', + value: selectedMapRow?.plate || selectedMapRow?.vin || '未选车', + detail: selectedMapRow ? '围绕选中车辆回放轨迹、核对里程和告警。' : '先在地图或表格中选择一辆可查询车辆。', + action: '轨迹回放', + color: selectedMapRow ? 'blue' as const : 'grey' as const, + onClick: () => selectedMapRow && onOpenHistory?.(selectedMapRow.vin, selectedVehicleProtocol), + disabled: !selectedMapRow || !canOpenVehicle(selectedMapRow.vin) + }, + { + step: '05', + title: '数据交付', + value: `${rows.length.toLocaleString()} 当前页`, + detail: '导出当前实时车辆清单,用于客户问询和运营交接。', + action: '导出 CSV', + color: rows.length > 0 ? 'blue' as const : 'grey' as const, + onClick: exportRealtime, + disabled: rows.length === 0 + } + ]; if (mode === 'map') { return ( @@ -1303,6 +1357,43 @@ export function Realtime({ return (
+ +
+ + 客户实时监控 + {amapConfigured ? '高德地图可用' : '坐标预览'} + 0 || staleCount > 0 ? 'orange' : 'green'}>{degradedCount.toLocaleString()} 辆降级 / {staleCount.toLocaleString()} 辆超时 + + 从实时列表直接完成车辆监控闭环 + + 先筛在线车辆,再看地图位置,异常车辆优先处置,最后进入单车轨迹、里程、告警或导出当前页。 + + + + + + + +
+
+ {realtimeCustomerJourneyItems.map((item) => ( + + ))} +
+
{ const nextFilters = values as Record; diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index f23b090e..94a1e72e 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -909,6 +909,92 @@ body { gap: 8px; } +.vp-realtime-customer-board { + margin-bottom: 16px; + overflow: hidden; +} + +.vp-realtime-customer-board .semi-card-body { + display: grid; + grid-template-columns: minmax(280px, 0.58fr) minmax(0, 1fr); +} + +.vp-realtime-customer-summary { + padding: 18px; + border-right: 1px solid var(--vp-border); + background: #f8fbff; + display: grid; + gap: 12px; + align-content: start; +} + +.vp-realtime-customer-steps { + padding: 16px; + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 10px; + background: #ffffff; +} + +.vp-realtime-customer-step { + min-height: 152px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: 8px; + background: #fbfcff; + text-align: left; + cursor: pointer; + font: inherit; + display: grid; + gap: 8px; + align-content: start; +} + +.vp-realtime-customer-step:hover, +.vp-realtime-customer-step:focus-visible { + border-color: rgba(22, 100, 255, 0.45); + box-shadow: var(--vp-shadow-sm); + outline: none; +} + +.vp-realtime-customer-step:disabled { + cursor: not-allowed; + opacity: 0.62; +} + +.vp-realtime-customer-step > span { + width: 28px; + height: 28px; + border-radius: 50%; + background: #1664ff; + color: #fff; + font-size: 12px; + font-weight: 700; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.vp-realtime-customer-step strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; +} + +.vp-realtime-customer-step small { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; +} + +.vp-realtime-customer-step em { + color: #1664ff; + font-style: normal; + font-size: 12px; + font-weight: 600; + align-self: end; +} + .vp-realtime-command-board { margin-bottom: 16px; padding: 14px; @@ -4529,6 +4615,8 @@ button.vp-realtime-command-item:focus-visible { .vp-time-review-grid, .vp-time-audit-strip, .vp-time-audit-grid, + .vp-realtime-customer-board .semi-card-body, + .vp-realtime-customer-steps, .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 e48360c5..130f8ac0 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -9030,6 +9030,13 @@ test('frames realtime page as one vehicle service with source evidence', async ( expect(await screen.findByRole('heading', { name: '实时监控' })).toBeInTheDocument(); expect(screen.getByText('高德地图待配置')).toBeInTheDocument(); + expect(screen.getByText('客户实时监控')).toBeInTheDocument(); + expect(screen.getByText('从实时列表直接完成车辆监控闭环')).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.getByText('车辆核心数据')).toBeInTheDocument(); expect(screen.getByText('实时通道')).toBeInTheDocument();