324 lines
9.8 KiB
JavaScript
324 lines
9.8 KiB
JavaScript
import { apiFetch } from "./api.js";
|
|
import { dom } from "./dom.js";
|
|
import { renderEquipmentKindOptions, renderRoleOptions } from "./roles.js";
|
|
import { clearSelectedPoints, loadPoints, updatePointFilterSummary } from "./points.js";
|
|
import { state } from "./state.js";
|
|
|
|
function equipmentOf(item) {
|
|
return item && item.equipment ? item.equipment : item;
|
|
}
|
|
|
|
function currentUnitLabel(unitId) {
|
|
if (!unitId) {
|
|
return "未绑定单元";
|
|
}
|
|
const unit = state.unitMap.get(unitId);
|
|
return unit ? `${unit.code} / ${unit.name}` : "未知单元";
|
|
}
|
|
|
|
function filteredEquipments() {
|
|
if (!state.selectedUnitId) {
|
|
return state.equipments;
|
|
}
|
|
|
|
return state.equipments.filter((item) => {
|
|
const equipment = equipmentOf(item);
|
|
return equipment.unit_id === state.selectedUnitId;
|
|
});
|
|
}
|
|
|
|
function renderEquipmentUnitOptions(selected = "", target = dom.equipmentUnitId) {
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
const options = ['<option value="">未绑定单元</option>'];
|
|
state.units.forEach((unit) => {
|
|
const isSelected = unit.id === selected ? "selected" : "";
|
|
options.push(`<option value="${unit.id}" ${isSelected}>${unit.code} / ${unit.name}</option>`);
|
|
});
|
|
target.innerHTML = options.join("");
|
|
}
|
|
|
|
function renderBatchUnitOptions(selected = "") {
|
|
if (!dom.equipmentBatchUnitId) {
|
|
return;
|
|
}
|
|
|
|
const options = ['<option value="">批量绑定到单元...</option>'];
|
|
state.units.forEach((unit) => {
|
|
const isSelected = unit.id === selected ? "selected" : "";
|
|
options.push(`<option value="${unit.id}" ${isSelected}>${unit.code} / ${unit.name}</option>`);
|
|
});
|
|
dom.equipmentBatchUnitId.innerHTML = options.join("");
|
|
}
|
|
|
|
function updateSelectedEquipmentSummary() {
|
|
if (!dom.selectedEquipmentSummary) {
|
|
return;
|
|
}
|
|
dom.selectedEquipmentSummary.textContent = `已选 ${state.selectedEquipmentIds.size} 台设备`;
|
|
}
|
|
|
|
export function renderBindingEquipmentOptions(selected = "", target = dom.bindingEquipmentId) {
|
|
const options = ['<option value="">Unbound</option>'];
|
|
filteredEquipments().forEach((item) => {
|
|
const equipment = equipmentOf(item);
|
|
const isSelected = equipment.id === selected ? "selected" : "";
|
|
options.push(
|
|
`<option value="${equipment.id}" ${isSelected}>${equipment.code} / ${equipment.name}</option>`,
|
|
);
|
|
});
|
|
target.innerHTML = options.join("");
|
|
}
|
|
|
|
export function renderBatchBindingDefaults() {
|
|
renderBindingEquipmentOptions("", dom.batchBindingEquipmentId);
|
|
dom.batchBindingSignalRole.innerHTML = renderRoleOptions("");
|
|
}
|
|
|
|
export function resetEquipmentForm() {
|
|
dom.equipmentForm.reset();
|
|
dom.equipmentId.value = "";
|
|
renderEquipmentUnitOptions("");
|
|
dom.equipmentKind.innerHTML = renderEquipmentKindOptions("");
|
|
}
|
|
|
|
function openEquipmentModal() {
|
|
dom.equipmentModal.classList.remove("hidden");
|
|
}
|
|
|
|
export function closeEquipmentModal() {
|
|
dom.equipmentModal.classList.add("hidden");
|
|
}
|
|
|
|
export function openCreateEquipmentModal() {
|
|
resetEquipmentForm();
|
|
openEquipmentModal();
|
|
}
|
|
|
|
function openEditEquipmentModal(equipment) {
|
|
dom.equipmentId.value = equipment.id || "";
|
|
dom.equipmentUnitId.value = equipment.unit_id || "";
|
|
dom.equipmentCode.value = equipment.code || "";
|
|
dom.equipmentName.value = equipment.name || "";
|
|
dom.equipmentKind.innerHTML = renderEquipmentKindOptions(equipment.kind || "");
|
|
dom.equipmentDescription.value = equipment.description || "";
|
|
openEquipmentModal();
|
|
}
|
|
|
|
async function selectEquipment(equipmentId) {
|
|
state.selectedEquipmentId = state.selectedEquipmentId === equipmentId ? null : equipmentId;
|
|
state.pointsPage = 1;
|
|
clearSelectedPoints();
|
|
renderEquipments();
|
|
updatePointFilterSummary();
|
|
await loadPoints();
|
|
}
|
|
|
|
function toggleEquipmentSelection(equipmentId, checked) {
|
|
if (checked) {
|
|
state.selectedEquipmentIds.add(equipmentId);
|
|
} else {
|
|
state.selectedEquipmentIds.delete(equipmentId);
|
|
}
|
|
updateSelectedEquipmentSummary();
|
|
}
|
|
|
|
export function clearSelectedEquipments() {
|
|
state.selectedEquipmentIds.clear();
|
|
renderEquipments();
|
|
updateSelectedEquipmentSummary();
|
|
}
|
|
|
|
export function clearEquipmentFilter() {
|
|
state.selectedEquipmentId = null;
|
|
state.pointsPage = 1;
|
|
renderEquipments();
|
|
updatePointFilterSummary();
|
|
return loadPoints();
|
|
}
|
|
|
|
export function renderEquipments() {
|
|
dom.equipmentList.innerHTML = "";
|
|
updateSelectedEquipmentSummary();
|
|
|
|
const activeEquipment = state.selectedEquipmentId
|
|
? state.equipmentMap.get(state.selectedEquipmentId) || null
|
|
: null;
|
|
dom.clearEquipmentFilterBtn.textContent = activeEquipment
|
|
? `设备筛选 ${activeEquipment.name}`
|
|
: "设备筛选 全部";
|
|
|
|
const items = filteredEquipments();
|
|
if (!items.length) {
|
|
dom.equipmentList.innerHTML = '<div class="list-item"><div class="muted">No equipment</div></div>';
|
|
return;
|
|
}
|
|
|
|
items.forEach((item) => {
|
|
const equipment = equipmentOf(item);
|
|
const box = document.createElement("div");
|
|
box.className = `list-item equipment-card ${state.selectedEquipmentId === equipment.id ? "selected" : ""}`;
|
|
box.innerHTML = `
|
|
<label class="equipment-select-row">
|
|
<input type="checkbox" data-equipment-select="true" ${state.selectedEquipmentIds.has(equipment.id) ? "checked" : ""} />
|
|
<span class="muted">批量选择</span>
|
|
</label>
|
|
<div class="row">
|
|
<strong>${equipment.code}</strong>
|
|
<span class="badge">${item.point_count ?? 0} pts</span>
|
|
</div>
|
|
<div>${equipment.name}</div>
|
|
<div class="muted">${equipment.kind || "No type"}</div>
|
|
<div class="muted">单元: ${currentUnitLabel(equipment.unit_id)}</div>
|
|
<div class="row equipment-card-actions"></div>
|
|
`;
|
|
|
|
box.addEventListener("click", () => {
|
|
selectEquipment(equipment.id).catch((error) => {
|
|
dom.statusText.textContent = error.message;
|
|
});
|
|
});
|
|
|
|
const checkbox = box.querySelector('input[data-equipment-select="true"]');
|
|
checkbox.addEventListener("click", (event) => {
|
|
event.stopPropagation();
|
|
});
|
|
checkbox.addEventListener("change", (event) => {
|
|
toggleEquipmentSelection(equipment.id, event.target.checked);
|
|
});
|
|
|
|
const actionRow = box.querySelector(".equipment-card-actions");
|
|
|
|
const editBtn = document.createElement("button");
|
|
editBtn.className = "secondary";
|
|
editBtn.textContent = "Edit";
|
|
editBtn.addEventListener("click", (event) => {
|
|
event.stopPropagation();
|
|
openEditEquipmentModal(equipment);
|
|
});
|
|
|
|
const deleteBtn = document.createElement("button");
|
|
deleteBtn.className = "danger";
|
|
deleteBtn.textContent = "Delete";
|
|
deleteBtn.addEventListener("click", (event) => {
|
|
event.stopPropagation();
|
|
deleteEquipment(equipment.id).catch((error) => {
|
|
dom.statusText.textContent = error.message;
|
|
});
|
|
});
|
|
|
|
actionRow.append(editBtn, deleteBtn);
|
|
dom.equipmentList.appendChild(box);
|
|
});
|
|
}
|
|
|
|
export async function loadEquipments() {
|
|
const keyword = dom.equipmentKeyword.value.trim();
|
|
const query = keyword
|
|
? `?page=1&page_size=-1&keyword=${encodeURIComponent(keyword)}`
|
|
: "?page=1&page_size=-1";
|
|
const data = await apiFetch(`/api/equipment${query}`);
|
|
state.equipments = data.data || [];
|
|
state.equipmentMap = new Map(
|
|
state.equipments.map((item) => {
|
|
const equipment = equipmentOf(item);
|
|
return [equipment.id, equipment];
|
|
}),
|
|
);
|
|
|
|
state.selectedEquipmentIds.forEach((id) => {
|
|
if (!state.equipmentMap.has(id)) {
|
|
state.selectedEquipmentIds.delete(id);
|
|
}
|
|
});
|
|
|
|
renderEquipmentUnitOptions(dom.equipmentUnitId?.value || "");
|
|
renderBatchUnitOptions(dom.equipmentBatchUnitId?.value || "");
|
|
dom.equipmentKind.innerHTML = renderEquipmentKindOptions(dom.equipmentKind?.value || "");
|
|
renderBindingEquipmentOptions();
|
|
renderBatchBindingDefaults();
|
|
if (state.selectedEquipmentId && !state.equipmentMap.has(state.selectedEquipmentId)) {
|
|
state.selectedEquipmentId = null;
|
|
}
|
|
renderEquipments();
|
|
updatePointFilterSummary();
|
|
document.dispatchEvent(new Event("equipments-updated"));
|
|
}
|
|
|
|
export async function saveEquipment(event) {
|
|
event.preventDefault();
|
|
|
|
const unitId = dom.equipmentUnitId.value || null;
|
|
const payload = {
|
|
unit_id: unitId,
|
|
code: dom.equipmentCode.value.trim(),
|
|
name: dom.equipmentName.value.trim(),
|
|
kind: dom.equipmentKind.value.trim() || null,
|
|
description: dom.equipmentDescription.value.trim() || null,
|
|
};
|
|
|
|
const id = dom.equipmentId.value;
|
|
const result = await apiFetch(id ? `/api/equipment/${id}` : "/api/equipment", {
|
|
method: id ? "PUT" : "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
closeEquipmentModal();
|
|
await loadEquipments();
|
|
if (!id && result?.id) {
|
|
state.selectedEquipmentId = result.id;
|
|
}
|
|
renderEquipments();
|
|
updatePointFilterSummary();
|
|
await loadPoints();
|
|
}
|
|
|
|
export async function applyBatchEquipmentUnit() {
|
|
if (!state.selectedEquipmentIds.size) {
|
|
throw new Error("请先选择设备");
|
|
}
|
|
|
|
const value = dom.equipmentBatchUnitId.value;
|
|
await apiFetch("/api/equipment/batch/set-unit", {
|
|
method: "PUT",
|
|
body: JSON.stringify({
|
|
equipment_ids: Array.from(state.selectedEquipmentIds),
|
|
unit_id: value || null,
|
|
}),
|
|
});
|
|
|
|
clearSelectedEquipments();
|
|
renderBatchUnitOptions("");
|
|
await loadEquipments();
|
|
}
|
|
|
|
export async function deleteEquipment(equipmentId) {
|
|
if (!window.confirm("Delete this equipment?")) {
|
|
return;
|
|
}
|
|
|
|
await apiFetch(`/api/equipment/${equipmentId}`, { method: "DELETE" });
|
|
if (state.selectedEquipmentId === equipmentId) {
|
|
state.selectedEquipmentId = null;
|
|
}
|
|
state.selectedEquipmentIds.delete(equipmentId);
|
|
resetEquipmentForm();
|
|
closeEquipmentModal();
|
|
clearSelectedPoints();
|
|
await loadEquipments();
|
|
await loadPoints();
|
|
}
|
|
|
|
export async function clearPointBinding(pointId = dom.bindingPointId.value) {
|
|
await apiFetch(`/api/point/${pointId}`, {
|
|
method: "PUT",
|
|
body: JSON.stringify({ equipment_id: null, signal_role: null }),
|
|
});
|
|
|
|
dom.pointBindingModal.classList.add("hidden");
|
|
await loadEquipments();
|
|
await loadPoints();
|
|
}
|