feat(platform): add notification orchestration strip

This commit is contained in:
lingniu
2026-07-06 03:32:00 +08:00
parent 08f2b62362
commit 3e9128b441
3 changed files with 234 additions and 1 deletions

View File

@@ -761,6 +761,74 @@ export function NotificationRules() {
onClick: () => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')
}
];
const primaryPolicy = policies.find((policy) => policy.name.startsWith('P0')) ?? policies[0];
const notificationOrchestrationItems = [
{
label: '告警触发',
value: `${activeRuleCount.toLocaleString()} 类活跃规则`,
detail: `${p0RuleCount.toLocaleString()} 类 P0当前命中 ${totalActiveHits.toLocaleString()} 次。`,
color: p0RuleCount > 0 ? 'red' as const : activeRuleCount > 0 ? 'orange' as const : 'green' as const,
onClick: () => navigateHash('#/alert-events')
},
{
label: '受影响车辆',
value: `${priorityIssues.length.toLocaleString()} 待通知`,
detail: p0IssueCount > 0 ? `P0 ${p0IssueCount.toLocaleString()} 条,优先通知业务责任人。` : `${evidenceCompleteCount}/${priorityIssues.length || 0} 证据完整。`,
color: p0IssueCount > 0 ? 'red' as const : priorityIssues.length > 0 ? 'orange' as const : 'green' as const,
onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
},
{
label: '通知对象',
value: primaryPolicy?.target || primaryOwner,
detail: `渠道:${primaryPolicy?.channel || policies[0]?.channel || '待配置渠道'}`,
color: primaryPolicy?.target ? 'blue' as const : 'orange' as const,
onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
},
{
label: '升级时钟',
value: overdueCount > 0 ? `已超时 ${overdueCount.toLocaleString()}` : dueSoonCount > 0 ? `临近 ${dueSoonCount.toLocaleString()}` : '正常跟进',
detail: primaryPolicy ? formatEscalationMinutes(primaryPolicy.escalationMinutes) : '缺少升级策略。',
color: overdueCount > 0 ? 'red' as const : dueSoonCount > 0 ? 'orange' as const : 'green' as const,
onClick: () => copyText(slaEscalationReport(slaRows, release), 'SLA升级报告')
},
{
label: '恢复验收',
value: primaryPolicy?.acceptanceCriteria || '按车辆服务证据验收',
detail: '恢复后必须复核实时、轨迹、原始记录和规则命中状态。',
color: primaryPolicy?.acceptanceCriteria ? 'green' as const : 'orange' as const,
onClick: () => copyText(notificationRulesRunbook(plan, release), '通知规则Runbook')
}
];
const notificationOrchestrationActions = [
{
label: '查看告警',
action: '告警队列',
color: priorityIssues.length > 0 ? 'orange' as const : 'green' as const,
disabled: false,
onClick: () => navigateHash('#/alert-events')
},
{
label: '复制通知',
action: '待通知',
color: priorityIssues.length > 0 ? 'blue' as const : 'grey' as const,
disabled: priorityIssues.length === 0,
onClick: () => copyText(priorityIssueDigest(plan, release), '待通知告警')
},
{
label: '复制SLA',
action: 'SLA报告',
color: overdueCount > 0 ? 'red' as const : dueSoonCount > 0 ? 'orange' as const : 'green' as const,
disabled: slaRows.length === 0,
onClick: () => copyText(slaEscalationReport(slaRows, release), 'SLA升级报告')
},
{
label: '导出演练',
action: '演练包',
color: 'blue' as const,
disabled: false,
onClick: () => copyText(alertNotificationDrillPackage(plan, health, release), '通知演练包')
}
];
const exportNotificationRules = () => {
const rows = notificationExportRows(plan, release);
if (rows.length === 0) {
@@ -812,6 +880,53 @@ export function NotificationRules() {
))}
</div>
</Card>
<section className="vp-notification-orchestration-strip" aria-label="客户通知编排条">
<div className="vp-notification-orchestration-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={p0IssueCount > 0 || overdueCount > 0 ? 'red' : priorityIssues.length > 0 ? 'orange' : 'green'}>
{priorityIssues.length > 0 ? `${priorityIssues.length.toLocaleString()} 待通知` : '当前稳定'}
</Tag>
<Tag color={primaryPolicy?.target ? 'blue' : 'orange'}>{primaryPolicy?.channel || '待配置渠道'}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>
SLA
</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-notification-orchestration-grid">
{notificationOrchestrationItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-notification-orchestration-item"
onClick={item.onClick}
aria-label={`客户通知编排 ${item.label} ${item.value}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</button>
))}
</div>
<div className="vp-notification-orchestration-actions">
{notificationOrchestrationActions.map((item) => (
<button
key={item.label}
type="button"
className="vp-notification-orchestration-action"
disabled={item.disabled}
onClick={item.onClick}
aria-label={`客户通知编排动作 ${item.label} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<span>{item.action}</span>
</button>
))}
</div>
</section>
<Card bordered loading={loading} title="客户通知常问" style={{ marginBottom: 16 }}>
<div className="vp-notification-question-board">
<div className="vp-notification-question-summary">