RTU_ALL_AI/config/web/js/pages/plc_debug.js

216 lines
10 KiB
JavaScript

/**
* @file plc_debug.js
* @brief PLC 调试页 — 指示灯/按键绑定 out 信号 + 增量刷新
*/
var PlcDebugLeds = [];
var PlcDebugButtons = [];
Router.register('#plc_debug', function(c) { PlcDebugPage.render(c); });
var PlcDebugPage = (function() {
var outSignals = [];
var ledChecked = {}, btnChecked = {}, sigChecked = {};
function getOut() {
var out = SignalStore.get('out');
return (Array.isArray(out) && out.length) ? out : outSignals;
}
function showPicker(cb) {
var sigs = getOut();
if (!sigs.length) { toast(I18n.t('plc_debug.no_sig'), 'error'); return; }
var rows = '';
sigs.forEach(function(s) {
rows += '<div class="plc-pick-row" data-saddr="' + esc(s.saddr) + '" data-desc="' + esc(s.desc || '') + '">'
+ '<strong>' + esc(s.saddr) + '</strong>'
+ (s.desc ? ' <span style="color:var(--text-muted)">' + esc(s.desc) + '</span>' : '')
+ '<span style="float:right;color:var(--accent);font-weight:700">' + fmtVal(s.val) + '</span></div>';
});
var ov = document.createElement('div');
ov.className = 'modal-overlay';
ov.innerHTML = '<div class="modal-box" style="max-width:550px"><h3>' + I18n.t('plc_debug.bind_title') + '</h3>'
+ '<div style="max-height:300px;overflow-y:auto;margin:12px 0">' + rows + '</div>'
+ '<button class="btn btn-sm" id="plc-pick-cancel">' + I18n.t('toast.cancel') + '</button></div>';
document.body.appendChild(ov);
document.getElementById('plc-pick-cancel').onclick = function() { ov.remove(); };
ov.addEventListener('click', function(e) { if (e.target === ov) ov.remove(); });
ov.querySelectorAll('.plc-pick-row').forEach(function(r) {
r.onclick = function() {
var sa = this.getAttribute('data-saddr'), de = this.getAttribute('data-desc');
ov.remove();
if (confirm(I18n.t('plc_debug.confirm_bind') + ' ' + sa + ' ?')) cb(sa, de);
};
});
}
function findSig(sa) {
for (var i = 0; i < outSignals.length; i++) if (outSignals[i].saddr === sa) return outSignals[i];
return null;
}
function refreshData() {
var o = getOut(); if (Array.isArray(o)) outSignals = o;
PlcDebugLeds.forEach(function(l) { var f = findSig(l.saddr); l.val = f ? f.val : 0; });
PlcDebugButtons.forEach(function(b) { var f = findSig(b.saddr); b.val = f ? f.val : 0; });
}
function incrRefresh() {
refreshData();
PlcDebugLeds.forEach(function(l, i) {
var on = parseFloat(l.val) > 0;
var ve = document.getElementById('led-val-' + i), ie = document.getElementById('led-icon-' + i);
if (ve) { ve.textContent = '(' + l.val + ')'; ve.style.color = on ? 'var(--accent)' : 'var(--text-muted)'; }
if (ie) ie.className = 'plc-led' + (on ? ' plc-led-on' : '');
});
PlcDebugButtons.forEach(function(b, j) {
var ve = document.getElementById('btn-val-' + j);
if (ve) { ve.textContent = '(' + b.val + ')'; ve.style.color = parseFloat(b.val) > 0 ? 'var(--accent)' : 'var(--text-muted)'; }
});
}
function countChecked(obj) { var n = 0; for (var k in obj) if (obj.hasOwnProperty(k)) n++; return n; }
function delChecked() {
var todel = [];
for (var k in ledChecked) if (ledChecked.hasOwnProperty(k)) todel.push({ type: 'led', idx: parseInt(k) });
for (var k in btnChecked) if (btnChecked.hasOwnProperty(k)) todel.push({ type: 'btn', idx: parseInt(k) });
if (!todel.length) { toast(I18n.t('toast.no_selected'), 'error'); return; }
if (!confirm(I18n.t('plc_debug.confirm_del') + ' ' + todel.length + ' ?')) return;
todel.sort(function(a, b) { return b.idx - a.idx; }).forEach(function(t) {
if (t.type === 'led') PlcDebugLeds.splice(t.idx, 1);
else PlcDebugButtons.splice(t.idx, 1);
});
ledChecked = {}; btnChecked = {};
renderAll();
}
function renderAll() {
var c = document.getElementById('plc-content');
if (!c) return;
refreshData();
/* 指示灯列 */
var ledHtml = '';
if (!PlcDebugLeds.length) {
ledHtml = '<div style="color:var(--text-muted);padding:20px;text-align:center">' + I18n.t('plc_debug.no_led') + '</div>';
} else {
PlcDebugLeds.forEach(function(l, i) {
var on = parseFloat(l.val) > 0, chk = !!ledChecked[i];
ledHtml += '<div class="plc-item">'
+ '<div class="plc-led' + (on ? ' plc-led-on' : '') + '" id="led-icon-' + i + '" onclick="PlcDebugPage._bindLed(' + i + ')" title="' + I18n.t('plc_debug.click_to_bind') + '"></div>'
+ '<div class="plc-info">'
+ (l.saddr ? '<span class="plc-saddr">' + esc(l.saddr) + ' <span id="led-val-' + i + '" style="color:' + (on ? 'var(--accent)' : 'var(--text-muted)') + '">(' + fmtVal(l.val) + ')</span></span>'
+ (l.desc ? '<span style="color:var(--text-muted);font-size:11px">' + esc(l.desc) + '</span>' : '')
: '<span style="color:var(--text-muted)">' + I18n.t('plc_debug.unbound') + '</span>')
+ '</div>'
+ '<input type="checkbox" ' + (chk ? 'checked' : '') + ' onchange="PlcDebugPage._toggleLed(' + i + ',this.checked)">'
+ '</div>';
});
}
/* 按键列 */
var btnHtml = '';
if (!PlcDebugButtons.length) {
btnHtml = '<div style="color:var(--text-muted);padding:20px;text-align:center">' + I18n.t('plc_debug.no_btn') + '</div>';
} else {
PlcDebugButtons.forEach(function(b, j) {
var chk = !!btnChecked[j];
btnHtml += '<div class="plc-item">'
+ '<button class="plc-btn-icon" id="btn-icon-' + j + '" onclick="PlcDebugPage._toggleBtn(' + j + ')" title="' + I18n.t('plc_debug.toggle') + '">🔘</button>'
+ '<div class="plc-info">'
+ (b.saddr ? '<span class="plc-saddr">' + esc(b.saddr) + ' <span id="btn-val-' + j + '" style="color:var(--text-muted)">(' + fmtVal(b.val) + ')</span></span>'
+ (b.desc ? '<span style="color:var(--text-muted);font-size:11px">' + esc(b.desc) + '</span>' : '')
: '<span style="color:var(--text-muted)">' + I18n.t('plc_debug.unbound') + '</span>')
+ '</div>'
+ '<input type="checkbox" ' + (chk ? 'checked' : '') + ' onchange="PlcDebugPage._toggleBtnChk(' + j + ',this.checked)">'
+ '</div>';
});
}
/* 信号列 */
var o = getOut();
var sigHtml = '';
if (!o.length) {
sigHtml = '<div style="color:var(--text-muted);padding:20px;text-align:center">' + I18n.t('plc_debug.no_sig') + '</div>';
} else {
o.forEach(function(s, i) {
var chk = !!sigChecked[i];
sigHtml += '<div class="plc-item">'
+ '<input type="checkbox" ' + (chk ? 'checked' : '') + ' onchange="PlcDebugPage._toggleSig(' + i + ',this.checked)">'
+ '<span class="plc-saddr">' + esc(s.saddr) + '</span>'
+ '<span style="color:var(--text-muted);font-size:11px">' + esc(s.desc || '') + '</span>'
+ '<span style="margin-left:auto;color:var(--accent);font-weight:700;font-family:var(--font-mono)">' + fmtVal(s.val) + '</span>'
+ '</div>';
});
}
c.innerHTML = '<div class="plc-debug-grid">'
+ '<div class="plc-col"><div class="card-header" style="margin-bottom:8px"><span>💡 ' + I18n.t('plc_debug.led_col') + '</span>'
+ '<div class="btn-group">'
+ '<button class="btn btn-sm" onclick="PlcDebugPage._addLed()">' + I18n.t('plc_debug.add_led') + '</button>'
+ '</div></div><div class="plc-sig-list">' + ledHtml + '</div></div>'
+ '<div class="plc-col plc-sig-col"><div class="card-header" style="margin-bottom:8px"><span>📋 ' + I18n.t('plc_debug.sig_col') + '</span>'
+ '<div class="plc-arrow-row">'
+ '<button class="btn btn-sm" onclick="PlcDebugPage._moveSel(\'led\')" title="' + I18n.t('plc_debug.to_led') + '">← LED</button>'
+ '<button class="btn btn-sm" onclick="PlcDebugPage._moveSel(\'btn\')" title="' + I18n.t('plc_debug.to_btn') + '">→ BTN</button>'
+ '</div></div><div class="plc-sig-list">' + sigHtml + '</div></div>'
+ '<div class="plc-col"><div class="card-header" style="margin-bottom:8px"><span>🔘 ' + I18n.t('plc_debug.btn_col') + '</span>'
+ '<div class="btn-group">'
+ '<button class="btn btn-sm" onclick="PlcDebugPage._addBtn()">' + I18n.t('plc_debug.add_btn') + '</button>'
+ '</div></div><div class="plc-sig-list">' + btnHtml + '</div></div>'
+ '</div>';
updateDelBtn();
}
function updateDelBtn() {
var el = document.getElementById('plc-del-btn');
if (!el) return;
var n = countChecked(ledChecked) + countChecked(btnChecked);
el.textContent = I18n.t('plc_debug.del_sel') + (n ? ' (' + n + ')' : '');
el.disabled = !n;
}
return {
render: function(c) {
ledChecked = {}; btnChecked = {}; sigChecked = {};
refreshData();
c.innerHTML = '<div class="card">'
+ '<div class="card-header"><span>' + I18n.t('plc_debug.title') + '</span>'
+ '<div class="btn-group">'
+ '<button class="btn btn-sm" onclick="PlcDebugPage._refresh()">🔄 ' + I18n.t('plc_debug.refresh_btn') + '</button>'
+ '<button class="btn btn-danger btn-sm" id="plc-del-btn" disabled onclick="PlcDebugPage._delChecked()">🗑 ' + I18n.t('plc_debug.del_sel') + '</button>'
+ '</div></div>'
+ '<div id="plc-content"></div></div>';
renderAll();
},
_addLed: function() { PlcDebugLeds.push({ saddr: '', desc: '', val: 0 }); renderAll(); },
_addBtn: function() { PlcDebugButtons.push({ saddr: '', desc: '', val: 0 }); renderAll(); },
_bindLed: function(i) { showPicker(function(sa, de) { PlcDebugLeds[i].saddr = sa; PlcDebugLeds[i].desc = de; var f = findSig(sa); if (f) PlcDebugLeds[i].val = f.val; renderAll(); }); },
_toggleBtn: function(j) {
var b = PlcDebugButtons[j];
if (!b.saddr) { showPicker(function(sa, de) { b.saddr = sa; b.desc = de; var f = findSig(sa); if (f) b.val = f.val; renderAll(); }); return; }
var nv = parseFloat(b.val) > 0 ? 0 : 1;
WsClient.send({ curd: 'set', signal_type: 'out', saddr: b.saddr, signal_data: String(nv), setting_zone: '0' });
b.val = nv; renderAll();
},
_toggleLed: function(i, v) { ledChecked[i] = v; updateDelBtn(); },
_toggleBtnChk: function(j, v) { btnChecked[j] = v; updateDelBtn(); },
_toggleSig: function(i, v) { sigChecked[i] = v; },
_moveSel: function(target) {
var picked = [];
for (var k in sigChecked) if (sigChecked.hasOwnProperty(k)) picked.push(parseInt(k));
if (!picked.length) { toast(I18n.t('toast.no_selected'), 'error'); return; }
picked.sort(function(a, b) { return a - b; }).forEach(function(i) {
var s = outSignals[i];
if (!s) return;
if (target === 'led') PlcDebugLeds.push({ saddr: s.saddr, desc: s.desc || '', val: s.val || 0 });
else PlcDebugButtons.push({ saddr: s.saddr, desc: s.desc || '', val: s.val || 0 });
});
sigChecked = {}; renderAll();
},
_refresh: function() { refreshData(); incrRefresh(); },
_delChecked: delChecked,
};
})();