feat(integration): 新增数据接入监控页
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- 字段:车牌 / VIN / 品牌+型号 / GB32960 状态+最后接收 / JT808 状态+最后接收 / 接入时间
- 状态:在线 / 断流 / 未对接,三色 pill + 脉冲点
- 重点标记:双协议均未对接的车辆 → 行红底 + 警示图标 + 顶部 banner
- 工具栏:搜索(车牌/VIN/品牌/型号)+ 5 维筛选 + 4 维排序 + CSV 导出
- KPI:总车辆 / GB 在线 / GB 断流 / JT 在线 / JT 断流 / 完全未对接
- 数据:fleet.js 增 brand/model/gbStatus/gbLastSeen/jtStatus/jtLastSeen/onboardAt
- 路由 #/integration · sidebar 增 plug 图标项
This commit is contained in:
kkfluous
2026-04-28 15:45:59 +08:00
parent ed37fe3de5
commit e38bd8a1d8
5 changed files with 432 additions and 8 deletions

View File

@@ -133,6 +133,60 @@ const _enrich = (v, i) => {
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);
// ── Brand & model — 真实国内氢能车型样本 ──
const MODELS = [
{ brand: "上汽大通", model: "MIFA-H 氢燃料 MPV" },
{ brand: "上汽大通", model: "EUNIQ 7 氢电版" },
{ brand: "现代", model: "NEXO" },
{ brand: "丰田", model: "MIRAI 第二代" },
{ brand: "格罗夫", model: "格罗夫氢能 SUV" },
{ brand: "海马汽车", model: "7X-H" },
{ brand: "红旗", model: "H5 FCV" },
{ brand: "长安深蓝", model: "SL03 氢燃料版" },
{ brand: "飞驰科技", model: "FCB80 氢燃料客车" },
{ brand: "宇通客车", model: "ZK6105FCEVG3" },
];
const m = MODELS[Math.floor(r() * MODELS.length)];
// ── Integration / 对接情况 ──
// src: T = TBOX(GB/T 32960), J = JT808/1078, B = both
// 状态online (recent) / offline (had data, now stale) / not_connected (从未对接)
// 5% 完全未对接(重点标记) · 12% TBOX 断流 · 8% JT 断流
const integFlag = r();
let gbStatus, jtStatus;
if (i >= 12 && integFlag < 0.05) {
// 5% 全部未对接(重点标记)
gbStatus = "not_connected";
jtStatus = "not_connected";
} else {
if (v.src === "T" || v.src === "B") {
gbStatus = (r() < 0.12) ? "offline" : "online";
} else {
gbStatus = (r() < 0.30) ? "offline" : "not_connected";
}
if (v.src === "J" || v.src === "B") {
jtStatus = (r() < 0.08) ? "offline" : "online";
} else {
jtStatus = (r() < 0.20) ? "offline" : "not_connected";
}
}
// 时间戳生成
const now = Date.now();
const minute = 60 * 1000, hour = 60 * minute, day = 24 * hour;
const tsFor = (st, baseSeed) => {
if (st === "not_connected") return null;
if (st === "online") return now - Math.floor(baseSeed * 5 * minute) - 2000; // 2s ~ 5min
// offline
return now - Math.floor(baseSeed * 7 * day) - 30 * minute; // 30min ~ 7d ago
};
const gbLastSeen = tsFor(gbStatus, r());
const jtLastSeen = tsFor(jtStatus, r());
// 接入时间首次对接日期6 个月内随机)
const onboardDays = Math.floor(r() * 180) + 7;
const onboardAt = now - onboardDays * day;
return {
...v,
plate: v.id,
@@ -148,6 +202,12 @@ const _enrich = (v, i) => {
// Hydrogen-specific
h2Pressure: parseFloat(h2),
range: Math.round(v.soc * 6.2),
// Brand / model
brand: m.brand, model: m.model,
// Integration / 对接情况
gbStatus, gbLastSeen,
jtStatus, jtLastSeen,
onboardAt,
};
};
@@ -175,6 +235,17 @@ const COUNTS = {
acc[d.id] = VEHICLES.filter(v => v.dept === d.id).length;
return acc;
}, {}),
// Integration / 数据接入
gbOnline: VEHICLES.filter(v => v.gbStatus === "online").length,
gbOffline: VEHICLES.filter(v => v.gbStatus === "offline").length,
gbNotConn: VEHICLES.filter(v => v.gbStatus === "not_connected").length,
jtOnline: VEHICLES.filter(v => v.jtStatus === "online").length,
jtOffline: VEHICLES.filter(v => v.jtStatus === "offline").length,
jtNotConn: VEHICLES.filter(v => v.jtStatus === "not_connected").length,
// 完全未对接(重点标记)
bothNone: VEHICLES.filter(v => v.gbStatus === "not_connected" && v.jtStatus === "not_connected").length,
// 任一在线
anyOnline: VEHICLES.filter(v => v.gbStatus === "online" || v.jtStatus === "online").length,
};
// ── User roles for permission demo ────────────────────────