216 lines
10 KiB
JavaScript
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,
|
|
};
|
|
})();
|