import { apiFetch } from "./api.js"; import { dom } from "./dom.js"; import { formatValue } from "./points.js"; import { state } from "./state.js"; const SIGNAL_ROLES = ["rem", "run", "flt"]; const ROLE_LABELS = { rem: "REM", run: "RUN", flt: "FLT" }; export function renderOpsUnits() { if (!dom.opsUnitList) return; dom.opsUnitList.innerHTML = ""; if (!state.units.length) { dom.opsUnitList.innerHTML = '
暂无控制单元
'; return; } state.units.forEach((unit) => { const runtime = state.runtimes.get(unit.id); const item = document.createElement("div"); item.className = `ops-unit-item${state.selectedOpsUnitId === unit.id ? " selected" : ""}`; item.innerHTML = `
${unit.code} / ${unit.name}
${unit.enabled ? "EN" : "DIS"} ${runtime ? `Acc ${Math.floor(runtime.accumulated_run_sec / 1000)}s` : ""}
`; item.addEventListener("click", () => selectOpsUnit(unit.id)); dom.opsUnitList.appendChild(item); }); } async function selectOpsUnit(unitId) { state.selectedOpsUnitId = unitId === state.selectedOpsUnitId ? null : unitId; renderOpsUnits(); if (!state.selectedOpsUnitId) { await loadAllEquipmentCards(); return; } dom.opsEquipmentArea.innerHTML = '
加载中...
'; state.opsPointEls.clear(); const detail = await apiFetch(`/api/unit/${state.selectedOpsUnitId}/detail`); renderOpsEquipments(detail.equipments || []); } export async function loadAllEquipmentCards() { if (!dom.opsEquipmentArea) return; if (!state.units.length) { dom.opsEquipmentArea.innerHTML = '
暂无控制单元
'; return; } dom.opsEquipmentArea.innerHTML = '
加载中...
'; state.opsPointEls.clear(); const details = await Promise.all( state.units.map((u) => apiFetch(`/api/unit/${u.id}/detail`).catch(() => ({ equipments: [] }))) ); const allEquipments = details.flatMap((d) => d.equipments || []); renderOpsEquipments(allEquipments); } function renderOpsEquipments(equipments) { dom.opsEquipmentArea.innerHTML = ""; if (!equipments.length) { dom.opsEquipmentArea.innerHTML = '
该单元下暂无设备
'; return; } equipments.forEach((eq) => { const card = document.createElement("div"); card.className = "ops-eq-card"; // Build role → point map const roleMap = {}; (eq.points || []).forEach((p) => { if (p.signal_role) roleMap[p.signal_role] = p; }); // Signal rows HTML (placeholders; WS will fill values) const signalRowsHtml = SIGNAL_ROLES.map((role) => { const point = roleMap[role]; if (!point) return ""; return `
${ROLE_LABELS[role] || role} ? --
`; }).join(""); const canControl = eq.kind === "coal_feeder" || eq.kind === "distributor"; card.innerHTML = `
${eq.code} ${eq.kind || "--"}
${signalRowsHtml || '无绑定信号'}
${canControl ? '
' : ""} `; if (canControl) { const actions = card.querySelector(".ops-eq-card-actions"); const startBtn = document.createElement("button"); startBtn.className = "secondary"; startBtn.textContent = "Start"; startBtn.addEventListener("click", () => apiFetch(`/api/control/equipment/${eq.id}/start`, { method: "POST" }).catch(() => {}) ); const stopBtn = document.createElement("button"); stopBtn.className = "danger"; stopBtn.textContent = "Stop"; stopBtn.addEventListener("click", () => apiFetch(`/api/control/equipment/${eq.id}/stop`, { method: "POST" }).catch(() => {}) ); actions.append(startBtn, stopBtn); } dom.opsEquipmentArea.appendChild(card); // Register DOM elements for WS updates, then seed from cached monitor data SIGNAL_ROLES.forEach((role) => { const point = roleMap[role]; if (!point) return; const valueEl = card.querySelector(`[data-ops-value="${point.id}"]`); const qualityEl = card.querySelector(`[data-ops-quality="${point.id}"]`); if (valueEl && qualityEl) { state.opsPointEls.set(point.id, { valueEl, qualityEl }); if (point.point_monitor) { const m = point.point_monitor; valueEl.textContent = formatValue(m); qualityEl.className = `badge quality-${(m.quality || "unknown").toLowerCase()}`; qualityEl.textContent = (m.quality || "unknown").toUpperCase(); } } }); }); } export function startOps() { renderOpsUnits(); loadAllEquipmentCards(); }