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
? `
暂无步骤
`
: `
| # | Code | Action | 设备 | 工位 | 确认 | 超时 | 方式 | 超时策略 | |
${steps.map(renderStepRow).join("")}
`
}
${renderStepForm()}
联锁
${
interlocks.length === 0
? `
暂无联锁
`
: `
| applies_to | rule_kind | 对象 ID | 期望 | 说明 | |
${interlocks.map(renderInterlockRow).join("")}
`
}
${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());
}