From afbe29b8de1b3d27ca269c5f34362bb601170832 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 5 Jul 2026 17:17:53 +0800 Subject: [PATCH] feat(platform): add alert notification channel closure --- .../apps/web/src/pages/Quality.tsx | 52 +++++++++++ .../apps/web/src/styles/global.css | 88 +++++++++++++++++++ .../apps/web/src/test/App.test.tsx | 9 +- 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/vehicle-data-platform/apps/web/src/pages/Quality.tsx b/vehicle-data-platform/apps/web/src/pages/Quality.tsx index 022e3a59..30fc5367 100644 --- a/vehicle-data-platform/apps/web/src/pages/Quality.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Quality.tsx @@ -843,6 +843,17 @@ export function Quality({ onClick: () => copyNotificationHandoff() } ]; + const notificationChannelItems = (primaryPolicy?.channel || '站内告警') + .split('/') + .map((item) => item.trim()) + .filter(Boolean) + .map((channel) => ({ + channel, + target: primaryPolicy?.target || '待配置责任人', + condition: primaryPolicy?.condition || '告警规则命中', + escalation: formatEscalationMinutes(primaryPolicy?.escalationMinutes) || '未配置升级', + acceptance: primaryPolicy?.acceptanceCriteria || '车辆服务状态恢复,实时、轨迹、历史和里程证据可查询。' + })); const loadIssues = (values: Record = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => { setLoadingIssues(true); @@ -1131,6 +1142,47 @@ export function Quality({ + +
+
+ + {primaryPolicy?.name || '策略待配置'} + 0 ? 'red' : priorityP1Count > 0 ? 'orange' : 'green'}> + P0 {priorityP0Count.toLocaleString()} / P1 {priorityP1Count.toLocaleString()} + + {customerDecision.label} + + 通知不是结束,客户要看到触达、升级和恢复验收 + + 把站内、邮件、企业微信这些触达方式按客户能理解的处置路径展示:谁收到、为什么收到、多久升级、恢复后怎么验收。 + + + + + {onOpenNotificationRules ? : null} + +
+
+ {notificationChannelItems.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 9c5f1328..623c4d84 100644 --- a/vehicle-data-platform/apps/web/src/styles/global.css +++ b/vehicle-data-platform/apps/web/src/styles/global.css @@ -3796,6 +3796,92 @@ button.vp-realtime-command-item:focus-visible { line-height: 20px; } +.vp-alert-channel-board { + display: grid; + grid-template-columns: minmax(280px, 0.72fr) minmax(0, 1.28fr); + gap: 16px; +} + +.vp-alert-channel-summary { + min-height: 168px; + padding: 12px; + border: 1px solid rgba(22, 100, 255, 0.2); + border-radius: var(--vp-radius); + background: #f5f9ff; + display: grid; + gap: 10px; + align-content: start; +} + +.vp-alert-channel-summary strong { + color: var(--vp-text); + font-size: 18px; + line-height: 24px; + word-break: break-word; +} + +.vp-alert-channel-summary span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-alert-channel-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.vp-alert-channel-item { + min-height: 168px; + 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; + gap: 9px; + align-content: start; +} + +.vp-alert-channel-item:hover, +.vp-alert-channel-item:focus-visible { + border-color: rgba(22, 100, 255, 0.42); + background: #f5f9ff; + outline: none; +} + +.vp-alert-channel-item > div { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.vp-alert-channel-item strong { + color: var(--vp-text); + font-size: 16px; + line-height: 22px; + font-weight: 700; + word-break: break-word; +} + +.vp-alert-channel-item span { + color: var(--vp-text-muted); + font-size: 13px; + line-height: 20px; +} + +.vp-alert-channel-item em { + color: var(--vp-primary); + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 18px; +} + .vp-alert-center-board { display: grid; grid-template-columns: minmax(280px, 0.7fr) minmax(0, 1.3fr); @@ -7592,6 +7678,8 @@ button.vp-realtime-command-item:focus-visible { .vp-alert-decision-grid, .vp-alert-sla-board, .vp-alert-sla-grid, + .vp-alert-channel-board, + .vp-alert-channel-grid, .vp-alert-impact-board, .vp-alert-impact-grid, .vp-alert-dispatch-grid, 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 bf34c7a5..d3453860 100644 --- a/vehicle-data-platform/apps/web/src/test/App.test.tsx +++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx @@ -3769,7 +3769,14 @@ test('shows alert rule and notification policy workspace on quality page', async expect(screen.getAllByText('接入运维 + 业务责任人').length).toBeGreaterThan(0); expect(screen.getByText('站内告警 / 邮件 / 企业微信')).toBeInTheDocument(); expect(screen.getAllByText('30 分钟升级').length).toBeGreaterThanOrEqual(1); - expect(screen.getByText('验收:来源恢复并持续 10 分钟,车辆服务可查到实时与历史证据')).toBeInTheDocument(); + expect(screen.getAllByText('验收:来源恢复并持续 10 分钟,车辆服务可查到实时与历史证据').length).toBeGreaterThanOrEqual(1); + 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.getAllByText('接入运维 + 业务责任人').length).toBeGreaterThan(1); + expect(screen.getAllByText('无来源、VIN 缺失、存储不可写').length).toBeGreaterThanOrEqual(1); fireEvent.click(screen.getByRole('button', { name: '复制业务影响报告' })); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【告警业务影响报告】'));