+
+
+
+ {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();