// mobile.jsx — Native mobile layouts for each route // Renders a single-column, gesture-friendly version of each page // ── Shared mobile chrome ─────────────────────────────────── const MAppBar = ({ title, subtitle, onMenu, right }) => (
{title}
{subtitle &&
{subtitle}
}
{right}
); const MIconBtn = ({ icon, badge, onClick }) => ( ); const MTabBar = ({ active, onChange }) => { const tabs = [ { id: "overview", icon: "map", label: "总览" }, { id: "history", icon: "history", label: "查询" }, { id: "playback", icon: "route", label: "回放" }, { id: "inbox", icon: "bell", label: "通知" }, { id: "esg", icon: "chart", label: "ESG" }, ]; return (
{tabs.map(t => ( ))}
); }; // ── Mobile shell wrapper ─────────────────────────────────── const MobileShell = ({ title, subtitle, right, children, hideTabBar }) => { const ctx = window.useRoute(); return (
{children}
{!hideTabBar && }
); }; // ── 1. Mobile Overview: hero map + bottom sheet vehicle list ── const MobileOverview = () => { const [selected, setSelected] = React.useState("浙F08638F"); const [sheetOpen, setSheetOpen] = React.useState(false); const [filter, setFilter] = React.useState("all"); const v = (window.VEHICLES || []).find(x => x.id === selected) || {}; const vehicles = window.VEHICLES || []; const counts = { all: vehicles.length, ok: vehicles.filter(x=>x.status==="ok").length, warn: vehicles.filter(x=>x.status==="warn").length, danger: vehicles.filter(x=>x.status==="danger").length }; const filtered = filter === "all" ? vehicles : vehicles.filter(x => x.status === filter); return ( window.useRoute().navigate("inbox")}/>} > {/* Map fills, sheet floats */}
{/* KPI strip floating on map */}
{[ { l: "在线率", v: "95.1%", c: "var(--ok)" }, { l: "告警", v: "8", c: "var(--danger)" }, { l: "今日里程", v: "24.7K km", c: "var(--fg-1)" }, { l: "平均能耗", v: "1.16", c: "var(--info)" }, ].map((k, i) => (
{k.l}
{k.v}
))}
{/* Floating action: locate */} {/* Bottom sheet */}
{/* Drag handle + selected vehicle quick card */}
setSheetOpen(s => !s)} style={{ padding: "8px 16px 6px", cursor: "pointer" }}>
{v.id} {v.deptName}
{v.soc}%
{!sheetOpen ? ( // Mini quick stats when collapsed
速度{v.speed} km/h
续航{Math.round((v.soc||0)*6.2)} km
温度{v.status==="danger"?"102":"68"}°C
) : ( // Full list when expanded <>
{[ {id:"all", label:`全部 ${counts.all}`, c:""}, {id:"ok", label:`行驶 ${counts.ok}`, c:"ok"}, {id:"warn", label:`异常 ${counts.warn}`, c:"warn"}, {id:"danger", label:`故障 ${counts.danger}`, c:"danger"}, ].map(t => ( ))}
{filtered.map(x => (
{ setSelected(x.id); }} style={{ padding: "12px 16px", borderBottom: "1px solid var(--border-1)", display: "flex", alignItems: "center", gap: 12, background: x.id === selected ? "var(--accent-soft)" : "transparent", cursor: "pointer", }}>
{x.id}
{x.deptName} · {x.speed} km/h
{x.soc}%
))}
)}
); }; // ── 2. Mobile Detail ──────────────────────────────────────── const MobileDetail = () => { const vehicles = window.VEHICLES || []; const v = vehicles.find(x => x.id === "浙F03980F") || vehicles[0] || {}; return ( }>
{/* Hero status card */}
{v.asset === "leasing" ? "租赁" : v.asset === "abnormal" ? "异常" : "在库 · 运营中"} {v.own === "self" ? "自有" : "外租"}
业务部门 {v.deptName}
业务负责人{v.deptLead}
客户{v.customer}
所属公司{v.ownCompany}
{v.contractNo &&
合同{v.contractNo}
}
{[ { l: "左前", p: "0.24", t: "32" }, { l: "右前", p: "0.24", t: "33" }, { l: "左后", p: "0.23", t: "31" }, { l: "右后", p: v.asset === "abnormal" ? "0.16" : "0.24", t: "38", warn: v.asset === "abnormal" }, ].map((tire, i) => (
{tire.l}{tire.warn && 低压}
{tire.p} MPa
{tire.t}°C
))}
距下次保养 {v.kmToMaint.toLocaleString()} km
上次保养 {v.lastMaintDays} 天前 · {v.lastMaintKm.toLocaleString()} km
{[ { l: "TBOX (3296/2016)", st: "ok", info: "5s 上报 · 信号 -68dBm" }, { l: "JT808 部标", st: "ok", info: "实时 · 北京·朝阳" }, { l: "JT1078 视频", st: "ok", info: "4 路 · 720P" }, ].map((c, i) => (
{c.l}
{c.info}
))}
); }; const MStat = ({ label, value, unit, color, big }) => (
{label}
{value}{unit}
); const MMini = ({ label, value, sub }) => (
{label}
{value}
{sub &&
{sub}
}
); const MSection = ({ title, children, action }) => (
{title} {action}
{children}
); // ── 3. Mobile History ─────────────────────────────────────── const MobileHistory = () => { const [showFilter, setShowFilter] = React.useState(false); const trips = [ { d: "04-28", t: "14:02–14:44", v: "浙F07179F", k: "32.4 km", h: "0.84 kg", st: "ok" }, { d: "04-28", t: "10:11–11:03", v: "浙F07179F", k: "48.2 km", h: "1.21 kg", st: "ok" }, { d: "04-28", t: "08:30–09:18", v: "浙F08638F", k: "29.8 km", h: "0.76 kg", st: "warn" }, { d: "04-27", t: "17:42–18:25", v: "浙F07179F", k: "36.1 km", h: "0.92 kg", st: "ok" }, { d: "04-27", t: "14:08–15:01", v: "浙F02002F", k: "44.5 km", h: "1.13 kg", st: "danger" }, { d: "04-27", t: "09:22–10:14", v: "浙F07179F", k: "39.7 km", h: "1.01 kg", st: "ok" }, ]; return ( setShowFilter(s=>!s)}/>}>
{/* Search */}
{/* Filter chips - horizontal scroll */}
近 7 日 编组A 全部车型 有告警
{/* KPI summary */}
{[ { l: "总里程", v: "1,847", u: "km", c: "var(--fg-0)" }, { l: "氢耗", v: "47.2", u: "kg", c: "var(--info)" }, { l: "减碳", v: "118", u: "kg", c: "var(--ok)" }, ].map((k, i) => (
{k.l}
{k.v} {k.u}
))}
{/* Trip list */}
{trips.map((t, i) => { const cls = t.st === "danger" ? "danger" : t.st === "warn" ? "warn" : "ok"; return (
{t.v} {t.st === "danger" ? "故障" : t.st === "warn" ? "告警" : "正常"}
{t.d} {t.t}
里程 {t.k}
氢耗 {t.h}
); })}
); }; // ── 4. Mobile Playback ────────────────────────────────────── const MobilePlayback = () => { const [t, setT] = React.useState(38); const [playing, setPlaying] = React.useState(true); const [speed, setSpeed] = React.useState(2); React.useEffect(() => { if (!playing) return; const id = setInterval(() => setT(v => (v + speed * 0.6) % 100), 200); return () => clearInterval(id); }, [playing, speed]); return (
{/* Map area */}
{/* Floating speed badge */}
当前速度
{Math.round(40 + Math.sin(t/8)*20)} km/h
SOC
{Math.round(78 - t * 0.15)}%
{/* Bottom playback panel */}
{/* Time + scrub */}
14:{String(Math.floor(t * 0.42 + 2)).padStart(2,"0")}:{String(Math.floor(t*36)%60).padStart(2,"0")} 已 {Math.round(t*0.42)} 分 / 共 42 分
setT(+e.target.value)} style={{ width: "100%", height: 4, accentColor: "var(--accent)", marginBottom: 12, }}/> {/* Controls row */}
{/* Mini chart timeline events */}
{[12, 38, 65, 88].map((p, i) => (
))}
事件 急刹×1 · 超速×2 · 故障×1
); }; const ctrlBtn = { flex: 1, height: 40, display: "grid", placeItems: "center", background: "var(--bg-2)", border: "1px solid var(--border-1)", color: "var(--fg-1)", borderRadius: 8, fontSize: 12, fontFamily: "var(--font-mono)", cursor: "pointer", }; // ── 5. Mobile Alarm Rules (list-based) ────────────────────── const MobileAlarm = () => { const rules = [ { n: "电池SOC严重不足", st: "on", trig: 12, cond: "SOC < 15% 持续 30s", p: "P0" }, { n: "右后胎压低", st: "on", trig: 8, cond: "压力 < 0.20 MPa", p: "P0" }, { n: "超速预警", st: "on", trig: 47, cond: "速度 > 限速 + 5 km/h", p: "P1" }, { n: "H₂压力异常下降", st: "on", trig: 5, cond: "5min 内下降 > 1.0 MPa", p: "P0" }, { n: "电堆过温保护", st: "on", trig: 3, cond: "温度 > 90°C", p: "P0" }, { n: "急加速密集", st: "on", trig: 31, cond: "5min 内 ≥ 3 次", p: "P2" }, { n: "疲劳驾驶", st: "off", trig: 0, cond: "连续驾驶 > 4h", p: "P1" }, ]; return ( r.st==="on").length} / ${rules.length} 启用`} right={}>
{[ { l: "P0 紧急", v: 4, c: "var(--danger)" }, { l: "P1 警告", v: 2, c: "var(--warn)" }, { l: "P2 提示", v: 1, c: "var(--info)" }, ].map((k, i) => (
{k.l}
{k.v}
))}
{rules.map((r, i) => (
{r.p} {r.n}
{r.cond}
7日触发 {r.trig} 短信 · App推送 · 邮件
))}
); }; const MSwitch = ({ on }) => (
); // ── 6. Mobile Inbox ───────────────────────────────────────── const MobileInbox = () => { const [filter, setFilter] = React.useState("all"); 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"}, ]; const filtered = filter === "all" ? alerts : filter === "new" ? alerts.filter(a=>a.st==="new") : alerts.filter(a=>a.p===filter); return (
{[ {id:"all", l:"全部 24"}, {id:"new", l:"未处理 3"}, {id:"P0", l:"P0 · 2"}, {id:"P1", l:"P1 · 8"}, {id:"P2", l:"P2 · 14"}, ].map(t => ( ))}
{filtered.map((a, i) => { const c = a.p === "P0" ? "var(--danger)" : a.p === "P1" ? "var(--warn)" : "var(--info)"; return (
{a.p} {a.n}
{a.t}
{a.v}
{a.det}
{a.st === "new" && (
)}
); })}
); }; // ── 7. Mobile ESG ──────────────────────────────────────────── const MobileESG = () => { return (
{/* Hero stat */}
本年度累计减碳
1,847.2tCO₂e
较去年同期 ▲ 32.4%
{[ { l: "氢能消耗", v: "47.2", u: "万 m³", c: "var(--info)" }, { l: "里程", v: "1.84", u: "万 km", c: "var(--fg-0)" }, { l: "碳交易收益", v: "18.78", u: "万元", c: "var(--accent)" }, { l: "覆盖城市", v: "23", u: "个", c: "var(--fg-0)" }, ].map((k, i) => (
{k.l}
{k.v}{k.u}
))}
{[180,220,255,235,290,270,310,345,320,355,380,420].map((v, i, arr) => { const x = 12 + i * 26; const h = (v / 420) * 90; return ( {i%3===0 && {i+1}月} ); })}
{[ { p:"浙F·8A03F", v: 24.38 }, { p:"浙F·2C57G", v: 22.15 }, { p:"浙F·9D14B", v: 19.84 }, { p:"浙F·6E72H", v: 17.21 }, { p:"浙F·1B49K", v: 15.67 }, ].map((r, i) => (
#{i+1} {r.p} {r.v} kg
))}
); }; // ── Mobile route map ──────────────────────────────────────── const MOBILE_PAGES = { overview: MobileOverview, detail: MobileDetail, history: MobileHistory, playback: MobilePlayback, alarm: MobileAlarm, inbox: MobileInbox, esg: MobileESG, }; const MobileRouter = ({ route }) => { const Cmp = MOBILE_PAGES[route]; if (!Cmp) { return (
设计画板模式
请在桌面端访问以查看完整设计稿
); } return ; }; window.MobileRouter = MobileRouter; window.MobileShell = MobileShell;