refactor: web_server 模块化拆分(web/ 蓝图包 auth/monitor/tasks/admin/tools/devices/apks/tailscale + context/common,web_server 1796→193 行仅留装配与启动)+ 大屏优化(网格 5s 轮询、弹窗独占缩略图、流支持 q/fps)+ 三修复(MIUI 屏幕状态检测改 dumpsys power、补 ADB_PATH 导入修复一键亮屏、renderGrid 增量渲染不重载缩略图)
This commit is contained in:
+73
-34
@@ -239,50 +239,89 @@ function fmtProgress(p){
|
||||
}
|
||||
function shortIp(serial){return String(serial||'').replace(/:5555$/,'');}
|
||||
|
||||
function cardHtml(d, i){
|
||||
const cls=cardCls(d);
|
||||
const serial=esc(d.serial);
|
||||
const name=esc(d.device_name||d.model||'设备');
|
||||
const ip=esc(shortIp(d.serial));
|
||||
const st=badgeText(d);
|
||||
const stTag=_screenState[d.serial]==='off'?'<div class="screen-tag off">🌙</div>':'';
|
||||
const action=d.current_action
|
||||
?'<div class="action">'+(d.task_job?'<b>'+esc(d.task_job)+'</b> · ':'')+esc(d.current_action)+'</div>'
|
||||
:(d.task_job?'<div class="action"><b>'+esc(d.task_job)+'</b></div>':'<div class="action"></div>');
|
||||
return '<div class="card '+cls+'" data-serial="'+serial+'" style="animation-delay:'+(i*30)+'ms" onclick="openCtrl(\''+serial+'\')">'+
|
||||
'<div class="thumb">'+
|
||||
'<img data-thumb="'+serial+'" alt="">'+
|
||||
'<div class="ph"><span class="ico">📱</span><span>'+serial+'</span></div>'+
|
||||
'<div class="badge"><span class="dot"></span>'+st+'</div>'+stTag+
|
||||
'</div>'+
|
||||
'<div class="info">'+
|
||||
'<div class="name">'+name+'</div>'+
|
||||
'<div class="ip">'+ip+'</div>'+
|
||||
'<div class="model">'+esc(d.model||'')+'</div>'+
|
||||
action+fmtProgress(d.progress)+
|
||||
'<div class="hint">点击进入设备操作</div>'+
|
||||
'</div>'+
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// 增量渲染:已有卡片只更新信息区(不重建 img,缩略图不闪不重载);增删设备才动 DOM
|
||||
function renderGrid(devs){
|
||||
const grid=document.getElementById('wall-grid');
|
||||
if(!devs.length){
|
||||
grid.innerHTML='<div class="empty-wall">设备池为空,请到「工具 → 设备池管理」添加设备</div>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML=devs.map((d,i)=>{
|
||||
const cls=cardCls(d);
|
||||
const serial=esc(d.serial);
|
||||
const name=esc(d.device_name||d.model||'设备');
|
||||
const ip=esc(shortIp(d.serial));
|
||||
const st=badgeText(d);
|
||||
const stTag=_screenState[d.serial]==='off'?'<div class="screen-tag off">🌙</div>':'';
|
||||
const action=d.current_action
|
||||
?'<div class="action">'+(d.task_job?'<b>'+esc(d.task_job)+'</b> · ':'')+esc(d.current_action)+'</div>'
|
||||
:(d.task_job?'<div class="action"><b>'+esc(d.task_job)+'</b></div>':'<div class="action"></div>');
|
||||
return '<div class="card '+cls+'" data-serial="'+serial+'" style="animation-delay:'+(i*30)+'ms" onclick="openCtrl(\''+serial+'\')">'+
|
||||
'<div class="thumb">'+
|
||||
'<img data-thumb="'+serial+'" alt="">'+
|
||||
'<div class="ph"><span class="ico">📱</span><span>'+serial+'</span></div>'+
|
||||
'<div class="badge"><span class="dot"></span>'+st+'</div>'+stTag+
|
||||
'</div>'+
|
||||
'<div class="info">'+
|
||||
'<div class="name">'+name+'</div>'+
|
||||
'<div class="ip">'+ip+'</div>'+
|
||||
'<div class="model">'+esc(d.model||'')+'</div>'+
|
||||
action+fmtProgress(d.progress)+
|
||||
'<div class="hint">点击进入设备操作</div>'+
|
||||
'</div>'+
|
||||
'</div>';
|
||||
}).join('');
|
||||
// 离线卡片显示占位
|
||||
grid.querySelectorAll('.card').forEach(c=>{
|
||||
const d=devs.find(x=>x.serial===c.dataset.serial);
|
||||
const img=c.querySelector('img[data-thumb]');
|
||||
const ph=c.querySelector('.ph');
|
||||
if(d&&d.present&&img){img.style.display='block';ph.style.display='none';}
|
||||
else if(ph){ph.style.display='flex';}
|
||||
const existing={};
|
||||
grid.querySelectorAll('.card').forEach(c=>{existing[c.dataset.serial]=c;});
|
||||
// 1. 移除已不存在的卡片
|
||||
Object.keys(existing).forEach(s=>{
|
||||
if(!devs.find(d=>d.serial===s))existing[s].remove();
|
||||
});
|
||||
// 2. 更新/新建
|
||||
devs.forEach((d,i)=>{
|
||||
const serial=d.serial;
|
||||
const card=existing[serial];
|
||||
if(card){
|
||||
// 已有卡片:只更新状态类信息(badge/class/信息区),不动 img
|
||||
card.className='card '+cardCls(d);
|
||||
const badge=card.querySelector('.badge');
|
||||
const bt=badgeText(d);
|
||||
if(badge&&badge.textContent!==bt)badge.innerHTML='<span class="dot"></span>'+bt;
|
||||
// 熄屏角标
|
||||
const tag=card.querySelector('.screen-tag');
|
||||
if(_screenState[serial]==='off'&&!tag)card.querySelector('.thumb').insertAdjacentHTML('beforeend','<div class="screen-tag off">🌙</div>');
|
||||
else if(_screenState[serial]!=='off'&&tag)tag.remove();
|
||||
// 信息区
|
||||
card.querySelector('.name').textContent=d.device_name||d.model||'设备';
|
||||
card.querySelector('.ip').textContent=shortIp(serial);
|
||||
card.querySelector('.model').textContent=d.model||'';
|
||||
const action=d.current_action
|
||||
?(d.task_job?'<b>'+esc(d.task_job)+'</b> · ':'')+esc(d.current_action)
|
||||
:(d.task_job?'<b>'+esc(d.task_job)+'</b>':'');
|
||||
card.querySelector('.action').innerHTML=action;
|
||||
// 进度
|
||||
const progWrap=card.querySelector('.prog-wrap');
|
||||
if(progWrap)progWrap.innerHTML=fmtProgress(d.progress);
|
||||
else if(d.progress&&d.progress.total)card.querySelector('.info').insertAdjacentHTML('beforeend','<div class="prog-wrap">'+fmtProgress(d.progress)+'</div>');
|
||||
// 离线占位切换
|
||||
const img=card.querySelector('img[data-thumb]');
|
||||
const ph=card.querySelector('.ph');
|
||||
if(d.present){if(img)img.style.display='block';if(ph)ph.style.display='none';}
|
||||
else{if(img)img.style.display='none';if(ph)ph.style.display='flex';}
|
||||
}else{
|
||||
grid.insertAdjacentHTML('beforeend',cardHtml(d,i));
|
||||
const nc=grid.lastElementChild;
|
||||
if(!d.present)nc.querySelector('.ph').style.display='flex';
|
||||
}
|
||||
});
|
||||
refreshThumbs();
|
||||
}
|
||||
|
||||
// 缩略图轮询(2.5s):fetch 读取 X-Screen-State 头 → blob 喂 img
|
||||
async function refreshThumbs(){
|
||||
// 进入设备操作弹窗后:停止全部缩略图获取,资源全部给当前设备的高清流
|
||||
if(_ctrlSerial)return;
|
||||
if(document.visibilityState!=='visible')return;
|
||||
const now=Date.now();
|
||||
const imgs=[...document.querySelectorAll('img[data-thumb]')];
|
||||
@@ -343,7 +382,7 @@ function closeCtrl(){
|
||||
function startCtrlStream(){
|
||||
if(!_ctrlSerial)return;
|
||||
const img=document.getElementById('ctrl-img');
|
||||
img.src='/api/screen/stream?serial='+encodeURIComponent(_ctrlSerial)+'&t='+Date.now();
|
||||
img.src='/api/screen/stream?serial='+encodeURIComponent(_ctrlSerial)+'&q=85&fps=10&t='+Date.now(); // 弹窗高清高帧
|
||||
}
|
||||
function updateCtrlHead(){
|
||||
const d=_devices.find(x=>x.serial===_ctrlSerial);
|
||||
@@ -414,7 +453,7 @@ function ctrlSendText(){
|
||||
})();
|
||||
|
||||
// 定时器
|
||||
setInterval(()=>{if(document.visibilityState==='visible')refreshThumbs();},2500);
|
||||
setInterval(()=>{if(document.visibilityState==='visible')refreshThumbs();},5000); // 网格低帧率(省资源)
|
||||
setInterval(()=>{if(document.visibilityState==='visible')refreshStatus();},5000);
|
||||
|
||||
// 初始化
|
||||
|
||||
Reference in New Issue
Block a user