All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
- React 18 + Babel-in-browser SPA 原型,覆盖 8 个画板: 实时地图 / 车辆详情 / 历史查询 / 轨迹回放 / 事件规则 / 通知中心 / ESG 碳减排 / 移动端 - 设计系统:IBM Plex Sans + JetBrains Mono,亮/暗双主题,羚牛绿 #007143 - 数据模型:12 + 40 辆车,TBOX (T) / JT808+1078 (JT) / 双源 (B) - 部署:nginx 静态托管,Dockerfile + woodpecker.yml + docker-compose.yml - 镜像:harbor.lnh2e.com/lingniu-v1/ln-vdc:<branch>-<VERSION> - 容器端口 80,宿主映射 8112,含 /healthz 探活
153 lines
8.8 KiB
JavaScript
153 lines
8.8 KiB
JavaScript
// artboard-inbox.jsx — Notification center
|
|
const ArtboardInbox = () => {
|
|
const alerts = [
|
|
{p:"P0", n:"电池SOC严重不足", v:"浙F08638F", t:"刚刚", det:"SOC 9% < 阈值 15% · 持续 4分20秒", st:"new"},
|
|
{p:"P0", n:"右后胎压低", v:"浙F08638F", t:"3分钟前", det:"0.16 MPa · 阈值 0.20 MPa", st:"new"},
|
|
{p:"P1", n:"超速预警", v:"浙F02002F", t:"12分钟前", det:"实测 89 km/h · 限速 80 km/h · 持续 12s", st:"new"},
|
|
{p:"P1", n:"H₂压力异常下降", v:"浙F07179F", t:"32分钟前", det:"5分钟内下降 1.2 MPa · 异常", st:"ack"},
|
|
{p:"P0", n:"电堆过温保护", v:"浙F00598F", t:"1小时前", det:"电堆温度 95°C · 阈值 90°C", st:"resolved"},
|
|
{p:"P2", n:"急加速密集", v:"浙F02608F", t:"2小时前", det:"5分钟内 3 次急加速", st:"resolved"},
|
|
{p:"P1", n:"偏离规划路线", v:"浙F00278F", t:"3小时前", det:"偏离 1.2 km · 持续 6 分钟", st:"resolved"},
|
|
];
|
|
return (
|
|
<div className="app">
|
|
<Sidebar active="inbox"/>
|
|
<div style={{flex:1, display:"flex", flexDirection:"column", minWidth:0, position:"relative", zIndex:1}}>
|
|
<Topbar
|
|
crumbs={["通知中心", "告警时间线"]}
|
|
kpis={[
|
|
{lbl:"未处理", val:"3"},
|
|
{lbl:"今日", val:"24"},
|
|
{lbl:"本周", val:"187"},
|
|
]}
|
|
showSearch={false}
|
|
/>
|
|
<div style={{flex:1, display:"grid", gridTemplateColumns:"1fr 320px", minHeight:0}}>
|
|
<div style={{display:"flex", flexDirection:"column", minHeight:0}}>
|
|
{/* Filter chips */}
|
|
<div style={{padding:"10px 16px", borderBottom:"1px solid var(--border-1)", display:"flex", gap:8, alignItems:"center", background:"var(--bg-1)"}}>
|
|
<span className="chip accent">全部 · 24</span>
|
|
<span className="chip">未处理 · 3</span>
|
|
<span className="chip">P0 · 2</span>
|
|
<span className="chip">P1 · 8</span>
|
|
<span className="chip">P2 · 14</span>
|
|
<span style={{width:1, height:20, background:"var(--border-1)"}}/>
|
|
<span className="muted" style={{fontSize:11}}>今日</span>
|
|
<div style={{marginLeft:"auto", display:"flex", gap:6}}>
|
|
<button className="btn sm">全部已读</button>
|
|
<button className="btn sm"><Icon name="filter" size={11}/> 筛选</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Hourly distribution */}
|
|
<div style={{padding:"12px 16px", borderBottom:"1px solid var(--border-1)", background:"var(--bg-1)"}}>
|
|
<div className="between" style={{marginBottom:6}}>
|
|
<span className="eyebrow">24小时告警分布</span>
|
|
<span className="muted mono" style={{fontSize:10}}>峰值 14:00-15:00</span>
|
|
</div>
|
|
<Bars data={[1,0,0,1,0,2,3,5,8,4,3,4,7,12,18,9,7,5,4,3,2,1,1,0]} w={1100} h={48} color="var(--accent)"/>
|
|
</div>
|
|
|
|
{/* Timeline list */}
|
|
<div className="scroll" style={{flex:1}}>
|
|
{alerts.map((a,i)=>{
|
|
const c = a.p === "P0" ? "var(--danger)" : a.p === "P1" ? "var(--warn)" : "var(--info)";
|
|
return (
|
|
<div key={i} style={{display:"flex", gap:14, padding:"14px 20px", borderBottom:"1px solid var(--border-1)", background: a.st==="new" ? "oklch(0.20 0.020 245)":"transparent", cursor:"pointer"}}>
|
|
<div style={{width:40, paddingTop:4, position:"relative"}}>
|
|
<div style={{width:32, height:32, borderRadius:16, background: a.st==="new"?c:"var(--bg-3)", opacity: a.st==="resolved" ? 0.4 : 1, display:"grid", placeItems:"center", color:"var(--fg-0)", boxShadow: a.st==="new" ? `0 0 16px ${c}` : "none"}}>
|
|
<Icon name="bell" size={14}/>
|
|
</div>
|
|
{i < alerts.length-1 && <span style={{position:"absolute", left:19, top:42, bottom:-14, width:2, background:"var(--border-1)"}}/>}
|
|
</div>
|
|
<div style={{flex:1, minWidth:0}}>
|
|
<div className="between">
|
|
<div className="mid gap-2">
|
|
<span className={"chip " + (a.p==="P0"?"danger":a.p==="P1"?"warn":"info")}>{a.p}</span>
|
|
<span className="strong" style={{fontSize:13}}>{a.n}</span>
|
|
<span className="mono muted" style={{fontSize:11}}>· {a.v}</span>
|
|
{a.st==="new" && <span style={{width:6, height:6, borderRadius:3, background:c, boxShadow: `0 0 6px ${c}`}}/>}
|
|
{a.st==="ack" && <span className="chip" style={{fontSize:9}}>已确认</span>}
|
|
{a.st==="resolved" && <span className="chip ok" style={{fontSize:9}}>已恢复</span>}
|
|
</div>
|
|
<span className="mono muted" style={{fontSize:11}}>{a.t}</span>
|
|
</div>
|
|
<div className="muted" style={{fontSize:12, marginTop:4}}>{a.det}</div>
|
|
{a.st === "new" && (
|
|
<div className="mid gap-2" style={{marginTop:10}}>
|
|
<button className="btn sm primary"><Icon name="route" size={11}/> 查看轨迹</button>
|
|
<button className="btn sm">确认</button>
|
|
<button className="btn sm">分配</button>
|
|
<button className="btn sm ghost">忽略</button>
|
|
<span className="muted mono" style={{fontSize:10, marginLeft:"auto"}}>规则 · {a.n}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right: stats */}
|
|
<div style={{borderLeft:"1px solid var(--border-1)", background:"var(--bg-1)", display:"flex", flexDirection:"column", minHeight:0}}>
|
|
<div className="panel-head" style={{borderBottom:"1px solid var(--border-1)"}}>
|
|
<Icon name="chart" size={13}/><span className="title">告警概览</span>
|
|
</div>
|
|
<div className="scroll" style={{flex:1, padding:14}}>
|
|
<div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:8, marginBottom:14}}>
|
|
{[
|
|
{l:"P0 紧急", v:"2", c:"var(--danger)"},
|
|
{l:"P1 警告", v:"8", c:"var(--warn)"},
|
|
{l:"P2 提示", v:"14", c:"var(--info)"},
|
|
{l:"已恢复", v:"19", c:"var(--ok)"},
|
|
].map((k,i)=>(
|
|
<div key={i} style={{padding:10, background:"var(--bg-2)", borderRadius:5, border:"1px solid var(--border-1)"}}>
|
|
<div className="muted" style={{fontSize:10}}>{k.l}</div>
|
|
<div className="mono" style={{fontSize:22, fontWeight:600, color:k.c}}>{k.v}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="eyebrow" style={{marginBottom:8}}>Top 5 告警类型 · 7日</div>
|
|
<div className="col gap-2" style={{fontSize:11, marginBottom:14}}>
|
|
{[
|
|
{l:"超速预警", v:47, c:"var(--warn)"},
|
|
{l:"急加速密集", v:31, c:"var(--info)"},
|
|
{l:"胎压报警", v:18, c:"var(--warn)"},
|
|
{l:"SOC不足", v:12, c:"var(--danger)"},
|
|
{l:"H₂压力异常", v:8, c:"var(--danger)"},
|
|
].map((t,i)=>(
|
|
<div key={i} className="mid gap-2">
|
|
<span style={{width:80, fontSize:11}} className="muted">{t.l}</span>
|
|
<div className="bar" style={{flex:1, height:6}}><i style={{width: (t.v/47*100)+"%", background: t.c}}/></div>
|
|
<span className="mono" style={{width:24, textAlign:"right"}}>{t.v}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="eyebrow" style={{marginBottom:8}}>Top 5 告警车辆</div>
|
|
<div className="col gap-2" style={{fontSize:11}}>
|
|
{[
|
|
{n:"浙F08638F", v:8, c:"var(--danger)"},
|
|
{n:"浙F02002F", v:6, c:"var(--warn)"},
|
|
{n:"浙F02608F", v:4, c:"var(--warn)"},
|
|
{n:"浙F00598F", v:3, c:"var(--info)"},
|
|
{n:"浙F00278F", v:3, c:"var(--info)"},
|
|
].map((t,i)=>(
|
|
<div key={i} className="between" style={{padding:"6px 10px", background:"var(--bg-2)", borderRadius:4, border:"1px solid var(--border-1)"}}>
|
|
<span className="mono strong">{t.n}</span>
|
|
<span className={"mono"} style={{color:t.c}}>{t.v} 次</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
window.ArtboardInbox = ArtboardInbox;
|