All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 移除 呼和浩特市钢铁工业园区 详情卡,让中国地图完整展示 - 图例(车辆数)改 flex column + 8px gap + 白底卡片,杜绝行重叠 - 车辆实时监控车牌补足新能源后缀(5位 → 6位 + D 后缀) - 当日里程列改用真实 km 数值(不再显示 m³ 氢量)
490 lines
26 KiB
JavaScript
490 lines
26 KiB
JavaScript
// artboard-esg.jsx — ESG · Carbon Reduction Cockpit (light green theme)
|
||
// Mirrors reference: white ground, multi-tier green, China choropleth + KPIs
|
||
|
||
const ChinaMapMini = ({ w = 480, h = 360 }) => {
|
||
// Simplified provincial silhouette — abstract, recognisable. Levels keyed by data.
|
||
// Each path is roughly positioned on a 480x360 canvas of mainland.
|
||
const G = { 4: "#1F8B4C", 3: "#4FB46E", 2: "#9DD3A6", 1: "#D7EBD2", 0: "#EEF5EC" };
|
||
const provs = [
|
||
// [name, level, polygon]
|
||
{n:"新疆", l:1, d:"M40 90 L130 70 L150 110 L120 160 L60 150 Z"},
|
||
{n:"西藏", l:0, d:"M70 160 L150 140 L180 180 L140 220 L80 200 Z"},
|
||
{n:"青海", l:1, d:"M150 130 L210 130 L210 175 L160 180 Z"},
|
||
{n:"甘肃", l:2, d:"M180 100 L240 80 L260 120 L220 140 L200 130 Z"},
|
||
{n:"内蒙", l:3, d:"M180 60 L320 40 L350 70 L330 95 L260 100 L210 90 Z"},
|
||
{n:"宁夏", l:1, d:"M225 110 L245 100 L255 125 L235 130 Z"},
|
||
{n:"陕西", l:3, d:"M245 110 L280 105 L290 160 L260 180 L245 145 Z"},
|
||
{n:"山西", l:2, d:"M285 90 L310 88 L320 145 L295 150 Z"},
|
||
{n:"河北", l:2, d:"M310 75 L355 70 L365 115 L325 130 L315 100 Z"},
|
||
{n:"北京", l:4, d:"M335 82 L355 80 L355 95 L338 95 Z"},
|
||
{n:"天津", l:3, d:"M358 92 L370 92 L370 105 L358 105 Z"},
|
||
{n:"辽宁", l:2, d:"M360 60 L405 55 L420 90 L385 105 L362 88 Z"},
|
||
{n:"吉林", l:1, d:"M395 35 L440 30 L450 65 L410 70 Z"},
|
||
{n:"黑龙江", l:1, d:"M390 5 L460 0 L470 35 L420 40 Z"},
|
||
{n:"山东", l:3, d:"M325 130 L380 125 L390 165 L335 165 Z"},
|
||
{n:"河南", l:3, d:"M280 155 L330 150 L335 195 L290 200 Z"},
|
||
{n:"江苏", l:3, d:"M345 165 L390 165 L395 200 L350 205 Z"},
|
||
{n:"上海", l:4, d:"M390 195 L405 195 L405 210 L390 210 Z"},
|
||
{n:"安徽", l:3, d:"M315 195 L350 200 L355 235 L320 235 Z"},
|
||
{n:"浙江", l:4, d:"M370 210 L400 210 L405 245 L375 245 Z"},
|
||
{n:"湖北", l:3, d:"M270 195 L320 200 L320 235 L275 230 Z"},
|
||
{n:"四川", l:2, d:"M195 175 L260 170 L270 230 L210 225 L195 205 Z"},
|
||
{n:"重庆", l:2, d:"M250 215 L275 210 L275 230 L255 232 Z"},
|
||
{n:"贵州", l:2, d:"M225 235 L275 235 L275 265 L235 265 Z"},
|
||
{n:"云南", l:2, d:"M170 240 L235 235 L240 285 L185 290 L160 270 Z"},
|
||
{n:"湖南", l:3, d:"M275 235 L320 235 L320 270 L280 270 Z"},
|
||
{n:"江西", l:3, d:"M320 235 L360 235 L365 275 L325 275 Z"},
|
||
{n:"福建", l:3, d:"M360 245 L395 245 L395 285 L360 280 Z"},
|
||
{n:"广东", l:4, d:"M270 270 L355 275 L355 305 L280 305 Z"},
|
||
{n:"广西", l:2, d:"M210 270 L275 270 L275 305 L215 305 Z"},
|
||
{n:"海南", l:1, d:"M250 320 L275 320 L275 340 L250 340 Z"},
|
||
{n:"台湾", l:0, d:"M395 270 L410 270 L410 300 L398 300 Z"},
|
||
];
|
||
return (
|
||
<svg viewBox={`0 0 ${w} ${h}`} width="100%" height="100%" style={{display:"block"}}>
|
||
<defs>
|
||
<pattern id="esgGrid" width="20" height="20" patternUnits="userSpaceOnUse">
|
||
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="#E8F1E5" strokeWidth="0.5"/>
|
||
</pattern>
|
||
</defs>
|
||
<rect x="0" y="0" width={w} height={h} fill="url(#esgGrid)"/>
|
||
{/* sea-line decoration */}
|
||
<path d="M 410 60 Q 440 130 430 220 Q 420 280 380 320" fill="none" stroke="#D5EBEB" strokeWidth="1" strokeDasharray="3 3"/>
|
||
{provs.map((p,i) => (
|
||
<g key={i}>
|
||
<path d={p.d} fill={G[p.l]} stroke="#FFFFFF" strokeWidth="1.2"/>
|
||
</g>
|
||
))}
|
||
{/* highlighted city marker — Beijing */}
|
||
<g transform="translate(345 88)">
|
||
<circle r="9" fill="#1F8B4C" opacity="0.18"/>
|
||
<circle r="4" fill="#1F8B4C"/>
|
||
<circle r="2" fill="#FFFFFF"/>
|
||
</g>
|
||
{/* Shanghai */}
|
||
<g transform="translate(398 202)">
|
||
<circle r="3.5" fill="#1F8B4C"/>
|
||
</g>
|
||
{/* Guangzhou */}
|
||
<g transform="translate(310 296)">
|
||
<circle r="3.5" fill="#1F8B4C"/>
|
||
</g>
|
||
{/* Compass / scale */}
|
||
<g transform="translate(20 320)" fontFamily="JetBrains Mono" fontSize="9" fill="#5C7A66">
|
||
<line x1="0" y1="0" x2="40" y2="0" stroke="#5C7A66" strokeWidth="1"/>
|
||
<line x1="0" y1="-3" x2="0" y2="3" stroke="#5C7A66" strokeWidth="1"/>
|
||
<line x1="40" y1="-3" x2="40" y2="3" stroke="#5C7A66" strokeWidth="1"/>
|
||
<text x="20" y="14" textAnchor="middle">800 km</text>
|
||
</g>
|
||
</svg>
|
||
);
|
||
};
|
||
|
||
// Curve helpers
|
||
const ESGSpark = ({ data, w, h, color = "#1F8B4C", fill = true, baseline = 0 }) => {
|
||
const max = Math.max(...data) * 1.1, min = Math.min(...data, 0);
|
||
const range = max - min || 1;
|
||
const pts = data.map((v,i) => `${(i/(data.length-1))*w},${h - ((v-min)/range)*(h-baseline) - baseline}`);
|
||
const d = "M" + pts.join(" L");
|
||
const fillD = d + ` L${w},${h} L0,${h} Z`;
|
||
return (
|
||
<svg width={w} height={h} style={{display:"block"}}>
|
||
{fill && <path d={fillD} fill={color} opacity="0.12"/>}
|
||
<path d={d} fill="none" stroke={color} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
|
||
</svg>
|
||
);
|
||
};
|
||
|
||
const ESGBars = ({ data, w, h, color = "#1F8B4C", labels }) => {
|
||
const max = Math.max(...data) * 1.15;
|
||
const bw = w / data.length * 0.62;
|
||
const gap = w / data.length * 0.38;
|
||
return (
|
||
<svg width={w} height={h} style={{display:"block"}}>
|
||
{/* y gridlines */}
|
||
{[0, 0.25, 0.5, 0.75, 1].map((p,i) => (
|
||
<line key={i} x1="0" y1={h - p*h*0.85 - 14} x2={w} y2={h - p*h*0.85 - 14} stroke="#E8F1E5" strokeWidth="1"/>
|
||
))}
|
||
{data.map((v,i) => {
|
||
const bh = (v / max) * (h * 0.85);
|
||
const x = i * (bw + gap) + gap/2;
|
||
const y = h - bh - 14;
|
||
return (
|
||
<g key={i}>
|
||
<rect x={x} y={y} width={bw} height={bh} fill={color} rx="1"/>
|
||
{labels && <text x={x + bw/2} y={h - 2} textAnchor="middle" fontSize="9" fill="#8FA897" fontFamily="JetBrains Mono">{labels[i]}</text>}
|
||
</g>
|
||
);
|
||
})}
|
||
</svg>
|
||
);
|
||
};
|
||
|
||
const DonutSeg = ({ size = 140, segments, label }) => {
|
||
const r = size/2 - 12, cx = size/2, cy = size/2;
|
||
const total = segments.reduce((a,s) => a + s.v, 0);
|
||
let acc = 0;
|
||
return (
|
||
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
||
<circle cx={cx} cy={cy} r={r} fill="none" stroke="#F0F5EE" strokeWidth="14"/>
|
||
{segments.map((s,i) => {
|
||
const start = (acc / total) * Math.PI * 2 - Math.PI/2;
|
||
acc += s.v;
|
||
const end = (acc / total) * Math.PI * 2 - Math.PI/2;
|
||
const large = (end - start) > Math.PI ? 1 : 0;
|
||
const x1 = cx + r * Math.cos(start), y1 = cy + r * Math.sin(start);
|
||
const x2 = cx + r * Math.cos(end), y2 = cy + r * Math.sin(end);
|
||
return (
|
||
<path key={i} d={`M ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2}`}
|
||
stroke={s.c} strokeWidth="14" fill="none" strokeLinecap="butt"/>
|
||
);
|
||
})}
|
||
<text x={cx} y={cy - 4} textAnchor="middle" fontSize="10" fill="#8FA897">合计</text>
|
||
<text x={cx} y={cy + 14} textAnchor="middle" fontSize="20" fontWeight="600" fill="#1A2A1F" fontFamily="JetBrains Mono">{label}</text>
|
||
</svg>
|
||
);
|
||
};
|
||
|
||
const ArtboardESG = () => {
|
||
// mock data
|
||
const monthlyReduction = [0.95, 1.10, 1.32, 1.55, 1.42, 1.38, 0, 0, 0, 0, 0, 0]; // 吨
|
||
const monthLabels = ["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"];
|
||
const mileageMonthly = [120, 145, 168, 152, 195, 180, 0,0,0,0,0,0];
|
||
const h2Monthly = [180, 220, 255, 235, 290, 270, 0,0,0,0,0,0];
|
||
|
||
const vehicles = [
|
||
{p:"浙F·8A03FD", km:"18,250 km", h2:"287 km", reduction:"24.38 kg", revenue:"18.785 元"},
|
||
{p:"浙F·2C57GD", km:"5,367 km", h2:"83 km", reduction:"7.13 kg", revenue:"181.785 元"},
|
||
{p:"浙F·9D14BD", km:"45,000 km", h2:"152 km", reduction:"12.82 kg", revenue:"194.382 元"},
|
||
{p:"浙F·6E72HD", km:"55,387 km", h2:"214 km", reduction:"17.94 kg", revenue:"152.578 元"},
|
||
{p:"浙F·1B49KD", km:"55,925 km", h2:"212 km", reduction:"17.87 kg", revenue:"148.392 元"},
|
||
{p:"浙F·4F88MD", km:"887,820 km",h2:"114 km", reduction:"9.6 kg", revenue:"73.627 元"},
|
||
{p:"浙F·7G31ND", km:"3,762 km", h2:"165 km", reduction:"13.91 kg", revenue:"66.991 元"},
|
||
{p:"浙F·3H56PD", km:"30,058 km", h2:"165 km", reduction:"13.87 kg", revenue:"82.578 元"},
|
||
{p:"浙F·5J92QD", km:"3,701 km", h2:"101 km", reduction:"8.49 kg", revenue:"103.928 元"},
|
||
{p:"浙F·8K27RD", km:"5,829 km", h2:"186 km", reduction:"15.62 kg", revenue:"76.354 元"},
|
||
{p:"浙F·2L68SD", km:"73,587 km", h2:"58 km", reduction:"4.85 kg", revenue:"54.812 元"},
|
||
{p:"浙F·9M03TD", km:"38,747 km", h2:"137 km", reduction:"11.57 kg", revenue:"72.836 元"},
|
||
];
|
||
|
||
const trades = [
|
||
{ex:"上海环境能源交易所", item:"SHEA", price:"74.28", region:"中国·上海"},
|
||
{ex:"湖北碳排放权交易中心", item:"CCER", price:"39.33", region:"中国·武汉"},
|
||
{ex:"全国碳市场自愿减排", item:"CCER", price:"86.55", region:"全国"},
|
||
{ex:"福建海峡股权交易中心", item:"碳排放配额", price:"25", region:"中国·福州"},
|
||
{ex:"天津排放权交易所", item:"碳排放配额", price:"73.60", region:"中国·天津"},
|
||
{ex:"广东省碳排放权交易所", item:"碳排放配额", price:"82.50", region:"中国·广州"},
|
||
];
|
||
|
||
const fleetMix = [
|
||
{n:"4.5吨冷链车", v:36.2, c:"#1F8B4C"},
|
||
{n:"18吨重卡", v:4.0, c:"#9DD3A6"},
|
||
{n:"49吨牵引车", v:21.7, c:"#4FB46E"},
|
||
{n:"18吨厢式物流车",v:29.5, c:"#76C18B"},
|
||
{n:"4.5吨货车", v:6.6, c:"#C5E2BD"},
|
||
{n:"客车", v:2.1, c:"#E5F1DF"},
|
||
];
|
||
|
||
return (
|
||
<div className="app" data-theme="light" style={{background:"#F2F5EF", colorScheme:"light"}}>
|
||
<Sidebar active="esg"/>
|
||
<div style={{flex:1, display:"flex", flexDirection:"column", minWidth:0, position:"relative", zIndex:1}}>
|
||
{/* Top brand bar */}
|
||
<div style={{
|
||
height:48, flex:"0 0 48px",
|
||
background:"#FFFFFF",
|
||
borderBottom:"1px solid var(--border-1)",
|
||
display:"flex", alignItems:"center", padding:"0 20px", gap:16,
|
||
}}>
|
||
<div className="mid gap-2">
|
||
<img src="assets/logo_light.svg" alt="羚牛氢能 Lingniu" style={{height:32, display:"block"}}/>
|
||
<div style={{fontSize:9, color:"#5C7A66", letterSpacing:"0.12em", fontFamily:"JetBrains Mono", paddingLeft:6, borderLeft:"1px solid #D4E2D5", marginLeft:4}}>
|
||
HYDROGEN<br/>MOBILITY
|
||
</div>
|
||
</div>
|
||
<div style={{flex:1, textAlign:"center", fontWeight:500, color:"#1F8B4C", letterSpacing:"0.06em", fontSize:18, fontFamily:"IBM Plex Sans"}}>
|
||
Lingniu ESG Link
|
||
</div>
|
||
<div className="mono" style={{fontSize:11, color:"#1F8B4C", background:"#DCEFD7", padding:"4px 10px", borderRadius:4, border:"1px solid #B5DDB1"}}>2026-04-28 周二 12:15:13</div>
|
||
<div className="icon-btn" style={{color:"#5C7A66"}}>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c0 .61.36 1.16.91 1.39l.13.05A1.65 1.65 0 0 0 21 13"/></svg>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Body grid */}
|
||
<div className="scroll" style={{flex:1, padding:14, display:"grid", gridTemplateColumns:"380px 1fr 380px", gap:12, gridAutoRows:"min-content"}}>
|
||
|
||
{/* ── LEFT COLUMN ── */}
|
||
<div className="col gap-3" style={{gap:12}}>
|
||
{/* Two top KPIs: emissions & H₂ */}
|
||
<div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:10}}>
|
||
<div className="panel" style={{padding:14}}>
|
||
<div className="between">
|
||
<div>
|
||
<div style={{fontSize:11, color:"#5C7A66"}}>当日减碳量</div>
|
||
<div className="mono" style={{fontSize:22, fontWeight:600, color:"#1F8B4C", marginTop:4}}>29486.78<span style={{fontSize:11, marginLeft:3, color:"#5C7A66", fontWeight:400}}>kg</span></div>
|
||
</div>
|
||
<svg width="34" height="34" viewBox="0 0 40 40" fill="none">
|
||
<circle cx="20" cy="20" r="18" fill="#DCEFD7"/>
|
||
<path d="M20 8 c-4 4 -8 8 -8 14 a8 8 0 0 0 16 0 c0 -6 -4 -10 -8 -14z" fill="#1F8B4C"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div className="panel" style={{padding:14}}>
|
||
<div className="between">
|
||
<div>
|
||
<div style={{fontSize:11, color:"#5C7A66"}}>当日H₂用量</div>
|
||
<div className="mono" style={{fontSize:22, fontWeight:600, color:"#1F8B4C", marginTop:4}}>974.7<span style={{fontSize:11, marginLeft:3, color:"#5C7A66", fontWeight:400}}>kg</span></div>
|
||
</div>
|
||
<svg width="34" height="34" viewBox="0 0 40 40" fill="none">
|
||
<circle cx="20" cy="20" r="18" fill="#D5EBEB"/>
|
||
<circle cx="14" cy="22" r="3" fill="#2E8C8C"/>
|
||
<circle cx="22" cy="16" r="2.4" fill="#2E8C8C"/>
|
||
<circle cx="26" cy="24" r="3.6" fill="#2E8C8C"/>
|
||
<circle cx="18" cy="14" r="2" fill="#2E8C8C"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Annual cumulative reduction — hero card */}
|
||
<div className="panel" style={{
|
||
padding:16, position:"relative", overflow:"hidden",
|
||
background:"linear-gradient(135deg, #DCEFD7 0%, #FFFFFF 70%)",
|
||
border:"1px solid #B5DDB1",
|
||
}}>
|
||
<div style={{fontSize:11, color:"#5C7A66"}}>今年累计减碳</div>
|
||
<div style={{display:"flex", alignItems:"baseline", gap:6, marginTop:6}}>
|
||
<span className="mono" style={{fontSize:34, fontWeight:600, color:"#1F8B4C", letterSpacing:"-0.02em"}}>4567.14</span>
|
||
<span style={{fontSize:13, color:"#5C7A66"}}>吨</span>
|
||
</div>
|
||
<div style={{fontSize:10, color:"#5C7A66", marginTop:4, display:"flex", alignItems:"center", gap:6}}>
|
||
<span style={{display:"inline-block", width:14, height:1, background:"#1F8B4C"}}/>
|
||
相当于种植 18.5 万棵树
|
||
</div>
|
||
{/* abstract tree silhouette */}
|
||
<svg width="100%" height="50" viewBox="0 0 280 50" style={{marginTop:10, opacity:0.55}}>
|
||
{[...Array(28)].map((_,i) => {
|
||
const x = i*10 + 4;
|
||
const heights = [22, 30, 26, 34, 28, 32, 24, 36, 30, 28, 32, 26, 34, 30];
|
||
const h = heights[i % heights.length];
|
||
return (
|
||
<g key={i} transform={`translate(${x} ${50-h})`}>
|
||
<path d={`M 4 ${h} L 0 ${h-h*0.7} L 3 ${h-h*0.7} L -1 ${h-h*0.85} L 2 ${h-h*0.85} L 0 ${h} z M 4 ${h} L 8 ${h-h*0.7} L 5 ${h-h*0.7} L 9 ${h-h*0.85} L 6 ${h-h*0.85} L 8 ${h} z`} fill="#1F8B4C" opacity={0.4 + (i%3)*0.18}/>
|
||
<rect x="3.5" y={h-2} width="1" height="2" fill="#5C7A66"/>
|
||
</g>
|
||
);
|
||
})}
|
||
</svg>
|
||
</div>
|
||
|
||
{/* Monthly reduction bars */}
|
||
<div className="panel" style={{padding:14}}>
|
||
<div className="between" style={{marginBottom:10}}>
|
||
<span style={{fontWeight:600, fontSize:13, color:"#1A2A1F"}}>月度碳减排</span>
|
||
<span className="chip" style={{fontSize:10}}>单位 · 吨</span>
|
||
</div>
|
||
<ESGBars data={monthlyReduction} w={350} h={150} labels={monthLabels}/>
|
||
</div>
|
||
|
||
{/* Monthly mileage / H2 */}
|
||
<div className="panel" style={{padding:14}}>
|
||
<div className="between" style={{marginBottom:10}}>
|
||
<span style={{fontWeight:600, fontSize:13, color:"#1A2A1F"}}>月度行驶里程 & 用氢量</span>
|
||
</div>
|
||
<div className="mid gap-3" style={{fontSize:10, color:"#5C7A66", marginBottom:8}}>
|
||
<span className="mid gap-1"><span className="dot" style={{background:"#1F8B4C", width:8, height:8, borderRadius:1}}/>用氢量</span>
|
||
<span className="mid gap-1"><span className="dot" style={{background:"#9DD3A6", width:8, height:8, borderRadius:1}}/>行驶里程</span>
|
||
<span style={{marginLeft:"auto", fontFamily:"JetBrains Mono"}}>kg / km</span>
|
||
</div>
|
||
<svg width="350" height="135" viewBox="0 0 350 135" style={{display:"block"}}>
|
||
{[0, 0.25, 0.5, 0.75, 1].map((p,i) => (
|
||
<line key={i} x1="0" y1={120 - p*100} x2="350" y2={120 - p*100} stroke="#E8F1E5" strokeWidth="1"/>
|
||
))}
|
||
{[0, 0.25, 0.5, 0.75, 1].map((p,i) => (
|
||
<text key={i} x="0" y={124 - p*100} fontSize="9" fill="#8FA897" fontFamily="JetBrains Mono">{Math.round(p*400)}</text>
|
||
))}
|
||
{/* Curves */}
|
||
{(() => {
|
||
const m = (arr, max) => arr.map((v,i)=>`${(i/(arr.length-1))*350},${120 - (v/max)*100}`);
|
||
const p1 = "M" + m(h2Monthly, 400).join(" L");
|
||
const p2 = "M" + m(mileageMonthly, 250).join(" L");
|
||
return (
|
||
<>
|
||
<path d={p1 + " L350,120 L0,120 z"} fill="#1F8B4C" opacity="0.13"/>
|
||
<path d={p1} fill="none" stroke="#1F8B4C" strokeWidth="2"/>
|
||
<path d={p2} fill="none" stroke="#9DD3A6" strokeWidth="2"/>
|
||
</>
|
||
);
|
||
})()}
|
||
{monthLabels.map((l,i) => (
|
||
<text key={i} x={(i/(monthLabels.length-1))*350} y="132" textAnchor="middle" fontSize="8" fill="#8FA897" fontFamily="JetBrains Mono">{l}</text>
|
||
))}
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── CENTER COLUMN ── */}
|
||
<div className="col" style={{gap:12}}>
|
||
{/* Map panel */}
|
||
<div className="panel" style={{padding:14, paddingBottom:18}}>
|
||
<div className="between">
|
||
<div className="mid gap-2">
|
||
<span style={{fontWeight:600, fontSize:14, color:"#1A2A1F"}}>羚牛全国车辆信息</span>
|
||
<span className="chip accent" style={{fontSize:10}}>加氢站</span>
|
||
</div>
|
||
<span className="chip" style={{fontSize:10, color:"#1F8B4C", borderColor:"#B5DDB1", background:"#DCEFD7"}}>实时反馈</span>
|
||
</div>
|
||
|
||
<div style={{position:"relative", marginTop:10, height:380}}>
|
||
<ChinaMapMini w={580} h={380}/>
|
||
{/* Legend */}
|
||
<div style={{
|
||
position:"absolute", bottom:16, left:16,
|
||
fontSize:11, color:"#5C7A66", lineHeight:1,
|
||
background:"rgba(255,255,255,0.92)",
|
||
padding:"10px 12px", borderRadius:6,
|
||
border:"1px solid #D9E8DA",
|
||
boxShadow:"0 1px 4px rgba(31,80,46,.06)",
|
||
display:"flex", flexDirection:"column", gap:8,
|
||
}}>
|
||
<div style={{fontWeight:600, color:"#2E4234", fontSize:11}}>车辆数</div>
|
||
{[
|
||
{l:"≥ 300 辆", c:"#1F8B4C"},
|
||
{l:"100–300 辆", c:"#4FB46E"},
|
||
{l:"50–100 辆", c:"#9DD3A6"},
|
||
{l:"< 50 辆", c:"#D7EBD2"},
|
||
].map((x,i) => (
|
||
<div key={i} style={{display:"flex", alignItems:"center", gap:8, lineHeight:1}}>
|
||
<span style={{display:"inline-block", width:16, height:12, background:x.c, border:"1px solid #FFFFFF", borderRadius:2, flex:"0 0 16px"}}/>
|
||
<span style={{fontFamily:"JetBrains Mono"}}>{x.l}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Carbon trades table */}
|
||
<div className="panel" style={{padding:0}}>
|
||
<div className="between" style={{padding:"12px 14px", borderBottom:"1px solid var(--border-1)"}}>
|
||
<span style={{fontWeight:600, fontSize:13, color:"#1A2A1F"}}>碳交易行情</span>
|
||
<span className="chip" style={{fontSize:10}}>实时报价</span>
|
||
</div>
|
||
<table className="tbl" style={{fontSize:11}}>
|
||
<thead>
|
||
<tr><th>交易所</th><th>项目</th><th>价格 (RMB)</th><th>地区</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
{trades.map((t,i) => (
|
||
<tr key={i}>
|
||
<td style={{color:"#1A2A1F"}}>{t.ex}</td>
|
||
<td className="mono" style={{color:"#1F8B4C", fontWeight:600}}>{t.item}</td>
|
||
<td className="mono" style={{color:"#1A2A1F", fontWeight:600}}>{t.price}</td>
|
||
<td style={{color:"#5C7A66"}}>{t.region}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── RIGHT COLUMN ── */}
|
||
<div className="col" style={{gap:12}}>
|
||
{/* Two top KPIs: vehicle total & cumulative mileage */}
|
||
<div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:10}}>
|
||
<div className="panel" style={{padding:14}}>
|
||
<div className="between">
|
||
<div>
|
||
<div style={{fontSize:11, color:"#5C7A66"}}>车辆总数</div>
|
||
<div className="mono" style={{fontSize:22, fontWeight:600, color:"#1F8B4C", marginTop:4}}>1006<span style={{fontSize:11, marginLeft:3, color:"#5C7A66", fontWeight:400}}>辆</span></div>
|
||
</div>
|
||
<svg width="34" height="34" viewBox="0 0 40 40" fill="none">
|
||
<circle cx="20" cy="20" r="18" fill="#DCEFD7"/>
|
||
<rect x="11" y="17" width="18" height="10" rx="2" fill="#1F8B4C"/>
|
||
<circle cx="14" cy="29" r="2" fill="#1F8B4C"/>
|
||
<circle cx="26" cy="29" r="2" fill="#1F8B4C"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div className="panel" style={{padding:14}}>
|
||
<div className="between">
|
||
<div>
|
||
<div style={{fontSize:11, color:"#5C7A66"}}>当日行驶里程</div>
|
||
<div className="mono" style={{fontSize:22, fontWeight:600, color:"#1F8B4C", marginTop:4}}>64508.42<span style={{fontSize:11, marginLeft:3, color:"#5C7A66", fontWeight:400}}>km</span></div>
|
||
</div>
|
||
<svg width="34" height="34" viewBox="0 0 40 40" fill="none">
|
||
<circle cx="20" cy="20" r="18" fill="#D5EBEB"/>
|
||
<path d="M10 22 a10 10 0 0 1 20 0" fill="none" stroke="#2E8C8C" strokeWidth="2"/>
|
||
<path d="M20 22 L25 14" stroke="#2E8C8C" strokeWidth="2" strokeLinecap="round"/>
|
||
<circle cx="20" cy="22" r="1.5" fill="#2E8C8C"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Vehicle live monitor table */}
|
||
<div className="panel" style={{padding:0, flex:1, minHeight:380, display:"flex", flexDirection:"column"}}>
|
||
<div className="between" style={{padding:"12px 14px", borderBottom:"1px solid var(--border-1)"}}>
|
||
<span style={{fontWeight:600, fontSize:13, color:"#1A2A1F"}}>车辆实时监控</span>
|
||
<span className="chip ok" style={{fontSize:10}}>· LIVE</span>
|
||
</div>
|
||
<div className="scroll" style={{flex:1}}>
|
||
<table className="tbl" style={{fontSize:11}}>
|
||
<thead>
|
||
<tr>
|
||
<th>车牌号</th><th>总里程</th><th>当日里程</th><th>当日减碳</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{vehicles.map((v,i) => (
|
||
<tr key={i}>
|
||
<td className="mono" style={{color:"#1A2A1F", fontWeight:600}}>{v.p}</td>
|
||
<td className="mono" style={{color:"#5C7A66"}}>{v.km}</td>
|
||
<td className="mono" style={{color:"#5C7A66"}}>{v.h2}</td>
|
||
<td className="mono" style={{color:"#1F8B4C", fontWeight:600}}>{v.reduction}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Fleet mix donut */}
|
||
<div className="panel" style={{padding:14}}>
|
||
<div className="between" style={{marginBottom:10}}>
|
||
<span style={{fontWeight:600, fontSize:13, color:"#1A2A1F"}}>车型结构分析</span>
|
||
</div>
|
||
<div className="mid gap-3" style={{alignItems:"center"}}>
|
||
<DonutSeg size={130} segments={fleetMix} label="1006"/>
|
||
<div style={{flex:1, display:"flex", flexDirection:"column", gap:5, fontSize:11}}>
|
||
{fleetMix.map((f,i) => (
|
||
<div key={i} className="between">
|
||
<span className="mid gap-2">
|
||
<span style={{width:10, height:10, borderRadius:2, background:f.c}}/>
|
||
<span style={{color:"#5C7A66"}}>{f.n}</span>
|
||
</span>
|
||
<span className="mono" style={{color:"#1A2A1F", fontWeight:600}}>{f.v}%</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Footer */}
|
||
<div style={{
|
||
height:24, flex:"0 0 24px",
|
||
borderTop:"1px solid var(--border-1)", background:"#FFFFFF",
|
||
display:"flex", alignItems:"center", justifyContent:"center", gap:14,
|
||
fontSize:10, color:"#8FA897",
|
||
}}>
|
||
<span>© 2026 羚牛氢能 · Lingniu Hydrogen Mobility · All Rights Reserved</span>
|
||
<span style={{color:"#1F8B4C"}}>· API 接口处理 · Build v4.2.0-stable</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
window.ArtboardESG = ArtboardESG;
|