// data/fleet.js — 羚牛 Hydrogen Fleet · Asset-management dataset // Switched from driver-centric to asset-centric model per business spec. // Vehicles relate to: 业务部门 / 业务负责人 / 客户 / 所属公司 / 租赁公司 // Driver field intentionally removed. (function(){ // ── Companies & departments ────────────────────────────── const COMPANIES = { own: ["浙江羚牛氢能科技有限公司", "嘉兴羚牛新能源运营公司"], lease: ["JXLN-23 浙F氢能", "JXGW-G 浙F氢能", "LNZLHT 嘉兴港区"], }; const DEPARTMENTS = [ { id: "biz1", name: "业务一部", lead: "高伟", color: "#1F8B4C" }, { id: "biz2", name: "业务二部", lead: "陈高伟", color: "#2E8C8C" }, { id: "biz3", name: "业务三部", lead: "尚建华", color: "#7A8C2E" }, { id: "biz4", name: "业务四部", lead: "刘念念", color: "#C97A3D" }, { id: "ops", name: "运营部", lead: "张兰", color: "#5C6E7C" }, ]; const CUSTOMERS = [ "嘉兴公司自营", "嘉兴氢能业务一部", "欧宝软件", "汇通运营公司", "嘉兴港区二部", "嘉兴县嘉一部", "午潮停车场", "—", ]; const PARKINGS = [ "平湖停车场", "平湖停车场异常", "嘉兴公司自营", "欧宝停车场", "汇通停车场", "嘉兴港区停车场", "午潮码头停车场", "云洋停车场", ]; const CITIES = [ "浙江省·嘉兴市·平湖", "浙江省·嘉兴市", "浙江省·嘉兴市·港区", "浙江省·嘉兴市·南湖", "广东省·云浮市", "浙江省·杭州市·萧山", ]; // VIN/车架号 prefix patterns from screenshot: LA9HE6.. / LA9G6.. / LJRC1.. const VIN_PREFIXES = ["LA9HE6F2N1A", "LA9G6E7L4N1B", "LJRC14A22NA0", "LA9HE6F8N1C"]; // ── Asset/operation status enums ────────────────────────── // asset: in_stock(在库) | leasing(租赁中) | abnormal(异常) // own: self(自有) | lease(外租) // op: operating(运营中) | suspended(停运) | maintenance(待整备) // gps: online(在线) | offline(离线) // grade: A | B | C // status (legacy): ok | warn | danger | idle (kept for map color coding) // ── Helper: deterministic pseudo-random ─────────────────── const seed = (n) => { let x = (n*9301+49297)%233280; return () => (x = (x*9301+49297)%233280) / 233280; }; // ── Build vehicles ──────────────────────────────────────── // Start with the 12 mapped vehicles (preserve x/y for the map), // enrich with business fields. Then add 40 more (no map coords). const _mapped = [ // Real plate numbers from the data screenshot · 浙F prefix // Coordinates positioned within Jiaxing Zhapu Port viewport (1240×800, sea below y=620) { id: "浙F03980F", x: 320, y: 180, h: 320, status: "ok", speed: 56, soc: 78, src: "B" }, { id: "浙F03311F", x: 460, y: 280, h: 60, status: "warn", speed: 0, soc: 24, src: "T" }, { id: "浙F03000F", x: 600, y: 240, h: 110, status: "ok", speed: 78, soc: 31, src: "B" }, { id: "浙FK800F", x: 760, y: 290, h: 200, status: "ok", speed: 64, soc: 82, src: "T" }, { id: "浙FK808F", x: 880, y: 410, h: 280, status: "ok", speed: 51, soc: 47, src: "B" }, { id: "浙F07918F", x: 240, y: 540, h: 30, status: "idle", speed: 0, soc: 96, src: "T" }, { id: "浙F01505F", x: 600, y: 470, h: 160, status: "ok", speed: 44, soc: 55, src: "B" }, { id: "浙F30778F", x: 540, y: 380, h: 240, status: "ok", speed: 38, soc: 71, src: "T" }, { id: "浙F39086F", x: 1000, y: 360, h: 90, status: "danger", speed: 0, soc: 9, src: "B" }, { id: "浙F02618F", x: 700, y: 540, h: 350, status: "ok", speed: 42, soc: 64, src: "T" }, { id: "浙F09860F", x: 960, y: 540, h: 70, status: "warn", speed: 0, soc: 18, src: "B" }, { id: "浙F02399F", x: 820, y: 470, h: 190, status: "ok", speed: 48, soc: 88, src: "J" }, ]; // Additional 40 vehicles — list-only, no map coords const _extra = [ "浙F08991F","浙F05969F","浙F07179F","浙F08278F","浙F02002F","浙F01689F", "浙F00598F","浙F02608F","浙F08638F","浙F00278F","浙F02289F","浙F06196F", "浙F00885F","浙F08889F","浙F03127F","浙F04421F","浙F05538F","浙F06693F", "浙F07412F","浙F08810F","浙F09125F","浙F10232F","浙F11456F","浙F12674F", "浙F13891F","浙F14037F","浙F15268F","浙F16495F","浙F17712F","浙F18934F", "浙F19156F","浙F20389F","浙F21516F","浙F22748F","浙F23973F","浙F24196F", "浙F25425F","浙F26658F","浙F27873F","浙F28095F", ].map(id => ({ id, x: null, y: null, h: 0, status: "ok", speed: 0, soc: 0, src: "T" })); // ── Enrichment ──────────────────────────────────────────── const _enrich = (v, i) => { const r = seed(i + 1); const isLeased = r() < 0.55; // 55% 外租 const dept = DEPARTMENTS[Math.floor(r() * 5)]; const company = isLeased ? COMPANIES.lease[Math.floor(r() * 3)] : COMPANIES.own[Math.floor(r() * 2)]; const ownCompany = COMPANIES.own[Math.floor(r() * 2)]; // Asset status correlates with vehicle status let asset, op, gps; if (v.status === "danger") { asset = "abnormal"; op = "suspended"; gps = "offline"; } else if (v.status === "idle") { asset = "in_stock"; op = "maintenance"; gps = r() < 0.4 ? "online" : "offline"; } else if (isLeased) { asset = "leasing"; op = "operating"; gps = v.status === "warn" ? "offline" : "online"; } else { asset = "in_stock"; op = r() < 0.4 ? "maintenance" : "operating"; gps = "online"; } // Status duration in days const statusDays = Math.floor(r() * 120) + 1; // Mileage const totalKm = Math.floor(r() * 80000) + 12000; const lastMaintKm = totalKm - Math.floor(r() * 8000) - 1000; const nextMaintKm = lastMaintKm + 10000; const kmToMaint = nextMaintKm - totalKm; // Last maintenance date (days ago) const lastMaintDays = Math.floor(r() * 90) + 1; // Grade const grade = ["A","B","B","C"][Math.floor(r()*4)]; // VIN const vin = VIN_PREFIXES[Math.floor(r() * VIN_PREFIXES.length)] + String(1000 + Math.floor(r()*9000)); const fleetCode = (i < 12 && r() < 0.3) ? (Math.floor(r()*99)+10) + "Q" : null; // Operating city const city = CITIES[Math.floor(r() * CITIES.length)]; // Customer (only if leased) const customer = asset === "leasing" ? CUSTOMERS[Math.floor(r() * 6)] : (asset === "in_stock" ? "—" : CUSTOMERS[Math.floor(r() * 6)]); // Parking const parking = PARKINGS[Math.floor(r() * PARKINGS.length)]; // Contract const hasContract = asset === "leasing" || (asset === "abnormal" && r() < 0.5); const contractNo = hasContract ? "JX-" + dept.id.toUpperCase() + "-2024-" + String(2000 + i) : null; const handoverKm = hasContract ? Math.floor(r() * 3000) + 200 : null; const returnKm = (asset === "in_stock" && hasContract) ? handoverKm + Math.floor(r()*40000) + 5000 : null; // Hydrogen pressure (MPa) and motor temp const h2 = v.status === "danger" ? 0.8 : (v.soc / 100 * 5.6 + 0.2).toFixed(1); const motorTemp = v.status === "danger" ? 102 : 58 + Math.floor(r()*15); return { ...v, plate: v.id, vin, fleetCode, city, parking, asset, own: isLeased ? "lease" : "self", op, gps, grade, statusDays, dept: dept.id, deptName: dept.name, deptLead: dept.lead, deptColor: dept.color, customer, company, ownCompany, contractNo, handoverKm, returnKm, totalKm, lastMaintKm, nextMaintKm, kmToMaint, lastMaintDays, h2, motorTemp, // Hydrogen-specific h2Pressure: parseFloat(h2), range: Math.round(v.soc * 6.2), }; }; const VEHICLES = [..._mapped, ..._extra].map(_enrich); // ── Aggregations for filters ────────────────────────────── const COUNTS = { all: VEHICLES.length, // Asset status inStock: VEHICLES.filter(v => v.asset === "in_stock").length, leasing: VEHICLES.filter(v => v.asset === "leasing").length, abnormal: VEHICLES.filter(v => v.asset === "abnormal").length, // Ownership self: VEHICLES.filter(v => v.own === "self").length, lease: VEHICLES.filter(v => v.own === "lease").length, // Operation operating: VEHICLES.filter(v => v.op === "operating").length, suspended: VEHICLES.filter(v => v.op === "suspended").length, maintenance: VEHICLES.filter(v => v.op === "maintenance").length, // GPS online: VEHICLES.filter(v => v.gps === "online").length, offline: VEHICLES.filter(v => v.gps === "offline").length, // Departments byDept: DEPARTMENTS.reduce((acc, d) => { acc[d.id] = VEHICLES.filter(v => v.dept === d.id).length; return acc; }, {}), }; // ── User roles for permission demo ──────────────────────── const ROLES = [ { id: "admin", name: "总管理员", scope: "all", desc: "可见所有车辆 · 所有部门" }, { id: "biz1_lead",name: "业务一部·负责人", scope: "dept", deptId: "biz1", desc: "仅可见业务一部车辆" }, { id: "biz2_lead",name: "业务二部·负责人", scope: "dept", deptId: "biz2", desc: "仅可见业务二部车辆" }, { id: "ops", name: "运营岗", scope: "ops", desc: "可见所有车辆 · 仅运维操作" }, { id: "finance", name: "财务岗", scope: "finance", desc: "可见合同/资产 · 隐藏实时车况" }, ]; // expose Object.assign(window, { VEHICLES, DEPARTMENTS, COMPANIES, CUSTOMERS, PARKINGS, CITIES, COUNTS, ROLES, }); })();