import { segmentApi } from "./api.js"; import { el, escapeHtml, setBanner } from "./dom.js"; const SEGMENT_TYPES = [ "front_load", "robot", "front_release", "front_transfer", "kiln_infeed", "kiln_step", "kiln_outfeed", "tail_transfer", "tail_step", "unload", "return", ]; const SEGMENT_MODES = ["auto", "remote_manual", "local_manual", "disabled"]; const ACTION_KINDS = [ "open_door", "close_door", "push_forward", "push_retract", "pull_run", "pull_retract", "transfer_move_to", "step_once", "robot_permit", "robot_release", "wait_signal", "pulse_cmd", ]; const ON_TIMEOUT = ["fault", "retry", "block"]; const APPLIES_TO = ["start_allow", "start_deny", "run_halt"]; const RULE_KINDS = [ "point_eq", "station_vacant", "station_occupied", "equipment_origin", "equipment_no_fault", "equipment_remote", "safety_chain_ok", ]; const segments = new Map(); const segmentDetails = new Map(); // id -> { segment, steps, interlocks, resources } const expanded = new Set(); let editing = null; let creating = false; function renderSegmentForm(initial) { const data = initial || {}; return `
`; } function renderStepRow(step) { return ` ${step.step_no} ${escapeHtml(step.step_code)} ${escapeHtml(step.action_kind)} ${escapeHtml(step.target_equipment_id || "")} ${escapeHtml(step.target_station_id || "")} ${escapeHtml(step.confirm_signal_role || "")} ${step.timeout_ms} ${step.hold_until_confirm ? "保持" : "脉冲"} ${escapeHtml(step.on_timeout)} `; } function renderStepForm() { return `
`; } function renderInterlockRow(rule) { return ` ${escapeHtml(rule.applies_to)} ${escapeHtml(rule.rule_kind)} ${escapeHtml(rule.point_id || rule.station_id || rule.equipment_id || "")} ${rule.expected_value === null || rule.expected_value === undefined ? "" : rule.expected_value ? "true" : "false"} ${escapeHtml(rule.description || "")} `; } function renderInterlockForm() { return `
`; } function renderResourcesEditor(detail) { const keys = (detail?.resources || []).map((r) => r.resource_key); return `
`; } function renderDetail(detail) { const steps = detail?.steps || []; const interlocks = detail?.interlocks || []; return `

步骤

${ steps.length === 0 ? `
暂无步骤
` : `${steps.map(renderStepRow).join("")}
#CodeAction设备工位确认超时方式超时策略
` } ${renderStepForm()}

联锁

${ interlocks.length === 0 ? `
暂无联锁
` : `${interlocks.map(renderInterlockRow).join("")}
applies_torule_kind对象 ID期望说明
` } ${renderInterlockForm()}

资源声明

