// artboard-alarm.jsx — Event rule engine (告警事件 / 运维通知 / 业务事件)
// "事件规则" — 抽象事件,触发条件 + 通知/动作
const EVENT_KINDS = [
{ id: "all", label: "全部", count: 24, color: "var(--fg-2)", bg: "transparent" },
{ id: "alarm", label: "告警事件", count: 12, color: "var(--danger)", bg: "var(--danger-soft)", desc: "需立即处理 · P0/P1/P2" },
{ id: "ops", label: "运维通知", count: 7, color: "var(--warn)", bg: "var(--warn-soft)", desc: "保养/检修/合同到期" },
{ id: "biz", label: "业务事件", count: 4, color: "var(--info)", bg: "var(--info-soft)", desc: "里程/交付/调度状态" },
{ id: "auto", label: "自动化", count: 1, color: "var(--accent)", bg: "var(--accent-soft)", desc: "自动派单/路径下发" },
];
const KIND_META = {
alarm: { label: "告警", color: "var(--danger)", bg: "var(--danger-soft)" },
ops: { label: "运维", color: "var(--warn)", bg: "var(--warn-soft)" },
biz: { label: "业务", color: "var(--info)", bg: "var(--info-soft)" },
auto: { label: "自动化", color: "var(--accent)", bg: "var(--accent-soft)" },
};
// Mock rule library
const RULES = [
// 告警 (P0)
{ n:"H₂压力异常下降", kind:"alarm", c:"P0", on:true, h:"已触发 3 次", cond:"pressure < 35 MPa", actions:["站内","邮件","短信"], a:false },
{ n:"电堆过温保护", kind:"alarm", c:"P0", on:true, h:"已触发 1 次", cond:"stack.temp > 95℃", actions:["站内","短信","Webhook"], a:false },
{ n:"剩余氢量不足", kind:"alarm", c:"P0", on:true, h:"已触发 8 次", cond:"H₂ < 15% & 持续 60s", actions:["站内","邮件","路径下发"], a:true },
{ n:"胎压异常", kind:"alarm", c:"P1", on:true, h:"已触发 12 次", cond:"tire.pressure > 3.0 MPa", actions:["站内","推送"], a:false },
{ n:"超速预警", kind:"alarm", c:"P1", on:true, h:"已触发 47 次", cond:"speed > limit + 10 km/h", actions:["站内"], a:false },
{ n:"急加速密集", kind:"alarm", c:"P2", on:true, h:"已触发 18 次", cond:"3 次/分钟 within 5min", actions:["邮件"], a:false },
{ n:"夜间行驶", kind:"alarm", c:"P2", on:true, h:"已触发 6 次", cond:"22:00–06:00 + 行驶中", actions:["站内"], a:false },
// 运维通知
{ n:"总里程到达保养点", kind:"ops", c:"M1", on:true, h:"今日 4 辆", cond:"odometer % 20000 ≈ 0", actions:["站内","工单"], a:false },
{ n:"保养到期 30 天", kind:"ops", c:"M2", on:true, h:"今日 9 辆", cond:"days_to_maintenance ≤ 30", actions:["邮件","工单"], a:false },
{ n:"保险即将到期", kind:"ops", c:"M2", on:true, h:"本月 6 辆", cond:"days_to_insurance_end ≤ 60", actions:["邮件"], a:false },
{ n:"合同到期", kind:"ops", c:"M2", on:true, h:"本月 2 辆", cond:"days_to_contract_end ≤ 90", actions:["邮件","工单"], a:false },
{ n:"异常静止超 24h", kind:"ops", c:"M3", on:true, h:"今日 2 辆", cond:"idle_duration > 24h", actions:["站内"], a:false },
{ n:"长时间停留", kind:"ops", c:"M3", on:false, h:"已禁用", cond:"stop_duration > 4h & 非补能站", actions:["站内"], a:false },
// 业务事件
{ n:"今日交付完成", kind:"biz", c:"B1", on:true, h:"今日 38 单", cond:"delivery.status = 完成", actions:["站内","Webhook"], a:false },
{ n:"偏离规划路线", kind:"biz", c:"B2", on:true, h:"已触发 2 次", cond:"distance_from_route > 500m", actions:["站内","邮件"], a:false },
{ n:"进入禁行区域", kind:"biz", c:"B1", on:true, h:"已触发 0 次", cond:"geofence ∈ 禁行集合", actions:["站内","推送"], a:false },
{ n:"调度状态变更", kind:"biz", c:"B3", on:true, h:"实时", cond:"dispatch.status changed", actions:["Webhook"], a:false },
// 自动化
{ n:"低 SOC 自动派单至最近补能站", kind:"auto", c:"A1", on:true, h:"已执行 5 次", cond:"SOC < 20% & 行驶中", actions:["路径下发","站内"], a:false },
];
const ArtboardAlarm = () => {
const [activeKind, setActiveKind] = React.useState("all");
const [activeRule, setActiveRule] = React.useState(RULES.findIndex(r => r.a));
const filtered = RULES.filter(r => activeKind === "all" || r.kind === activeKind);
const rule = RULES[activeRule] || RULES[0];
return (
{/* Event-kind tabs */}
事件类型
{EVENT_KINDS.map(k => (
setActiveKind(k.id)}
style={{
padding:"5px 12px",
borderRadius:14,
fontSize:11,
cursor:"pointer",
background: activeKind === k.id ? k.bg : "transparent",
color: activeKind === k.id ? k.color : "var(--fg-2)",
border: "1px solid " + (activeKind === k.id ? k.color : "var(--border-1)"),
display:"flex", alignItems:"center", gap:6,
}}>
{k.label}
{k.count}
))}
{/* Rules list */}
{filtered.map((r,i)=>{
const realIdx = RULES.indexOf(r);
const meta = KIND_META[r.kind];
const isActive = realIdx === activeRule;
return (
setActiveRule(realIdx)}
style={{
padding:"10px 14px",
borderBottom:"1px solid var(--border-1)",
borderLeft: isActive ? "2px solid var(--accent)" : "2px solid transparent",
background: isActive ? "var(--accent-soft)" : "transparent",
cursor:"pointer"
}}>
{r.n}
{meta.label}·{r.c}
{r.cond}
{r.h}
);
})}
{/* Rule editor canvas */}
{/* Right: properties */}
);
};
// ── Rule editor canvas ──────────────────────────────────────
const RuleEditor = ({ rule }) => {
const meta = KIND_META[rule.kind];
const isAlarm = rule.kind === "alarm";
const isOps = rule.kind === "ops";
const isBiz = rule.kind === "biz";
const isAuto = rule.kind === "auto";
// Build dynamic conditions per rule
const conds = (() => {
if (rule.n.startsWith("胎压")) return [{lbl:"WHEN", v:"tire.pressure", op:">", val:"3.0 MPa"}];
if (rule.n.startsWith("剩余氢量")) return [{lbl:"WHEN", v:"vehicle.h2.level", op:"<", val:"15 %"}, {lbl:"AND", v:"持续时长", op:"≥", val:"60 秒"}, {lbl:"AND NOT", v:"vehicle.location.poi", op:"=", val:"补能站"}];
if (rule.n.startsWith("H₂")) return [{lbl:"WHEN", v:"h2.pressure", op:"<", val:"35 MPa"}, {lbl:"AND", v:"vehicle.state", op:"=", val:"行驶中"}];
if (rule.n.startsWith("电堆")) return [{lbl:"WHEN", v:"fc.stack.temp", op:">", val:"95 ℃"}, {lbl:"AND", v:"持续时长", op:"≥", val:"30 秒"}];
if (rule.n.startsWith("超速")) return [{lbl:"WHEN", v:"vehicle.speed", op:">", val:"道路限速 + 10 km/h"}];
if (rule.n.startsWith("急加速")) return [{lbl:"WHEN", v:"急加速次数", op:"≥", val:"3 次"}, {lbl:"WITHIN", v:"时间窗", op:"=", val:"5 分钟"}];
if (rule.n.startsWith("夜间")) return [{lbl:"WHEN", v:"local_time", op:"∈", val:"[22:00, 06:00]"}, {lbl:"AND", v:"vehicle.state", op:"=", val:"行驶中"}];
if (rule.n.startsWith("总里程")) return [{lbl:"WHEN", v:"vehicle.odometer", op:"% 20,000 ≈", val:"0 km"}, {lbl:"AND", v:"距离上次保养里程", op:">", val:"19,500 km"}];
if (rule.n.startsWith("保养")) return [{lbl:"WHEN", v:"距下次保养", op:"≤", val:"30 天"}];
if (rule.n.startsWith("保险")) return [{lbl:"WHEN", v:"距保险到期", op:"≤", val:"60 天"}];
if (rule.n.startsWith("合同")) return [{lbl:"WHEN", v:"距合同到期", op:"≤", val:"90 天"}];
if (rule.n.startsWith("异常静止")) return [{lbl:"WHEN", v:"vehicle.idle_duration", op:">", val:"24 小时"}, {lbl:"AND", v:"asset.status", op:"=", val:"租赁/运营"}];
if (rule.n.startsWith("长时间停留")) return [{lbl:"WHEN", v:"stop_duration", op:">", val:"4 小时"}, {lbl:"AND NOT", v:"vehicle.location.poi", op:"=", val:"补能站/停车场"}];
if (rule.n.startsWith("今日交付")) return [{lbl:"WHEN", v:"delivery.status", op:"=", val:"已完成"}];
if (rule.n.startsWith("偏离")) return [{lbl:"WHEN", v:"distance_from_route", op:">", val:"500 m"}, {lbl:"AND", v:"持续时长", op:"≥", val:"30 秒"}];
if (rule.n.startsWith("进入禁行")) return [{lbl:"WHEN", v:"vehicle.geofence", op:"∈", val:"禁行围栏集合"}];
if (rule.n.startsWith("调度")) return [{lbl:"WHEN", v:"dispatch.status", op:"changed", val:"任意 → 任意"}];
if (rule.n.startsWith("低 SOC")) return [{lbl:"WHEN", v:"SOC", op:"<", val:"20 %"}, {lbl:"AND", v:"vehicle.state", op:"=", val:"行驶中"}, {lbl:"AND", v:"附近补能站", op:"≤", val:"5 km"}];
return [{lbl:"WHEN", v:"自定义条件", op:"-", val:"-"}];
})();
// Build dynamic actions per rule
const actionDefs = rule.actions.map(a => {
if (a === "站内") return {kind:"notif", icon:"inbox", title:"站内消息", who:"业务部门负责人 · 调度组", note:"实时"};
if (a === "邮件") return {kind:"notif", icon:"mail", title:"邮件", who:"业务负责人 · 安全官 (3人)", note:"含轨迹截图"};
if (a === "短信") return {kind:"notif", icon:"phone", title:"短信", who:"司机 + 业务负责人", note:"P0 级专用"};
if (a === "推送") return {kind:"notif", icon:"bell", title:"应用内推送", who:"业务负责人 + 客户联系人", note:"含一键导航"};
if (a === "Webhook") return {kind:"action",icon:"plug", title:"Webhook", who:"https://erp.lingniu.cn/hook/v1", note:"POST 事件 JSON"};
if (a === "工单") return {kind:"action",icon:"clipboard",title:"创建工单", who:"维保中心 · 自动指派", note:"24h SLA"};
if (a === "路径下发") return {kind:"action",icon:"route", title:"路径下发", who:"附近补能站 · TBOX", note:"司机端弹窗确认"};
return {kind:"notif", icon:"bell", title:a, who:"-", note:""};
});
return (
{/* Header */}
{rule.n}
{meta.label} · {rule.c}
已启用
v 1.4 · 2026-04-12
{isAlarm && "条件命中后立即生成告警事件,按通知渠道发送至业务负责人;P0 级支持一键派单。"}
{isOps && "条件命中后生成运维通知;如已配置工单动作,将创建保养/检修工单进入维保流程。"}
{isBiz && "条件命中后生成业务事件;可推送至 Webhook 联动 ERP / TMS / 调度系统。"}
{isAuto && "条件命中后自动执行动作(路径下发 / 状态变更),司机端弹窗确认后生效。"}
{/* Editor — split: WHEN | LOGIC | THEN */}
{/* WHEN column */}
① 触发条件 · WHEN
{conds.map((c,i) => (
{c.lbl}
{c.v}
{c.op}
{c.val}
))}
添加条件
{/* LOGIC center */}
LOGIC GATE
{conds.length === 1 ? "A" : conds.map((c,i) => (c.lbl === "AND NOT" ? "¬" : "") + String.fromCharCode(65+i)).join(conds.length > 1 ? " ∧ " : "")}
{conds.length} 个条件 · 全部满足时触发
评估窗口
采样频率10 s · TBOX
抑制窗口15 min
事件 IDEVT-{rule.kind.toUpperCase()}-{rule.c}
{/* THEN column — actions */}
② 触发动作 · THEN
{actionDefs.map((a,i) => (
{a.title}
{a.kind === "action" ? "动作" : "通知"}
{a.who}
{a.note}
))}
添加动作
{/* Block library */}
组件库 · 拖入条件 / 动作
{[
{ic:"gauge", l:"数值阈值"},
{ic:"history", l:"持续时间"},
{ic:"pin", l:"地理围栏"},
{ic:"timeline", l:"时间窗口"},
{ic:"branch", l:"逻辑分支"},
{ic:"speed", l:"速度变化率"},
{ic:"chart", l:"趋势异常"},
{ic:"shield", l:"白名单"},
{ic:"clipboard", l:"创建工单"},
{ic:"plug", l:"Webhook"},
{ic:"route", l:"路径下发"},
{ic:"mail", l:"邮件"},
].map((b,i)=>(
{b.l}
))}
);
};
// ── Right-side properties ──────────────────────────────────
const RuleProperties = ({ rule }) => {
const meta = KIND_META[rule.kind];
const isAlarm = rule.kind === "alarm";
const isOps = rule.kind === "ops";
const channels = [
{ic:"inbox", l:"站内消息中心", on: rule.actions.includes("站内"), who:"业务部门(5) · 调度组(8)"},
{ic:"mail", l:"邮件", on: rule.actions.includes("邮件"), who:"业务负责人 · 安全官"},
{ic:"phone", l:"短信", on: rule.actions.includes("短信"), who:"司机 + 业务负责人"},
{ic:"bell", l:"应用内推送", on: rule.actions.includes("推送"), who:"业务负责人 + 客户联系人"},
{ic:"plug", l:"Webhook", on: rule.actions.includes("Webhook"), who:"erp.lingniu.cn/hook/v1"},
{ic:"clipboard",l:"工单", on: rule.actions.includes("工单"), who:"维保中心 · 24h SLA"},
];
return (
规则属性
事件类型
{meta.label} · {rule.c}
{isAlarm ? "告警优先级" : isOps ? "运维等级" : "事件等级"}
{(isAlarm ? ["P0","P1","P2"] : isOps ? ["M1","M2","M3"] : ["B1","B2","B3"]).map(p => (
{p}
))}
适用车辆
全部车辆 · 512
排除维保中 6 辆 · 排除停运 4 辆
通知 / 动作渠道
{channels.map((c,i)=>(
))}
抑制策略
同车去重窗口{isOps ? "24 小时" : "15 分钟"}
每日上限{isAlarm && rule.c === "P0" ? "无限制" : "20 次/车"}
合并策略{isOps ? "按车辆合并" : "不合并"}
静音时段
{isAlarm && rule.c === "P0" ? "P0 紧急规则不静音" : isAlarm ? "工作时段:8:00–20:00 推送" : isOps ? "仅工作日发送" : "无静音"}
近 7 日触发
{[3,7,2,9,12,5,8].map((v,i) => (
8 ? meta.color : "var(--accent-soft)", borderRadius:1}}/>
))}
4-224-28
);
};
window.ArtboardAlarm = ArtboardAlarm;