diff --git a/vehicle-data-platform/apps/web/src/pages/Quality.tsx b/vehicle-data-platform/apps/web/src/pages/Quality.tsx index 72ab0295..c89f7b3e 100644 --- a/vehicle-data-platform/apps/web/src/pages/Quality.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Quality.tsx @@ -723,6 +723,8 @@ export function Quality({ const dispatchOwnerRows = alertDispatchOwnerRows(rules); const priorityP0Count = priorityRows.filter((row) => row.priority === 'P0').length; const priorityP1Count = priorityRows.filter((row) => row.priority === 'P1').length; + const focusIssue = priorityRows[0]; + const focusLookup = focusIssue ? qualityIssueVehicleLookup(focusIssue) : undefined; const activeRulesForImpact = rules.filter((row) => row.count > 0); const storageWritable = !health || (health.tdengineWritable && health.mysqlWritable); const capacityFindingCount = health?.capacityFindings?.length ?? 0; @@ -972,6 +974,81 @@ export function Quality({ + +
+
+ + + {focusIssue ? focusIssue.priority : '无待处置'} + + + {focusIssue ? qualityIssueLabel(focusIssue.issueType) : '车辆服务正常'} + + + {focusIssue?.vehicleLabel || '暂无优先车辆'} + + {focusIssue + ? `${focusIssue.actionLabel}:${focusIssue.actionDetail || focusIssue.detail || '请结合实时、轨迹、RAW 和里程证据闭环。'}` + : '当前没有进入优先队列的车辆,保持实时监控并定期复核告警规则。'} + + + + + +
+
+ {[ + { + label: '实时确认', + value: focusIssue ? '看在线' : '待命', + detail: '先确认车辆是否仍在线、最后上报来源和实时字段是否恢复。', + action: '实时状态', + color: focusIssue ? 'blue' as const : 'grey' as const, + disabled: !focusIssue || !focusLookup?.key || !onOpenRealtime, + onClick: () => focusIssue && openIssueRealtime(focusIssue) + }, + { + label: '轨迹证据', + value: focusIssue ? '看前后' : '待命', + detail: '按告警发生日期打开轨迹,判断是否影响车辆定位和回放。', + action: '轨迹回放', + color: focusIssue ? 'green' as const : 'grey' as const, + disabled: !focusIssue || !focusLookup?.key || !onOpenHistory, + onClick: () => focusIssue && openIssueHistory(focusIssue) + }, + { + label: '里程复核', + value: focusIssue ? '核统计' : '待命', + detail: '对同一天里程统计与轨迹证据做交叉校验,避免影响 BI。', + action: '里程统计', + color: focusIssue ? 'orange' as const : 'grey' as const, + disabled: !focusIssue || !focusLookup?.key || !onOpenMileage, + onClick: () => focusIssue && openIssueMileage(focusIssue) + }, + { + label: '原始证据', + value: focusIssue ? '查RAW' : '待命', + detail: '保留原始帧和解析字段,给平台、客户和研发确认责任边界。', + action: 'RAW证据', + color: focusIssue ? 'blue' as const : 'grey' as const, + disabled: !focusIssue || !focusLookup?.key || !onOpenRaw, + onClick: () => focusIssue && openIssueRaw(focusIssue) + } + ].map((item) => ( +
+ {item.label} + {item.value} + {item.detail} + +
+ ))} +
+
+
{[ { label: '问题车辆', value: issueVehicleCount.toLocaleString() }, diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css index 33bf4890..98e470f7 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -1417,6 +1417,68 @@ button.vp-realtime-command-item:focus-visible { line-height: 20px; } +.vp-alert-vehicle-task-board { + display: grid; + grid-template-columns: minmax(280px, 0.72fr) minmax(0, 1.28fr); + gap: 16px; +} + +.vp-alert-vehicle-focus { + min-height: 172px; + padding: 12px; + border: 1px solid rgba(239, 68, 68, 0.24); + border-radius: var(--vp-radius); + background: #fff7f7; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-alert-vehicle-focus strong { + color: var(--vp-text); + font-size: 20px; + line-height: 26px; + font-weight: 700; + word-break: break-word; +} + +.vp-alert-vehicle-focus span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-alert-vehicle-task-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.vp-alert-vehicle-task-item { + min-height: 172px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: var(--vp-radius); + background: #fbfcff; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-alert-vehicle-task-item strong { + color: var(--vp-text); + font-size: 20px; + line-height: 26px; + font-weight: 700; + word-break: break-word; +} + +.vp-alert-vehicle-task-item span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + .vp-alert-flow-item { min-height: 96px; padding: 12px; @@ -3344,6 +3406,8 @@ button.vp-realtime-command-item:focus-visible { .vp-alert-dispatch-grid, .vp-alert-center-board, .vp-alert-center-grid, + .vp-alert-vehicle-task-board, + .vp-alert-vehicle-task-grid, .vp-alert-command-board, .vp-alert-command-grid, .vp-notification-coverage-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 d7f7d569..ca9a188d 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -3727,6 +3727,13 @@ test('shows actionable priority queue on quality page', async () => { render(); expect(await screen.findByText('处置优先队列')).toBeInTheDocument(); + expect(screen.getByText('车辆风险处置任务板')).toBeInTheDocument(); + expect(screen.getAllByText('粤A优先1 / VIN-P0-001').length).toBeGreaterThan(0); + expect(screen.getByText('实时确认')).toBeInTheDocument(); + expect(screen.getAllByText('轨迹证据').length).toBeGreaterThan(0); + expect(screen.getByText('里程复核')).toBeInTheDocument(); + expect(screen.getAllByText('原始证据').length).toBeGreaterThan(0); + expect(screen.getByText('先确认车辆是否仍在线、最后上报来源和实时字段是否恢复。')).toBeInTheDocument(); expect(screen.getAllByText('P0').length).toBeGreaterThan(0); expect(screen.getAllByText('粤A优先1 / VIN-P0-001').length).toBeGreaterThan(0); expect(screen.getAllByText('确认平台转发').length).toBeGreaterThan(0);