From e07b339a510c4392ef8336a8f45decef5c3e1227 Mon Sep 17 00:00:00 2001
From: butubb <1422726308@qq.com>
Date: Wed, 19 Aug 2026 14:17:33 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=A7=E5=B1=8F=E5=A2=9E=E5=BC=BA?=
=?UTF-8?q?=E2=80=94=E2=80=94=E4=B8=80=E9=94=AE=E4=BA=AE=E5=B1=8F=E6=8C=89?=
=?UTF-8?q?=E9=92=AE=E3=80=81=E5=8D=A1=E7=89=87=E6=98=BE=E7=A4=BA=E8=AE=BE?=
=?UTF-8?q?=E5=A4=87=E5=90=8D+IP+=E7=86=84=E5=B1=8F=E7=8A=B6=E6=80=81?=
=?UTF-8?q?=EF=BC=88thumb=20=E5=93=8D=E5=BA=94=E5=A4=B4=20X-Screen-State?=
=?UTF-8?q?=EF=BC=89=E3=80=81=E7=82=B9=E5=87=BB=E5=8D=A1=E7=89=87=E8=BF=9B?=
=?UTF-8?q?=E5=85=A5=E8=AE=BE=E5=A4=87=E6=93=8D=E4=BD=9C=E5=BC=B9=E7=AA=97?=
=?UTF-8?q?=EF=BC=88=E5=AE=9E=E6=97=B6=E6=B5=81+=E8=A7=A6=E6=8E=A7+?=
=?UTF-8?q?=E6=8C=89=E9=94=AE+=E6=96=87=E5=AD=97=EF=BC=89=EF=BC=8C?=
=?UTF-8?q?=E5=B7=A5=E5=85=B7=E9=A1=B5=E8=BF=9C=E7=A8=8B=E7=9C=8B=E5=B1=8F?=
=?UTF-8?q?=E5=AD=90=E5=88=86=E6=A0=8F=E7=A7=BB=E9=99=A4=E5=B9=B6=E5=85=A5?=
=?UTF-8?q?=E5=A4=A7=E5=B1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
static/admin/base.js | 1 -
static/admin/tools.js | 121 ---------------
templates/admin/monitor.html | 36 -----
templates/admin/wall.html | 289 ++++++++++++++++++++++++++++++-----
web_server.py | 21 ++-
5 files changed, 269 insertions(+), 199 deletions(-)
diff --git a/static/admin/base.js b/static/admin/base.js
index 7e5d654..841db89 100644
--- a/static/admin/base.js
+++ b/static/admin/base.js
@@ -112,7 +112,6 @@ function showSubTab(tabId, name){
const tab = document.getElementById('tab-'+tabId);
tab.querySelectorAll('.sub-tab').forEach(b=>b.classList.toggle('active', b.dataset.sub===name));
tab.querySelectorAll('.sub-panel').forEach(p=>p.classList.toggle('active', p.id===tabId+'-sub-'+name));
- if(name==='screen' && typeof loadScreenDevices==='function') loadScreenDevices();
if(name==='devpool' && typeof loadDevPool==='function') loadDevPool();
}
diff --git a/static/admin/tools.js b/static/admin/tools.js
index 236cd04..376278c 100644
--- a/static/admin/tools.js
+++ b/static/admin/tools.js
@@ -277,127 +277,6 @@ function copyTsKey(){
}
-// ================== Tab: 工具 - 远程看屏(MJPEG 流 + u2 触控) ==================
-let _screenViewOn = false;
-
-async function loadScreenDevices(force){
- const sel=document.getElementById('screen-device');
- if(!sel)return;
- const list=await _fetchAdbDevices(force);
- if(list===null)return;
- const cur=sel.value;
- const stateTxt={device:'在线',offline:'离线',pool:'设备池(未连接)'};
- sel.innerHTML=list.map(d=>
- ''
- ).join('');
- if(cur)sel.value=cur;
-}
-
-function _screenSerial(){
- const sel=document.getElementById('screen-device');
- return sel?sel.value:'';
-}
-
-function startScreenView(){
- const serial=_screenSerial();
- if(!serial){showToast('请先选择设备','error');return;}
- stopScreenView();
- const img=document.getElementById('screen-img');
- const loading=document.getElementById('screen-loading');
- loading.style.display='flex';
- document.getElementById('screen-status').textContent='';
- img.src='/api/screen/stream?serial='+encodeURIComponent(serial);
- img.onload=()=>{loading.style.display='none';_screenViewOn=true;document.getElementById('screen-status').textContent='观看中';};
- img.onerror=()=>{loading.style.display='none';_screenViewOn=false;showToast('画面流中断(设备离线或已停止)','error');};
-}
-
-function stopScreenView(){
- const img=document.getElementById('screen-img');
- if(img)img.src='';
- _screenViewOn=false;
- const loading=document.getElementById('screen-loading');
- if(loading)loading.style.display='none';
- const st=document.getElementById('screen-status');
- if(st)st.textContent='';
-}
-
-// 显示尺寸 -> 设备原生分辨率(img.naturalWidth/Height 即设备分辨率)
-function _screenCoords(ev){
- const img=document.getElementById('screen-img');
- if(!img.naturalWidth)return [null,null];
- const r=img.getBoundingClientRect();
- return [
- Math.round((ev.clientX-r.left)/r.width*img.naturalWidth),
- Math.round((ev.clientY-r.top)/r.height*img.naturalHeight),
- ];
-}
-
-function _screenTap(x,y){
- const serial=_screenSerial();
- if(!serial||x==null||y==null)return;
- apiPost('/api/screen/tap',{serial,x,y}).then(r=>{if(r&&!r.ok)showToast(r.error,'error');});
-}
-
-function _screenSwipe(x1,y1,x2,y2){
- const serial=_screenSerial();
- if(!serial||x1==null)return;
- apiPost('/api/screen/swipe',{serial,x1,y1,x2,y2}).then(r=>{if(r&&!r.ok)showToast(r.error,'error');});
-}
-
-function screenKey(key){
- const serial=_screenSerial();
- if(!serial){showToast('请先选择设备','error');return;}
- apiPost('/api/screen/key',{serial,key}).then(r=>{if(r&&!r.ok)showToast(r.error,'error');});
-}
-
-function screenSendText(){
- const serial=_screenSerial();
- const inp=document.getElementById('screen-text');
- const text=(inp.value||'').trim();
- if(!serial){showToast('请先选择设备','error');return;}
- if(!text){showToast('请输入文字','error');return;}
- apiPost('/api/screen/text',{serial,text}).then(r=>{
- if(r&&!r.ok)showToast(r.error,'error');
- else inp.value='';
- });
-}
-
-// 鼠标 + 触控:拖拽=滑动,点按=轻触
-(function(){
- const img=document.getElementById('screen-img');
- if(!img)return;
- let dragging=false, sx=0, sy=0;
-
- function onDown(ev){
- if(!_screenViewOn)return;
- ev.preventDefault();
- dragging=true;
- const [x,y]=_screenCoords(ev);
- sx=x; sy=y;
- }
- function onUp(ev){
- if(!dragging)return;
- dragging=false;
- const [x,y]=_screenCoords(ev);
- if(x==null)return;
- if(Math.abs(x-sx)<8 && Math.abs(y-sy)<8){
- _screenTap(x,y); // 位移小 → 点击
- }else{
- _screenSwipe(sx,sy,x,y); // 有位移 → 滑动
- }
- }
-
- img.addEventListener('mousedown',onDown);
- img.addEventListener('mousemove',ev=>{if(dragging)ev.preventDefault();});
- window.addEventListener('mouseup',onUp);
- img.addEventListener('touchstart',onDown,{passive:false});
- img.addEventListener('touchend',ev=>{
- if(!dragging)return;
- const t=ev.changedTouches[0];
- onUp({clientX:t.clientX,clientY:t.clientY,preventDefault(){}});
- },{passive:false});
-})();
-
// ================== Tab: 工具 - 设备池管理(本地 SQLite 清单) ==================
async function loadDevPool(){
const tb=document.getElementById('tb-devpool');
diff --git a/templates/admin/monitor.html b/templates/admin/monitor.html
index 2e397db..6aa09d3 100644
--- a/templates/admin/monitor.html
+++ b/templates/admin/monitor.html
@@ -494,7 +494,6 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14
-
@@ -674,41 +673,6 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14
-
-
diff --git a/templates/admin/wall.html b/templates/admin/wall.html
index d29f377..f9d37fa 100644
--- a/templates/admin/wall.html
+++ b/templates/admin/wall.html
@@ -21,62 +21,91 @@
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{background:var(--bg);font-family:var(--body);color:var(--text);overflow:hidden}
-/* 背景网格纹理 */
body::before{content:"";position:fixed;inset:0;pointer-events:none;
background-image:linear-gradient(rgba(45,212,191,.035) 1px,transparent 1px),
linear-gradient(90deg,rgba(45,212,191,.035) 1px,transparent 1px);
background-size:42px 42px}
/* 顶部栏 */
-header{display:flex;align-items:center;gap:28px;padding:16px 26px 12px;border-bottom:1px solid var(--ink-line);background:rgba(13,15,19,.85);backdrop-filter:blur(6px);position:relative;z-index:5}
-header .title{font-family:var(--display);font-size:22px;font-weight:800;letter-spacing:1px;color:#fff;display:flex;align-items:center;gap:12px}
+header{display:flex;align-items:center;gap:24px;padding:14px 26px 12px;border-bottom:1px solid var(--ink-line);background:rgba(13,15,19,.85);backdrop-filter:blur(6px);position:relative;z-index:5}
+header .title{font-family:var(--display);font-size:21px;font-weight:800;letter-spacing:1px;color:#fff;display:flex;align-items:center;gap:12px;white-space:nowrap}
header .title::before{content:"";width:10px;height:10px;border-radius:50%;background:var(--teal);box-shadow:0 0 0 0 rgba(45,212,191,.55);animation:pulse 2.2s infinite}
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(45,212,191,.5)}70%{box-shadow:0 0 0 9px rgba(45,212,191,0)}100%{box-shadow:0 0 0 0 rgba(45,212,191,0)}}
header .stats{display:flex;gap:10px;flex:1}
-header .stat{background:var(--card);border:1px solid var(--card-line);border-radius:10px;padding:7px 18px;text-align:center;min-width:86px}
-header .stat .n{font-family:var(--mono);font-size:21px;font-weight:600;line-height:1.15}
+header .stat{background:var(--card);border:1px solid var(--card-line);border-radius:10px;padding:6px 16px;text-align:center;min-width:84px}
+header .stat .n{font-family:var(--mono);font-size:20px;font-weight:600;line-height:1.15}
header .stat .l{font-size:11px;color:var(--faint);margin-top:2px;letter-spacing:.4px}
header .stat.online .n{color:var(--green)}
header .stat.running .n{color:var(--teal)}
header .stat.error .n{color:var(--red)}
-header .clock{font-family:var(--mono);font-size:19px;color:var(--muted);text-align:right;line-height:1.2}
+header .wall-btn{background:var(--card);border:1px solid var(--card-line);color:var(--teal);border-radius:10px;padding:8px 18px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;font-family:var(--body)}
+header .wall-btn:hover{background:#1a2029;border-color:var(--teal);transform:translateY(-1px)}
+header .wall-btn:disabled{opacity:.5;cursor:not-allowed}
+header .clock{font-family:var(--mono);font-size:18px;color:var(--muted);text-align:right;line-height:1.2;white-space:nowrap}
header .clock .date{font-size:11px;color:var(--faint)}
/* 设备网格 */
-main{position:relative;z-index:2;height:calc(100vh - 86px);overflow-y:auto;padding:18px 24px 30px;display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:16px;align-content:start}
-/* 设备卡片 */
-.card{background:var(--card);border:1px solid var(--card-line);border-radius:14px;overflow:hidden;position:relative;animation:cardIn .4s ease-out backwards;transition:border-color .2s,box-shadow .2s}
+main{position:relative;z-index:2;height:calc(100vh - 88px);overflow-y:auto;padding:16px 24px 30px;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px;align-content:start}
+/* 设备卡片(可点击进操作) */
+.card{background:var(--card);border:1px solid var(--card-line);border-radius:14px;overflow:hidden;position:relative;animation:cardIn .4s ease-out backwards;cursor:pointer;transition:border-color .2s,box-shadow .2s,transform .15s}
+.card:hover{transform:translateY(-3px);box-shadow:0 8px 22px rgba(0,0,0,.4)}
@keyframes cardIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
-.card.online{border-color:#263142}
.card.running{border-color:rgba(45,212,191,.55);box-shadow:0 0 18px rgba(45,212,191,.12),inset 0 0 0 1px rgba(45,212,191,.2)}
.card.error{border-color:rgba(248,113,113,.5)}
.card.offline{opacity:.62}
.card .thumb{aspect-ratio:9/16;background:#0a0c10;position:relative;overflow:hidden}
.card .thumb img{width:100%;height:100%;object-fit:cover;display:block;transition:opacity .2s}
.card.offline .thumb img{display:none}
-.card .thumb .ph{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--faint);font-size:12px;background:repeating-linear-gradient(45deg,#0c0e12,#0c0e12 12px,#0f1218 12px,#0f1218 24px)}
+.card .thumb .ph{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--faint);font-size:12px;background:repeating-linear-gradient(45deg,#0c0e12,#0c0e12 12px,#0f1218 12px,#0f1218 24px)}
.card .thumb .ph .ico{font-size:26px;opacity:.6}
/* 状态角标 */
-.card .badge{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:6px;background:rgba(13,15,19,.78);backdrop-filter:blur(4px);border-radius:99px;padding:3px 10px;font-size:11px;font-weight:600;color:var(--muted);border:1px solid var(--ink-line)}
+.card .badge{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:6px;background:rgba(13,15,19,.8);backdrop-filter:blur(4px);border-radius:99px;padding:3px 10px;font-size:11px;font-weight:600;color:var(--muted);border:1px solid var(--ink-line)}
.card.running .badge{color:var(--teal);border-color:rgba(45,212,191,.4)}
.card.error .badge{color:var(--red)}
.card .badge .dot{width:7px;height:7px;border-radius:50%;background:var(--faint)}
-.card.online .badge .dot{background:var(--green);box-shadow:0 0 6px rgba(52,211,153,.7)}
.card.running .badge .dot{background:var(--teal);box-shadow:0 0 7px rgba(45,212,191,.8);animation:pulse 1.6s infinite}
.card.error .badge .dot{background:var(--red);box-shadow:0 0 6px rgba(248,113,113,.7)}
.card.offline .badge .dot{background:var(--faint)}
+/* 熄屏角标(右上) */
+.card .screen-tag{position:absolute;top:8px;right:8px;background:rgba(13,15,19,.8);backdrop-filter:blur(4px);border-radius:99px;padding:3px 10px;font-size:11px;font-weight:600;border:1px solid var(--ink-line)}
+.card .screen-tag.on{color:var(--green)}
+.card .screen-tag.off{color:var(--faint)}
/* 信息区 */
-.card .info{padding:10px 12px 12px}
-.card .info .serial{font-family:var(--mono);font-size:13px;font-weight:600;color:#eef2f7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
-.card .info .model{font-size:11px;color:var(--faint);margin-top:2px}
+.card .info{padding:9px 12px 11px}
+.card .info .name{font-size:13.5px;font-weight:700;color:#f1f4f8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.card .info .ip{font-family:var(--mono);font-size:12px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.card .info .model{font-size:10.5px;color:var(--faint);margin-top:2px}
.card .info .action{font-size:11.5px;color:var(--muted);margin-top:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:16px}
.card .info .action b{color:var(--amber);font-weight:600}
-.card .info .fg{margin-top:4px}
-.card .info .fg .tag{font-size:10.5px;background:#1c2430;color:#9fb3c8;border:1px solid var(--ink-line);border-radius:5px;padding:1px 7px;display:inline-block}
-/* 进度条 */
.card .info .prog{margin-top:7px;height:4px;background:#1d242e;border-radius:99px;overflow:hidden}
.card .info .prog i{display:block;height:100%;background:linear-gradient(90deg,var(--teal),#5eead4);border-radius:99px;transition:width .5s}
.card .info .prog-txt{font-size:10px;color:var(--faint);margin-top:3px;font-family:var(--mono)}
+.card .info .hint{font-size:10px;color:var(--faint);margin-top:5px;text-align:center;opacity:0;transition:opacity .2s}
+.card:hover .info .hint{opacity:.8}
/* 空状态 */
.empty-wall{grid-column:1/-1;text-align:center;color:var(--faint);padding:80px 0;font-size:14px}
+/* 设备操作弹窗 */
+.ctrl-overlay{display:none;position:fixed;inset:0;background:rgba(5,7,10,.78);backdrop-filter:blur(3px);z-index:100;justify-content:center;align-items:center;padding:26px}
+.ctrl-overlay.show{display:flex}
+.ctrl-box{background:var(--card);border:1px solid var(--card-line);border-radius:16px;width:min(420px,94vw);max-height:94vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 70px rgba(0,0,0,.6);animation:modalIn .18s ease-out}
+@keyframes modalIn{from{opacity:0;transform:translateY(12px) scale(.98)}to{opacity:1;transform:none}}
+.ctrl-head{padding:12px 16px;border-bottom:1px solid var(--ink-line);display:flex;align-items:center;gap:12px;background:#11151c}
+.ctrl-head .info{flex:1;min-width:0}
+.ctrl-head .name{font-size:15px;font-weight:700;color:#f1f4f8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.ctrl-head .sub{font-family:var(--mono);font-size:11.5px;color:var(--faint);margin-top:1px}
+.ctrl-head .close{background:none;border:none;color:var(--faint);font-size:22px;cursor:pointer;padding:0 6px;line-height:1;border-radius:6px}
+.ctrl-head .close:hover{color:#fff;background:#1d242e}
+.ctrl-body{flex:1;overflow-y:auto;padding:12px;background:#0a0c10}
+.ctrl-body .screen{position:relative;border-radius:10px;overflow:hidden;background:#000;cursor:crosshair;user-select:none;touch-action:none}
+.ctrl-body .screen img{width:100%;display:block;max-height:62vh;object-fit:contain;background:#000}
+.ctrl-body .screen .loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.55);color:var(--muted);font-size:12px;border-radius:10px}
+.ctrl-tools{display:flex;gap:6px;flex-wrap:wrap;padding:10px 12px 12px;border-top:1px solid var(--ink-line);background:#11151c}
+.ctrl-tools .btn{background:#1a2029;border:1px solid var(--card-line);color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer;transition:all .12s;font-family:var(--body)}
+.ctrl-tools .btn:hover{color:var(--teal);border-color:var(--teal)}
+.ctrl-tools .btn.wake{color:var(--green);border-color:rgba(52,211,153,.4)}
+.ctrl-tools .btn.wake:hover{border-color:var(--green)}
+.ctrl-tools .btn.sleep{color:var(--faint)}
+.ctrl-tools .text-row{display:flex;gap:6px;flex:1 0 100%;margin-top:2px}
+.ctrl-tools .text-row input{flex:1;background:#0d1117;border:1px solid var(--card-line);border-radius:8px;padding:6px 10px;font-size:12px;color:var(--text);outline:none;font-family:var(--body)}
+.ctrl-tools .text-row input:focus{border-color:var(--teal)}
/* 滚动条 */
::-webkit-scrollbar{width:8px}
::-webkit-scrollbar-thumb{background:#232936;border-radius:4px}
@@ -87,19 +116,60 @@ main{position:relative;z-index:2;height:calc(100vh - 86px);overflow-y:auto;paddi
+
+
+
+
+
+
+
![]()
+
连接中(首次需推送 atx-agent,最长约 30 秒)...
+
+
+
+
+
+