init: 羚牛车辆数据中心原型 + 部署配置
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 探活
This commit is contained in:
kkfluous
2026-04-28 15:12:32 +08:00
commit b2d0016a0d
59 changed files with 6938 additions and 0 deletions

132
artboards/variant-dense.jsx Normal file
View File

@@ -0,0 +1,132 @@
// artboard-dense.jsx — dense info variation: 4-column with mini-map
const ArtboardDense = () => {
return (
<div className="app">
<Sidebar active="map"/>
<div style={{flex:1, display:"flex", flexDirection:"column", minWidth:0, position:"relative", zIndex:1}}>
<Topbar
crumbs={["羚牛车辆数据中心", "运营驾驶舱"]}
kpis={[
{ lbl:"在线", val:"487", delta:"95%", deltaUp:true },
{ lbl:"行驶", val:"312" },
{ lbl:"告警P0", val:"2" },
{ lbl:"今日里程", val:"24,781 km" },
{ lbl:"H₂消耗", val:"482 kg" },
{ lbl:"评分", val:"86.4" },
]}
/>
<div style={{flex:1, display:"grid", gridTemplateColumns:"1fr 1fr 1fr 1fr", gridTemplateRows:"auto auto 1fr", gap:8, padding:8, minHeight:0}}>
{/* KPI cards row */}
{[
{l:"车辆健康度", v:"94.2", u:"%", c:"var(--accent)", d:[88,90,89,92,91,94,94]},
{l:"平均能耗", v:"18.4", u:"kWh/100km", c:"var(--info)", d:[19,18,18.5,18.2,18.4,18.4,18.4]},
{l:"H₂利用率", v:"83.1", u:"%", c:"var(--ok)", d:[80,81,82,82,83,83,83.1]},
{l:"安全评分", v:"86.4", u:"/100", c:"var(--warn)", d:[85,86,84,87,86,86,86.4]},
].map((k,i)=>(
<div key={i} className="panel" style={{padding:12}}>
<div className="between">
<span className="eyebrow">{k.l}</span>
<span className="chip ok" style={{fontSize:9}}></span>
</div>
<div style={{marginTop:6}}>
<span className="mono strong" style={{fontSize:24, fontWeight:600}}>{k.v}</span>
<span className="muted mono" style={{fontSize:10, marginLeft:3}}>{k.u}</span>
</div>
<div style={{marginTop:4}}>
<LineChart data={k.d} w={220} h={32} color={k.c}/>
</div>
</div>
))}
{/* Map spans 2 cols 2 rows */}
<div className="panel" style={{gridColumn:"1 / span 2", gridRow:"2 / span 2", overflow:"hidden", position:"relative"}}>
<div className="panel-head" style={{position:"absolute", top:0, left:0, right:0, zIndex:2, background:"oklch(0.18 0.020 245 / 0.85)", backdropFilter:"blur(8px)"}}>
<Icon name="map" size={13}/><span className="title">实时分布</span>
<div className="actions">
<span className="chip">热力</span><span className="chip accent">车辆</span>
</div>
</div>
<div style={{height:"100%"}}>
<FleetMap selectedId="浙F07179F" onSelect={()=>{}} showHeatmap/>
</div>
</div>
{/* Status donut */}
<div className="panel">
<div className="panel-head"><span className="title">车辆状态</span></div>
<div style={{padding:10, display:"flex", gap:14, alignItems:"center"}}>
<Donut size={90} value={0.61} color="var(--ok)" thick={10} label="312"/>
<div className="col gap-2" style={{flex:1, fontSize:11}}>
<div className="between"><span className="mid gap-1"><span className="dot ok"/> 行驶</span><span className="mono strong">312</span></div>
<div className="between"><span className="mid gap-1"><span className="dot warn"/> 待命</span><span className="mono strong">155</span></div>
<div className="between"><span className="mid gap-1"><span className="dot danger"/> 故障</span><span className="mono strong">8</span></div>
<div className="between"><span className="mid gap-1"><span className="dot idle"/> 离线</span><span className="mono strong">25</span></div>
<div className="between"><span className="muted">维保</span><span className="mono strong">12</span></div>
</div>
</div>
</div>
{/* Alerts feed */}
<div className="panel">
<div className="panel-head"><Icon name="bell" size={13}/><span className="title">实时告警</span><span className="chip danger" style={{marginLeft:"auto"}}>3 NEW</span></div>
<div style={{padding:8, display:"flex", flexDirection:"column", gap:6, overflow:"auto", maxHeight:240}}>
{[
{p:"P0", n:"SOC严重不足", v:"浙F08638F", t:"刚刚"},
{p:"P0", n:"右后胎压低", v:"浙F08638F", t:"3m"},
{p:"P1", n:"超速预警", v:"浙F02002F", t:"12m"},
{p:"P1", n:"H₂下降", v:"浙F07179F", t:"32m"},
{p:"P2", n:"急加速", v:"浙F02608F", t:"1h"},
].map((a,i)=>(
<div key={i} className="between" style={{padding:"6px 8px", background:"var(--bg-2)", borderRadius:4, border:"1px solid var(--border-1)", fontSize:11}}>
<div className="mid gap-2">
<span className={"chip " + (a.p==="P0"?"danger":a.p==="P1"?"warn":"")}>{a.p}</span>
<span className="strong">{a.n}</span>
<span className="mono muted">· {a.v}</span>
</div>
<span className="mono muted" style={{fontSize:10}}>{a.t}</span>
</div>
))}
</div>
</div>
{/* Energy chart */}
<div className="panel">
<div className="panel-head"><Icon name="bolt" size={13}/><span className="title">能耗 · 24h</span></div>
<div style={{padding:12}}>
<Bars data={[12,8,6,5,4,4,9,14,22,28,26,24,22,28,30,28,24,20,18,16,14,12,10,8]} w={240} h={80} color="var(--info)"/>
<div className="between" style={{marginTop:8, fontSize:11}}>
<span className="muted">总计</span>
<span className="mono strong">4,562 kWh</span>
</div>
</div>
</div>
{/* H2 stations */}
<div className="panel">
<div className="panel-head"><Icon name="h2" size={13}/><span className="title">补能站</span></div>
<div style={{padding:8, fontSize:11}}>
{[
{n:"#04 朝阳", v:0.78, k:"4号站 · 78%"},
{n:"#02 海淀", v:0.42, k:"2号站 · 42%"},
{n:"#07 丰台", v:0.91, k:"7号站 · 91%"},
{n:"#11 通州", v:0.25, k:"11号站 · 25%"},
].map((s,i)=>(
<div key={i} style={{marginBottom:8}}>
<div className="between" style={{marginBottom:3}}>
<span className="mono">{s.k}</span>
<span className="mono strong">{Math.round(s.v*100)}%</span>
</div>
<div className="bar" style={{height:5}}>
<i style={{width: s.v*100+"%", background: s.v < 0.3 ? "var(--danger)" : s.v < 0.5 ? "var(--warn)" : "var(--accent)"}}/>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
window.ArtboardDense = ArtboardDense;