From 059e4586a74daa52d70640fcf73cf1f0a8206af6 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 21:22:04 +0800 Subject: [PATCH] feat(platform): add realtime priority queue --- .../apps/web/src/pages/Realtime.tsx | 52 +++++++ .../apps/web/src/styles/global.css | 145 ++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 6 + 3 files changed, 203 insertions(+) diff --git a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx index fff84303..eec7f65f 100644 --- a/vehicle-data-platform/apps/web/src/pages/Realtime.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Realtime.tsx @@ -990,6 +990,10 @@ export function Realtime({ { label: '有效定位', value: locatedCount.toLocaleString(), color: locatedCount > 0 ? 'green' as const : 'orange' as const, helper: `定位率 ${formatPercent(locatedRate)}` }, { label: '需关注', value: mapAttentionRows.length.toLocaleString(), color: mapAttentionRows.length > 0 ? 'orange' as const : 'green' as const, helper: `${staleCount.toLocaleString()} 辆更新超时` } ]; + const realtimePriorityRows = mapAttentionRows.slice(0, 4); + const realtimePrioritySummary = realtimePriorityRows.length > 0 + ? `${realtimePriorityRows.length.toLocaleString()} 辆优先处置` + : '暂无阻断'; const missingCoordinateCount = Math.max(0, rows.length - locatedCount); const selectedVehicleProtocol = selectedMapRow ? filters.protocol || selectedMapRow.primaryProtocol || '' : ''; const selectedVehicleLabel = selectedMapRow?.plate || selectedMapRow?.vin || '未选择车辆'; @@ -2210,6 +2214,54 @@ export function Realtime({ ))} +
+
+ + 实时处置优先级 + 0 ? 'orange' : 'green'}>{realtimePrioritySummary} + 当前页 {rows.length.toLocaleString()} 辆 + + + 按客户影响排序:先处理不能看车、定位无效、数据通道不完整和更新超时的车辆。 + + + 这不是运维协议清单,而是实时运营的处置队列;每辆车都能直接进入告警、轨迹和车辆档案。 + +
+
+ {realtimePriorityRows.length === 0 ? ( +
+ 实时稳定 + 当前筛选范围暂无优先处置车辆 + 继续保持自动刷新,并用地图、时间窗和导出能力支撑客户问询。 +
+ ) : realtimePriorityRows.map((row, index) => { + const label = row.plate || row.vin || '未知车辆'; + const protocol = filters.protocol || row.primaryProtocol || ''; + const issueText = realtimeIssueLabels(row).join(';'); + return ( +
+
{index + 1}
+
+ + {vehicleServiceStatus(row).label} + {dataFreshness(row).label} + {isValidCoordinate(row) ? '有定位' : '无定位'} + + {label} + {issueText} + {row.vin} / {protocol || '全部数据通道'} / {row.lastSeen || '-'} +
+
+ + + +
+
+ ); + })} +
+
diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index 83b4389a..b1fe55ec 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -2732,6 +2732,138 @@ body { line-height: 18px; } +.vp-realtime-priority-board { + margin-bottom: 16px; + border: 1px solid rgba(255, 122, 69, 0.22); + border-radius: var(--vp-radius); + background: #fff; + display: grid; + grid-template-columns: minmax(300px, 0.48fr) minmax(0, 1.52fr); + overflow: hidden; + box-shadow: var(--vp-shadow-sm); +} + +.vp-realtime-priority-summary { + padding: 18px; + border-right: 1px solid var(--vp-border); + background: #fff8f3; + display: grid; + gap: 12px; + align-content: start; +} + +.vp-realtime-priority-summary .semi-typography-secondary { + font-size: 13px; + line-height: 20px; +} + +.vp-realtime-priority-grid { + min-width: 0; + padding: 14px; + display: grid; + gap: 10px; +} + +.vp-realtime-priority-item { + min-width: 0; + padding: 12px; + border: 1px solid rgba(255, 122, 69, 0.22); + border-radius: 8px; + background: #fffdfb; + display: grid; + grid-template-columns: 36px minmax(0, 1fr) auto; + gap: 12px; + align-items: center; +} + +.vp-realtime-priority-rank { + width: 32px; + height: 32px; + border-radius: 8px; + background: #fff1e8; + color: #d25f00; + font-size: 15px; + font-weight: 800; + line-height: 32px; + text-align: center; +} + +.vp-realtime-priority-main { + min-width: 0; + display: grid; + gap: 6px; +} + +.vp-realtime-priority-main strong { + color: var(--vp-text); + font-size: 17px; + line-height: 23px; + word-break: break-word; +} + +.vp-realtime-priority-main span, +.vp-realtime-priority-main small { + color: var(--vp-text-muted); + font-size: 12px; + line-height: 18px; + word-break: break-word; +} + +.vp-realtime-priority-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: flex-end; +} + +.vp-realtime-priority-actions button { + min-height: 30px; + padding: 0 10px; + border: 1px solid rgba(22, 100, 255, 0.22); + border-radius: 8px; + background: #f8fbff; + color: var(--vp-primary); + cursor: pointer; + font: inherit; + font-size: 12px; + font-weight: 700; +} + +.vp-realtime-priority-actions button:hover, +.vp-realtime-priority-actions button:focus-visible { + border-color: rgba(22, 100, 255, 0.45); + background: #eef5ff; + outline: none; +} + +.vp-realtime-priority-actions button:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.vp-realtime-priority-empty { + min-height: 118px; + padding: 14px; + border: 1px solid rgba(67, 160, 71, 0.2); + border-radius: 8px; + background: #f8fff8; + display: grid; + gap: 8px; + align-content: center; +} + +.vp-realtime-priority-empty strong { + color: var(--vp-text); + font-size: 17px; + line-height: 24px; +} + +.vp-realtime-priority-empty span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + .vp-realtime-customer-board { margin-bottom: 16px; overflow: hidden; @@ -10159,6 +10291,7 @@ button.vp-realtime-command-item:focus-visible { .vp-time-delivery-grid, .vp-realtime-command-center, .vp-realtime-command-center-grid, + .vp-realtime-priority-board, .vp-realtime-customer-board .semi-card-body, .vp-realtime-question-board, .vp-realtime-question-grid, @@ -10357,6 +10490,18 @@ button.vp-realtime-command-item:focus-visible { grid-template-columns: 1fr; } + .vp-realtime-priority-item { + grid-template-columns: 1fr; + } + + .vp-realtime-priority-rank { + width: 100%; + } + + .vp-realtime-priority-actions { + justify-content: flex-start; + } + .vp-map-commandbar, .vp-mileage-console-head, .vp-map-canvas-toolbar { 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 8b3be0d5..ccfa20e7 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -9878,6 +9878,12 @@ test('realtime page prioritizes customer vehicle monitoring command center', asy expect(screen.getByRole('button', { name: '客户实时监控总控台 轨迹复盘 粤A实时1 轨迹回放' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: '客户实时监控总控台 导出交付 2 当前页 导出CSV' })).toBeInTheDocument(); expect(screen.getByText('来源证据仅用于可信度判断:JT808 / GB32960')).toBeInTheDocument(); + expect(screen.getByText('实时处置优先级')).toBeInTheDocument(); + expect(screen.getByText('按客户影响排序:先处理不能看车、定位无效、数据通道不完整和更新超时的车辆。')).toBeInTheDocument(); + expect(screen.getAllByText('粤A实时2').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText((content) => content.includes('坐标无效')).length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText((content) => content.includes('缺少 GB32960')).length).toBeGreaterThanOrEqual(1); + expect(screen.getByRole('button', { name: '实时处置优先级 1 粤A实时2 查看告警' })).toBeInTheDocument(); }); test('frames realtime page as one vehicle service with source evidence', async () => {