From 7642f9c9eb005603c9895d763a4dda8df6d745b5 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 01:45:20 +0800 Subject: [PATCH] feat(platform): add alert notification center --- .../apps/web/src/pages/Quality.tsx | 76 ++++++++++++++++++ .../apps/web/src/styles/global.css | 77 +++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 12 ++- 3 files changed, 161 insertions(+), 4 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/pages/Quality.tsx b/vehicle-data-platform/apps/web/src/pages/Quality.tsx index 6f39fac7..72ab0295 100644 --- a/vehicle-data-platform/apps/web/src/pages/Quality.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Quality.tsx @@ -896,6 +896,82 @@ export function Quality({ return (
+ +
+
+ + {impactSeverity.label} + 0 ? 'red' : 'green'}>P0 {priorityP0Count.toLocaleString()} + 0 ? 'orange' : 'green'}>活跃规则 {activeRuleCount.toLocaleString()} + + {issueVehicleCount.toLocaleString()} 辆车受影响 + + 告警中心面向客户处置:先判断影响车辆和优先级,再通知责任团队,并用实时、轨迹、RAW、里程证据闭环。 + + + + + {onOpenNotificationRules ? : null} + +
+
+ {[ + { + label: '影响车辆', + value: `${issueVehicleCount.toLocaleString()} 辆`, + detail: `${issueRecordCount.toLocaleString()} 条告警记录,优先确认车辆服务是否仍可用。`, + color: issueVehicleCount > 0 ? 'orange' as const : 'green' as const, + onClick: () => undefined, + disabled: true + }, + { + label: '优先队列', + value: `P0 ${priorityP0Count.toLocaleString()} / P1 ${priorityP1Count.toLocaleString()}`, + detail: priorityRows.length > 0 ? '已按优先级生成可复制通知和处置工单。' : '当前没有待通知车辆。', + color: priorityP0Count > 0 ? 'red' as const : priorityP1Count > 0 ? 'orange' as const : 'green' as const, + onClick: copyPriorityDigest, + disabled: priorityRows.length === 0 + }, + { + label: '主要来源', + value: primaryProtocolImpact ? qualityProtocolLabel(primaryProtocolImpact.name) : '-', + detail: primaryProtocolImpact ? `${primaryProtocolImpact.count.toLocaleString()} 条问题,点击筛选该来源。` : '暂无来源问题。', + color: primaryProtocolImpact ? 'blue' as const : 'green' as const, + onClick: () => primaryProtocolImpact && drillProtocol(primaryProtocolImpact.name), + disabled: !primaryProtocolImpact + }, + { + label: '主要问题', + value: primaryIssueImpact ? qualityIssueLabel(primaryIssueImpact.name) : '-', + detail: primaryIssueImpact ? `${primaryIssueImpact.count.toLocaleString()} 条命中,点击筛选该问题。` : '暂无问题类型。', + color: primaryIssueImpact ? 'orange' as const : 'green' as const, + onClick: () => primaryIssueImpact && drillIssueType(primaryIssueImpact.name), + disabled: !primaryIssueImpact + }, + { + label: '链路状态', + value: storageWritable ? '可写' : '异常', + detail: `Kafka Lag ${formatLag(health?.kafkaLag)},容量发现 ${capacityFindingCount.toLocaleString()} 项。`, + color: storageWritable && capacityFindingCount === 0 ? 'green' as const : 'red' as const, + onClick: loadHealth, + disabled: false + } + ].map((item) => ( + + ))} +
+
+
{[ { 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 702a3d74..2d1d6835 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -1237,6 +1237,81 @@ button.vp-realtime-command-item:focus-visible { gap: 12px; } +.vp-alert-center-board { + display: grid; + grid-template-columns: minmax(280px, 0.7fr) minmax(0, 1.3fr); + gap: 16px; +} + +.vp-alert-center-summary { + min-height: 168px; + padding: 12px; + border: 1px solid rgba(245, 158, 11, 0.32); + border-radius: var(--vp-radius); + background: #fff8ed; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-alert-center-summary strong { + color: var(--vp-text); + font-size: 20px; + line-height: 26px; + word-break: break-word; +} + +.vp-alert-center-summary span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-alert-center-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 12px; +} + +.vp-alert-center-item { + min-height: 168px; + padding: 12px; + border: 1px solid var(--vp-border); + border-radius: var(--vp-radius); + background: #fbfcff; + display: grid; + gap: 10px; + align-content: start; + text-align: left; + font: inherit; + cursor: pointer; +} + +.vp-alert-center-item:disabled { + cursor: not-allowed; + opacity: 0.68; +} + +.vp-alert-center-item:not(:disabled):hover, +.vp-alert-center-item:not(:disabled):focus-visible { + border-color: rgba(245, 158, 11, 0.48); + background: #fff8ed; + outline: none; +} + +.vp-alert-center-item strong { + color: var(--vp-text); + font-size: 20px; + line-height: 26px; + word-break: break-word; +} + +.vp-alert-center-item span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + .vp-alert-flow-item { min-height: 96px; padding: 12px; @@ -3056,6 +3131,8 @@ button.vp-realtime-command-item:focus-visible { .vp-alert-impact-board, .vp-alert-impact-grid, .vp-alert-dispatch-grid, + .vp-alert-center-board, + .vp-alert-center-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 3e2338ef..51c1bafb 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -3543,15 +3543,19 @@ test('shows alert rule and notification policy workspace on quality page', async render(); + expect(await screen.findByText('告警事件与通知中心')).toBeInTheDocument(); + expect(screen.getByText('客户处置:先判断影响车辆和优先级,再通知责任团队,并用实时、轨迹、RAW、里程证据闭环。', { exact: false })).toBeInTheDocument(); + expect(screen.getByText('链路状态')).toBeInTheDocument(); + expect(screen.getAllByText('优先队列').length).toBeGreaterThanOrEqual(1); expect(await screen.findByText('告警触发规则')).toBeInTheDocument(); expect(screen.getByText('业务影响评估')).toBeInTheDocument(); expect(screen.getByRole('button', { name: '复制业务影响报告' })).toBeInTheDocument(); expect(screen.getAllByText('高风险').length).toBeGreaterThan(0); expect(screen.getByText('3 辆受影响')).toBeInTheDocument(); - expect(screen.getByText('影响车辆')).toBeInTheDocument(); - expect(screen.getByText('3 辆')).toBeInTheDocument(); - expect(screen.getByText('P0 0 / P1 0')).toBeInTheDocument(); - expect(screen.getAllByText('车辆服务').length).toBeGreaterThan(0); + expect(screen.getAllByText('影响车辆').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('3 辆').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('P0 0 / P1 0').length).toBeGreaterThanOrEqual(1); + expect(screen.getAllByText('车辆服务').length).toBeGreaterThan(0); expect(screen.getAllByText('暂无数据来源').length).toBeGreaterThan(0); expect(screen.getAllByText('异常').length).toBeGreaterThan(0); expect(screen.getByText('4 类活跃')).toBeInTheDocument();