pELoad 新增功能模式(FUNC:MODE)远程切换与实时曲线页:切换带白名单校验/下发后立即回读确认(真实反馈驱动显示),模式名规范化兼容设备长格式回读(Fixed/LIST/BATTery/SOLar/CARProfile);新增 /trend 实时曲线页(canvas 绘制 V/I/P 三曲线、窗口 30s~10min、历史按时间范围回填),WebSocket 快照推送提至 5Hz

This commit is contained in:
zjk
2026-09-03 20:48:30 +08:00
parent 91be306a92
commit 237e17ea50
5 changed files with 387 additions and 16 deletions
+52 -2
View File
@@ -104,6 +104,8 @@ td.l,th.l{text-align:left;}
<span id="badgeLink" class="badge err">未连接</span>
<span id="badgeOutp" class="badge err">输出关</span>
<span id="badgeMode" class="badge">模式 -</span>
<span style="flex:1"></span>
<a class="btn" href="/trend" target="_blank">📈 实时曲线</a>
</div>
<div class="steps" id="steps">
@@ -152,6 +154,19 @@ td.l,th.l{text-align:left;}
<div class="errline" id="measDiagErr" style="display:none"></div>
<div class="row"><span class="k">环路优先(回读)</span><span class="v" id="devFunc">-</span></div>
<div class="row"><span class="k">功能模式(回读)</span><span class="v" id="devFuncMode">-</span></div>
<div class="row"><span class="k">切换结果</span><span class="v" id="funcModeFeed">-</span></div>
<div class="sub">功能模式切换(FUNC:MODE)</div>
<div class="inline">
<select id="funcModeSel">
<option value="FIX">FIX 固定输出(实时控制)</option>
<option value="LIST">LIST 列表波形</option>
<option value="BATT">BATT 电池测试</option>
<option value="SOLAR">SOLAR 光伏曲线</option>
<option value="CAR">CAR 汽车波形</option>
</select>
<button id="btnFuncMode" class="btn" onclick="switchFuncMode()">切换功能模式</button>
</div>
<div class="tip" id="funcModeTip">实时功率控制必须保持 <b>FIX</b>。切换到 LIST/BATT/SOLAR/CAR 后,输出将按该模式自主运行,脱离本程序的实时功率控制(页面会红色告警),需切回 FIX 恢复。</div>
</div>
<!--======== 卡3:参数与输出控制 ========-->
@@ -301,6 +316,23 @@ function sendFromInput(action, inputId, label, unit){
if(isNaN(v)){ alert('请输入有效数值'); return; }
sendCmd(action, v, '确认设定'+label+' = '+v+' '+unit+' ?');
}
// 功能模式切换(带确认 + 脱离实时控制警告)
function switchFuncMode(){
const sel = $('funcModeSel');
const m = sel.value;
if(m === 'FIX'){
if(!confirm('确认切换为 FIX(固定输出)?\n将恢复本程序的实时功率控制。')) return;
} else {
if(!confirm('确认切换到 '+m+' 模式?\n\n⚠ 该模式会脱离本程序的实时功率控制,\n输出将按 '+m+' 的模式自主运行!\n请确保已理解风险后再确认。')) return;
}
fetch('/api/eload_cmd?action=func_mode&mode='+m)
.then(r => r.json())
.then(j => {
if(j.ok) statusEl.textContent = '已下发: func_mode '+m;
else alert('下发失败: '+(j.error||'未知错误'));
})
.catch(() => alert('网络错误,指令未下发'));
}
//------------------ 渲染(WebSocket 1Hz 快照,仅按 id 更新,不重建 DOM)------------------
let lastSnap = null;
@@ -382,8 +414,26 @@ function render(s){
mde.textContent = dev.lastMeasErr;
} else mde.style.display = 'none';
$('devFunc').textContent = dev.func || '-';
$('devFuncMode').innerHTML = (dev.funcMode || '-')
+ (dev.funcModeFixWarn ? ' <span class="badge err">非FIX!</span>' : ' <span class="badge ok">FIX</span>');
const fmName = (dev.funcMode || '').trim();
$('devFuncMode').innerHTML = fmName
? '<b style="font-size:15px">' + fmName + '</b>'
+ (dev.funcModeFixWarn ? ' <span class="badge err">非FIX</span>' : ' <span class="badge ok">FIX</span>')
: '<span class="dim">回读中...</span>';
const fmf = $('funcModeFeed');
if(dev.funcModeFeedValid){
const ok = dev.funcModeFeedOk ? '已发送成功' : '发送失败(未连接或超时)';
fmf.textContent = dev.funcModeFeed + ':' + ok + '('+ageText(now - dev.funcModeFeedTime)+')';
fmf.style.color = dev.funcModeFeedOk ? 'var(--ok)' : 'var(--bad)';
} else { fmf.textContent = '-'; fmf.style.color = ''; }
setDisabled('btnFuncMode', !connected);
setDisabled('funcModeSel', !connected);
// 下拉框同步当前设备功能模式(仅当未聚焦时)
const fmsel = $('funcModeSel');
if(document.activeElement !== fmsel && dev.funcMode){
const opts = ['FIX','LIST','BATT','SOLAR','CAR'];
const idx = opts.indexOf(dev.funcMode);
if(idx >= 0 && fmsel.selectedIndex !== idx) fmsel.selectedIndex = idx;
}
//---- 卡3:控制 ----
setDisabled('btnModeCC', !connected || cc);