${renderResourcesEditor(detail)}
`; } function renderRow(segment) { const isExpanded = expanded.has(segment.id); const isEditing = editing === segment.id; const detail = segmentDetails.get(segment.id); return `
${escapeHtml(segment.code)} ${escapeHtml(segment.name)} ${segment.line_code ? `${escapeHtml(segment.line_code)}` : ""} ${escapeHtml(segment.segment_type)} ${escapeHtml(segment.mode)} ${segment.enabled ? "" : `已禁用`}
${isEditing ? `
${renderSegmentForm(segment)}
` : ""} ${isExpanded ? renderDetail(detail) : ""}
`; } function renderAll() { const root = el("segmentConfigList"); if (!root) return; const list = Array.from(segments.values()).sort((a, b) => a.code.localeCompare(b.code)); root.innerHTML = ` ${creating ? `
${renderSegmentForm({})}
` : ""} ${list.length === 0 ? `
尚无段
` : list.map(renderRow).join("")} `; } function segmentFormToPayload(form) { const data = Object.fromEntries(new FormData(form)); const payload = { code: data.code?.trim(), name: data.name?.trim(), segment_type: data.segment_type, mode: data.mode, enabled: form.elements.enabled.checked, require_manual_ack_after_fault: form.elements.require_manual_ack_after_fault.checked, priority: Number(data.priority || 0), }; if (data.line_code) payload.line_code = data.line_code.trim(); if (data.description) payload.description = data.description; return payload; } function stepFormToPayload(form) { const data = Object.fromEntries(new FormData(form)); const payload = { step_no: Number(data.step_no), step_code: data.step_code, action_kind: data.action_kind, on_timeout: data.on_timeout || "fault", hold_until_confirm: form.elements.hold_until_confirm.checked, cancel_on_fault: form.elements.cancel_on_fault.checked, }; for (const key of [ "target_equipment_id", "target_station_id", "confirm_signal_role", "command_role", "stop_command_role", ]) { if (data[key]) payload[key] = data[key].trim(); } if (data.pulse_ms) payload.pulse_ms = Number(data.pulse_ms); if (data.timeout_ms) payload.timeout_ms = Number(data.timeout_ms); return payload; } function interlockFormToPayload(form) { const data = Object.fromEntries(new FormData(form)); const payload = { applies_to: data.applies_to, rule_kind: data.rule_kind, }; for (const key of ["point_id", "station_id", "equipment_id"]) { if (data[key]) payload[key] = data[key].trim(); } if (data.expected_value === "true") payload.expected_value = true; else if (data.expected_value === "false") payload.expected_value = false; if (data.description) payload.description = data.description; return payload; } async function refreshDetail(segmentId) { try { const detail = await segmentApi.detail(segmentId); segmentDetails.set(segmentId, detail); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } } async function handleClick(event) { const button = event.target.closest("button[data-action]"); if (!button) return; const action = button.dataset.action; const row = event.target.closest(".config-row"); const segmentId = row?.dataset?.segmentId; switch (action) { case "cancel-form": creating = false; editing = null; return renderAll(); case "toggle": if (!segmentId) return; if (expanded.has(segmentId)) { expanded.delete(segmentId); } else { expanded.add(segmentId); await refreshDetail(segmentId); } return renderAll(); case "edit": editing = editing === segmentId ? null : segmentId; return renderAll(); case "delete": if (!segmentId) return; if (!window.confirm("确认删除该段及其步骤 / 联锁 / 资源声明?")) return; try { await segmentApi.remove(segmentId); segments.delete(segmentId); segmentDetails.delete(segmentId); expanded.delete(segmentId); if (editing === segmentId) editing = null; renderAll(); setBanner(el("segmentConfigList"), "段已删除", "info"); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } return; case "delete-step": { if (!segmentId) return; const stepNo = button.dataset.stepNo; try { await segmentApi.deleteStep(segmentId, stepNo); await refreshDetail(segmentId); renderAll(); setBanner(el("segmentConfigList"), `已删除步骤 ${stepNo}`, "info"); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } return; } case "delete-interlock": { if (!segmentId) return; const interlockId = button.dataset.id; try { await segmentApi.deleteInterlock(segmentId, interlockId); await refreshDetail(segmentId); renderAll(); setBanner(el("segmentConfigList"), "已删除联锁", "info"); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } return; } default: return; } } async function handleSubmit(event) { const form = event.target.closest("form[data-form]"); if (!form) return; event.preventDefault(); const row = form.closest(".config-row"); const segmentId = row?.dataset?.segmentId; const kind = form.dataset.form; if (kind === "segment") { const payload = segmentFormToPayload(form); try { if (segmentId && editing === segmentId) { await segmentApi.update(segmentId, payload); setBanner(el("segmentConfigList"), "段已更新", "info"); } else { await segmentApi.create(payload); setBanner(el("segmentConfigList"), "段已创建", "info"); } creating = false; editing = null; await loadSegmentsConfig(); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } return; } if (!segmentId) return; if (kind === "step") { const payload = stepFormToPayload(form); try { await segmentApi.createStep(segmentId, payload); await refreshDetail(segmentId); renderAll(); setBanner(el("segmentConfigList"), "步骤已新增", "info"); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } return; } if (kind === "interlock") { const payload = interlockFormToPayload(form); try { await segmentApi.createInterlock(segmentId, payload); await refreshDetail(segmentId); renderAll(); setBanner(el("segmentConfigList"), "联锁已新增", "info"); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } return; } if (kind === "resources") { const raw = form.elements.resource_keys.value || ""; const keys = raw .split(/[,\n]/) .map((k) => k.trim()) .filter((k) => k.length > 0); try { await segmentApi.replaceResources(segmentId, keys); await refreshDetail(segmentId); renderAll(); setBanner(el("segmentConfigList"), "资源声明已保存", "info"); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } } } export async function loadSegmentsConfig() { try { const rows = await segmentApi.list(); segments.clear(); rows.forEach((s) => segments.set(s.id, s)); renderAll(); } catch (err) { setBanner(el("segmentConfigList"), err.message || String(err), "error"); } } export function bindSegmentConfigEvents() { const root = el("segmentConfigList"); if (root) { root.addEventListener("click", handleClick); root.addEventListener("submit", handleSubmit); } const addBtn = el("addSegmentBtn"); if (addBtn) { addBtn.addEventListener("click", () => { creating = !creating; editing = null; renderAll(); }); } const refreshBtn = el("refreshSegmentConfigBtn"); if (refreshBtn) refreshBtn.addEventListener("click", () => loadSegmentsConfig()); }