/** * @file ui.js * @brief 通用 UI 组件 — Toast, Modal, Table, Icon, Utilities */ /* ================================================================== * SVG 内联图标 (Lucide 风格, 18×18) * ================================================================== */ var Icon = { _map: { monitor: '', dashboard: '', signal: '', link: '', zap: '', sliders: '', settings: '', database: '', bug: '', cpu: '', activity: '', terminal: '', check: '', plus: '', trash: '', edit: '', alert: '', }, get: function(name, cls) { return (this._map[name] || '').replace('class="icon-svg"', 'class="' + (cls || 'icon-svg') + '"'); } }; /* ================================================================== * Toast 通知 * ================================================================== */ function toast(msg, type) { type = type || 'info'; var el = document.createElement('div'); el.className = 'toast toast-' + type; el.textContent = msg; document.getElementById('toast-container').appendChild(el); setTimeout(function() { el.style.opacity = '0'; el.style.transition = 'opacity 0.3s'; }, 2500); setTimeout(function() { el.remove(); }, 2800); } /* ================================================================== * Modal 模态框 * ================================================================== */ function showModal(title, bodyHtml, onOk, okText) { okText = okText || I18n.t('toast.confirm'); var overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = '
' + I18n.t('signal.confirm_del_msg') + ' ' + esc(saddr) + '?
', function(ov) { WsClient.send({ saddr: saddr, signal_type: type, curd: 'del', setting_zone: '0' }); SignalStore.remove(type, saddr); toast(I18n.t('toast.del_sent') + ': ' + saddr, 'info'); ov.remove(); }, Icon.get('trash') + ' ' + I18n.t('signal.delete')); }, batchDel: function(type) { var checked = [].slice.call(document.querySelectorAll('#tbody-' + type + ' input[type=checkbox]:checked')); if (checked.length === 0) { toast(I18n.t('toast.no_selected'), 'error'); return; } showModal(I18n.t('signal.confirm_batch_del'), '' + I18n.t('signal.confirm_batch_msg') + ' ' + checked.length + ' ' + I18n.t('signal.confirm_batch_del') + '?
', function(ov) { checked.forEach(function(cb) { var tr = cb.closest('tr'); var saddr = tr ? tr.getAttribute('data-saddr') : ''; if (saddr) { WsClient.send({ saddr: saddr, signal_type: type, curd: 'del', setting_zone: '0' }); SignalStore.remove(type, saddr); } }); toast(checked.length + ' ' + I18n.t('toast.batch_del_sent'), 'info'); ov.remove(); }, Icon.get('trash') + ' ' + I18n.t('signal.delete') + ' ' + checked.length); }, set: function(type, saddr) { showModal(I18n.t('signal.set_title') + ' \u2014 ' + esc(saddr), '', function(ov) { var val = getModalVal(ov, '#mf-val'); WsClient.send({ saddr: saddr, signal_type: type, curd: 'set', signal_data: val, setting_zone: '0' }); toast(I18n.t('toast.set_sent') + ': ' + saddr + ' = ' + val, 'success'); ov.remove(); }, I18n.t('signal.set')); } }; /* ---- Event delegation: data-action buttons ---- */ document.addEventListener('click', function(e) { var btn = e.target.closest('[data-action]'); if (!btn) return; var action = btn.getAttribute('data-action'); var saddr = btn.getAttribute('data-saddr'); var type = btn.getAttribute('data-page-type'); if (!type) { var tbody = e.target.closest('tbody[data-type]'); if (tbody) type = tbody.getAttribute('data-type'); } if (!type) return; if (action === 'set-go') { e.preventDefault(); var row = btn.closest('tr'); var inp = row ? row.querySelector('.sig-set-input') : null; var val = inp ? inp.value.trim() : ''; if (!val) { toast(I18n.t('toast.val_required'), 'error'); return; } WsClient.send({ saddr: saddr, signal_type: type, curd: 'set', signal_data: val, setting_zone: '0' }); /* 立即更新本地 SignalStore 和 DOM,推送到达前先显示 */ var arr = SignalStore.get(type); for (var k = 0; k < arr.length; k++) { if (arr[k].saddr === saddr) { arr[k].val = val; break; } } /* 直接更新当前行的值单元格 */ var vc = row.querySelector('.cell-val'); if (vc) { vc.textContent = val; vc.classList.add('changed'); setTimeout(function(){vc.classList.remove('changed');},600); } inp.value = ''; } else if (action === 'del') { e.preventDefault(); SignalActions.del(type, saddr); } }); /* Enter key on inline input triggers set */ document.addEventListener('keydown', function(e) { if (e.key === 'Enter' && e.target.classList.contains('sig-set-input')) { e.preventDefault(); var inp = e.target; var row = inp.closest('tr'); var btn = row ? row.querySelector('[data-action="set-go"]') : null; if (btn) btn.click(); } });