// 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:"电池SOC严重不足", kind:"alarm", c:"P0", on:true, h:"已触发 8 次", cond:"SOC < 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.length} 共 {RULES.length} 条
{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("电池SOC")) return [{lbl:"WHEN", v:"vehicle.battery.soc", 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)=>(
{c.l}
{c.who}
))}
抑制策略
同车去重窗口{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;