/** * @file datacenter.js * @brief RTU_ALL_AI 数据中心 — 搜索过滤 + 复选框 + 批量添加到配置页 */ Router.register('#datacenter', function(c) { DcPage.render(c); }); var DcPage = (function() { var allData = {}; var activeTab = 'out'; var searchText = ''; var selectedSet = {}; var MAX_SIGNALS = 500; function getVisibleData() { var data = allData[activeTab] || []; if (!searchText) return data; var st = searchText.toLowerCase(); return data.filter(function(x) { return (x.saddr && x.saddr.toLowerCase().indexOf(st) !== -1) || (x.desc && x.desc.toLowerCase().indexOf(st) !== -1); }); } function getSelectedCount() { var c = 0; for (var k in selectedSet) { if (selectedSet.hasOwnProperty(k)) c++; } return c; } function updateSelBar() { var b = document.getElementById('dc-sel-bar'); var l = document.getElementById('dc-sel-label'); if (!b || !l) return; var n = getSelectedCount(); b.style.display = n > 0 ? 'flex' : 'none'; l.textContent = I18n.t('dc.sel_count').replace('{n}', n); } function renderTabs() { var types = ['out', 'in', 'yk', 'ao', 'param']; var labels = { out: '注册 (out)', in: '链接注册 (in)', yk: '遥控 (yk)', ao: '参数 (ao)', param: '定值 (param)' }; var el = document.getElementById('dc-tab-bar'); if (!el) return; var h = ''; types.forEach(function(t) { h += ''; }); el.innerHTML = h; } function doAddSelected() { var visible = getVisibleData(), items = []; visible.forEach(function(x) { if (selectedSet[x.saddr]) items.push(x); }); if (items.length === 0) { toast(I18n.t('dc.no_selected'), 'error'); return; } var existing = SignalStore.get(activeTab); if (existing.length + items.length > MAX_SIGNALS) { toast('MAX ' + MAX_SIGNALS + ' exceeded', 'error'); return; } if (!confirm(I18n.t('dc.confirm_add').replace('{n}', items.length) + ' -> ' + activeTab.toUpperCase())) return; var ok = 0; items.forEach(function(x) { WsClient.send({ saddr: x.saddr, signal_type: activeTab, curd: 'add', setting_zone: '0', signal_data: '' }); ok++; }); toast('Added ' + ok + ' to ' + activeTab.toUpperCase(), 'success'); selectedSet = {}; DcPage._renderTable(); updateSelBar(); } function renderTable() { var data = getVisibleData(); var tbody = document.getElementById('dc-tbody'); var cntEl = document.getElementById('dc-count'); if (!tbody) return; if (cntEl) cntEl.textContent = data.length + ' / ' + (allData[activeTab] || []).length; var allChk = data.length > 0 && data.every(function(x) { return !!selectedSet[x.saddr]; }); var thEl = document.getElementById('dc-thead'); if (thEl) thEl.innerHTML = '' + 'idsaddrdescvaltypectrllink'; if (data.length === 0) { tbody.innerHTML = '' + I18n.t('dc.no_signal') + ''; return; } var h = ''; data.forEach(function(x) { var chk = !!selectedSet[x.saddr]; h += '' + '' + fmtVal(x.id) + '' + '' + esc(x.saddr) + '' + '' + esc(x.desc || '') + '' + '' + fmtVal(x.val) + '' + '' + esc(x.type || '') + '' + '' + (x.ctrl_type == 1 ? 'SBO' : 'Direct') + '' + '' + esc(x.link_saddrs || '') + ''; }); tbody.innerHTML = h; } function parseDcOutput(text) { if (!text || text.length < 10) return; var types = ['out', 'in', 'yk', 'ao', 'param'], currentType = ''; var lines = text.split('\n'); lines.forEach(function(line) { types.forEach(function(t) { if (line.indexOf(t + ' signals:') >= 0) currentType = t; }); if (!currentType) return; if (line.indexOf('---') >= 0 || line.indexOf('saddr') >= 0 || line.indexOf(' id ') >= 0) return; var trimmed = line.trim(); if (!trimmed) return; var parts = trimmed.split(/\s+/); if (parts.length < 4) return; var item = {}; if (currentType === 'param') { item = { id: parseInt(parts[0]), saddr: parts[1], desc: parts[2], type: parts[3] || '', ctrl_type: parseInt(parts[4]) || 0 }; } else if (currentType === 'in') { item = { id: parseInt(parts[0]), saddr: parts[1], desc: parts[2], link_saddrs: parts[3] || '', type: parts[4] || '', val: parts[5] || '' }; } else { item = { id: parseInt(parts[0]), saddr: parts[1], desc: parts[2], type: parts[3] || '', val: parts[4] || '' }; } allData[currentType] = allData[currentType] || []; allData[currentType].push(item); }); DcPage._renderTable(); toast(I18n.t('dc.refreshed'), 'success'); } function refreshAll() { allData = { out: [], in: [], yk: [], ao: [], param: [] }; toast(I18n.t('dc.querying'), 'info'); var buf = '', timer = null, done = false; var handler = function(data, binary) { if (binary) { buf += new TextDecoder().decode(binary); clearTimeout(timer); timer = setTimeout(function() { if (done) return; done = true; WsClient.msgCbs = WsClient.msgCbs.filter(function(cb) { return cb !== handler; }); parseDcOutput(buf); }, 600); } }; WsClient.msgCbs.unshift(handler); WsClient.send({ type: 'cmd', data: 'datacenter all' }); /* 兜底超时, 但只在 600ms 定时器未触发时生效 */ setTimeout(function() { if (buf && !done) { done = true; WsClient.msgCbs = WsClient.msgCbs.filter(function(cb) { return cb !== handler; }); parseDcOutput(buf); } }, 6000); } return { render: function(container) { allData = {}; activeTab = 'out'; searchText = ''; selectedSet = {}; container.innerHTML = '
' + '
' + I18n.t('dc.title') + '' + '
' + '' + '
' + '
' + '
' + '' + '0' + '
' + '' + '
' + '' + '' + '
'; renderTabs(); renderTable(); }, refreshAll: refreshAll, switchTab: function(t) { activeTab = t; searchText = ''; selectedSet = {}; var inp = document.getElementById('dc-search'); if (inp) inp.value = ''; renderTabs(); DcPage._renderTable(); updateSelBar(); }, _renderTable: renderTable, _search: function() { var inp = document.getElementById('dc-search'); searchText = inp ? inp.value : ''; renderTable(); }, _toggleAll: function() { var cb = document.getElementById('dc-cb-all'); var allChk = cb ? cb.checked : false; var v = getVisibleData(); if (allChk) { v.forEach(function(x) { selectedSet[x.saddr] = true; }); } else { v.forEach(function(x) { delete selectedSet[x.saddr]; }); } renderTable(); updateSelBar(); }, _toggleRow: function(saddr) { if (selectedSet[saddr]) { delete selectedSet[saddr]; } else { selectedSet[saddr] = true; } renderTable(); updateSelBar(); }, _addSelected: doAddSelected }; })();