106 lines
4.7 KiB
JavaScript
106 lines
4.7 KiB
JavaScript
/**
|
|
* @file dashboard.js
|
|
* @brief Dashboard 仪表盘页面 — 信号卡片 + 快捷操作 + 连接状态
|
|
*/
|
|
|
|
|
|
Router.register('#dashboard', function(c) {
|
|
var types = [
|
|
{ t: 'out', key: 'nav.out', descKey: 'dash.out.desc', color: 'var(--accent)' },
|
|
{ t: 'in', key: 'nav.in', descKey: 'dash.in.desc', color: 'var(--accent2)' },
|
|
{ t: 'yk', key: 'nav.yk', descKey: 'dash.yk.desc', color: 'var(--warning)' },
|
|
{ t: 'ao', key: 'nav.ao', descKey: 'dash.ao.desc', color: 'var(--success)' },
|
|
{ t: 'param', key: 'nav.param', descKey: 'dash.param.desc', color: '#a78bfa' },
|
|
];
|
|
|
|
var cards = types.map(function(t) {
|
|
var count = SignalStore.get(t.t).length;
|
|
return '<div class="dash-card" onclick="window.location.hash=\'#' + t.t + '\'">'
|
|
+ '<div class="dash-card-icon">' + Icon.get(t.icon === 'out' ? 'signal' : t.icon === 'in' ? 'link' : t.icon === 'yk' ? 'zap' : t.icon === 'ao' ? 'sliders' : 'settings') + '</div>'
|
|
+ '<div class="dash-card-count">' + count + '</div>'
|
|
+ '<div class="dash-card-label">' + I18n.t(t.key) + '</div>'
|
|
+ '<div style="font-size:11px;color:var(--text-muted);margin-top:4px;">' + I18n.t(t.descKey) + '</div>'
|
|
+ '</div>';
|
|
}).join('');
|
|
|
|
c.innerHTML = '<div class="dash-grid">' + cards + '</div>'
|
|
+ '<div class="card" style="margin-top:16px;">'
|
|
+ '<div class="card-header"><span>' + I18n.t('dash.quick_actions') + '</span></div>'
|
|
+ '<div class="btn-group">'
|
|
+ '<button class="btn" onclick="DashActions.exportAll()">' + I18n.t('dash.export_xml') + '</button>'
|
|
+ '<button class="btn" onclick="DashActions.importAll()">' + I18n.t('dash.import_xml') + '</button>'
|
|
+ '<button class="btn btn-sm" onclick="window.location.hash=\'#terminal\'">' + I18n.t('dash.open_terminal') + '</button>'
|
|
+ '<button class="btn btn-sm" onclick="window.location.hash=\'#monitor\'">' + I18n.t('dash.open_monitor') + '</button>'
|
|
+ '</div></div>'
|
|
+ '<div class="card">'
|
|
+ '<div class="card-header"><span>' + I18n.t('dash.connection') + '</span></div>'
|
|
+ '<div style="font-family:var(--font-mono);font-size:12px;color:var(--text-secondary);">'
|
|
+ I18n.t('dash.url') + ': <span style="color:var(--accent);">ws://' + location.hostname + ':8080/ws</span><br>'
|
|
+ I18n.t('dash.status') + ': <span id="dash-status" style="color:var(--offline);">' + I18n.t('status.disconnected') + '</span><br>'
|
|
+ '<span style="color:var(--text-muted);" id="dash-time"></span></div></div>';
|
|
|
|
WsClient.onStatus(function(s) {
|
|
var el = document.getElementById('dash-status');
|
|
if (el)
|
|
{
|
|
el.textContent = I18n.t('status.' + s) || s;
|
|
el.style.color = s === 'connected' ? 'var(--online)' : s === 'connecting' ? 'var(--warning)' : 'var(--offline)';
|
|
}
|
|
});
|
|
});
|
|
|
|
var DashActions = {
|
|
exportAll: function() {
|
|
var types = ['out', 'in', 'yk', 'ao', 'param'];
|
|
var xml = '<?xml version="1.0" encoding="UTF-8"?>\n<root>\n';
|
|
var hasData = false;
|
|
types.forEach(function(st) {
|
|
var data = SignalStore.get(st);
|
|
xml += ' <signals type="' + st + '">\n';
|
|
if (data.length > 0) { hasData = true; data.forEach(function(s, i) { xml += ' <signal no="' + i + '" saddr="' + esc(s.saddr) + '"/>\n'; }); }
|
|
xml += ' </signals>\n';
|
|
});
|
|
xml += '</root>';
|
|
if (!hasData) { toast(I18n.t('toast.no_export'), 'error'); return; }
|
|
var blob = new Blob([xml], { type: 'application/xml' });
|
|
var a = document.createElement('a');
|
|
a.href = URL.createObjectURL(blob);
|
|
a.download = 'rtu_signals_' + Date.now() + '.xml';
|
|
a.click();
|
|
toast(I18n.t('toast.xml_exported'), 'success');
|
|
},
|
|
|
|
importAll: function() {
|
|
var input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.accept = '.xml';
|
|
input.onchange = function(e) {
|
|
var file = e.target.files[0];
|
|
if (!file) return;
|
|
var reader = new FileReader();
|
|
reader.onload = function(ev) {
|
|
var parser = new DOMParser();
|
|
var doc = parser.parseFromString(ev.target.result, 'text/xml');
|
|
var err = doc.querySelector('parsererror');
|
|
if (err) { toast(I18n.t('toast.xml_parse_error') + ': ' + err.textContent, 'error'); return; }
|
|
var nodes = doc.querySelectorAll('signals');
|
|
if (nodes.length === 0) { toast(I18n.t('toast.xml_no_signals'), 'error'); return; }
|
|
var count = 0;
|
|
nodes.forEach(function(el) {
|
|
var type = el.getAttribute('type');
|
|
if (['out', 'in', 'yk', 'ao', 'param'].indexOf(type) === -1) return;
|
|
el.querySelectorAll('signal').forEach(function(sig) {
|
|
var saddr = sig.getAttribute('saddr');
|
|
if (!saddr || !saddr.trim()) return;
|
|
WsClient.send({ saddr: saddr.trim(), signal_type: type, curd: 'add', setting_zone: '0', signal_data: '' });
|
|
count++;
|
|
});
|
|
});
|
|
toast(I18n.t('toast.xml_importing') + ' (' + count + ')', 'success');
|
|
};
|
|
reader.readAsText(file);
|
|
};
|
|
input.click();
|
|
}
|
|
};
|