From 4ea777a55a609c7879fee5edcfa4be44db1875d4 Mon Sep 17 00:00:00 2001 From: butubb <1422726308@qq.com> Date: Sun, 13 Sep 2026 21:30:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E6=8A=93=E5=8F=96):=20=E5=85=83=E7=B4=A0?= =?UTF-8?q?=E6=A1=86=E8=B7=9F=E7=9D=80=E5=9B=BE=E7=89=87=E8=B5=B0=EF=BC=88?= =?UTF-8?q?ResizeObserver=EF=BC=89+=20=E7=95=8C=E9=9D=A2=E6=94=BE=E5=A4=A7?= =?UTF-8?q?=20+=20=E5=8F=B3=E4=BE=A7=E5=B1=82=E7=BA=A7/=E5=B1=9E=E6=80=A7/?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E4=B8=89=E9=A1=B5=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户报的准确现象:**点某些元素会让画面变大,但框没跟着变大 → 错位**。 根因:预览图是 `max-width:100%` —— 窗口够宽时恒为原始尺寸(我这儿复现不出), 但窗口窄时图片被压小;此时任何**布局微动**都会改变预览列宽度 → 图片重新缩放, 而框是上一次算好的**像素坐标**,不会跟着变。触发布局微动的正是"点元素": `.el-picker-item.active` 用 `border-left:3px`(占宽度)→ 列表变宽 → 预览变窄 → 图片变小 ✗。实测(视口压到 900):图片从 720 → 363,框仍停在 720 的位置。 修法: 1. **ResizeObserver 盯着图片**:显示尺寸一变就重算所有框 —— 窗口缩放、面板宽度 变化、滚动条出现…都会自动跟上,这是"完全不错位"的兜底保证。 2. 高亮一律改用 **outline / box-shadow**(不占布局)→ 选中元素不再引发任何重排。 3. 弹窗放大到 **98vw × 88vh**(用户要"整个界面大一点"),图片在宽窗口下 1:1。 4. 右侧改成和 uiauto.dev 一样的**三页签**: - **层级**:元素树(原列表,缩进 + 搜索) - **属性**:选中元素的全部属性(建议选择器/class/resource-id/text/desc/package/ clickable/bounds/深度) - **颜色**:鼠标在图上移动实时取**像素色**;选中元素显示**元素中心色** 5. 交互对齐云检查器:**点层级条目 = 选中**(看属性/颜色/高亮,不关窗), 回填改由每行的「✓ 填入」按钮触发。 实测(视口 900 窄窗口):图片 720→363、框同步变 363 ✓;点元素不再错位 ✓; 三页签可用、属性/取色正确(选中元素中心 #f7f7f7)✓;无 JS 报错 ✓。 --- static/admin/editor.js | 103 ++++++++++++++++++++++++++++++++++- templates/admin/monitor.html | 22 ++++++-- 2 files changed, 116 insertions(+), 9 deletions(-) diff --git a/static/admin/editor.js b/static/admin/editor.js index 4929e1b..4e2b058 100644 --- a/static/admin/editor.js +++ b/static/admin/editor.js @@ -839,8 +839,20 @@ var _stepEditor={ ''+ ''+ '
'+ - ''+ - '
加载元素树...
'+ + '
'+ + ''+ + ''+ + ''+ + '
'+ + '
'+ + ''+ + '
加载元素树...
'+ + '
'+ + '
点左侧图上的元素,或点「层级」里的条目看属性
'+ + '
'+ + '
把鼠标移到左侧图上取色;点元素可看该元素的主色
'+ + '
'+ + '
'+ '
'+ ''; document.getElementById('el-picker-footer').innerHTML= @@ -852,6 +864,71 @@ var _stepEditor={ // 一次取齐截图 + 元素树(见 _loadSnapshot 的说明) self._loadSnapshot(); }, + // 右侧页签切换 + _tab:function(btn){ + var pane=btn.dataset.pane; + document.querySelectorAll('#el-picker-body .el-tab').forEach(function(b){ + b.classList.toggle('active', b===btn); + }); + ['tree','attr','color'].forEach(function(p){ + var el=document.getElementById('el-pane-'+p); + if(el)el.classList.toggle('active', p===pane); + }); + }, + // 属性页:选中元素的全部属性 + _renderAttrs:function(i){ + var el=(this._elData||[])[i]; + var box=document.getElementById('el-pane-attr'); + if(!box)return; + if(!el){box.innerHTML='
点左侧图上的元素,或点「层级」里的条目看属性
';return;} + var sg=el.suggested||{}; + var rows=[['建议选择器', sg.value||'(无可靠选择器)'], + ['class', el.class||''], ['resource-id', el.resource_id||''], + ['text', el.text||''], ['content-desc', el.description||''], + ['package', el.package||''], ['clickable', el.clickable||''], + ['bounds', el.bounds||''], ['层级深度', String(el.depth||0)]]; + box.innerHTML=rows.map(function(r){ + return '
'+esc(r[0])+''+esc(r[1]||'-')+'
'; + }).join(''); + }, + // 颜色页:鼠标位置的像素色 + 选中元素的主色 + _pixelAt:function(x,y){ + try{ + var c=this._colorCanvas, img=document.getElementById('el-shot-container').querySelector('img'); + if(!c||!img)return null; + if(c.width!==img.naturalWidth){c.width=img.naturalWidth;c.height=img.naturalHeight;} + var ctx=c.getContext('2d'); + ctx.drawImage(img,0,0); + var d=ctx.getImageData(Math.max(0,Math.min(c.width-1,x)),Math.max(0,Math.min(c.height-1,y)),1,1).data; + return '#'+[d[0],d[1],d[2]].map(function(v){return ('0'+v.toString(16)).slice(-2);}).join(''); + }catch(e){return null;} + }, + _updatePixel:function(x,y){ + var box=document.getElementById('el-color-list'); + if(!box)return; + var col=this._pixelAt(Math.round(x),Math.round(y)); + if(!col)return; + box.innerHTML='
'+ + '光标处 '+esc(col)+'
'+ + (box.dataset.dom||''); + }, + _renderColors:function(i){ + var box=document.getElementById('el-color-list'); + if(!box)return; + var el=(this._elData||[])[i]; + var dom=''; + if(el&&el.bounds){ + var m=el.bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/); + if(m){ + var x=Math.round((+m[1]+ +m[3])/2), y=Math.round((+m[2]+ +m[4])/2); + var c=this._pixelAt(x,y); + if(c) dom='
'+ + '选中元素中心 '+esc(c)+'
'; + } + } + box.dataset.dom=dom; + box.innerHTML=dom||'
把鼠标移到左侧图上取色
'; + }, // 一次取齐:截图 + 元素树(后端用原生 u2 背靠背取) // 为什么不再分两个接口:截图与元素树分两次拿,中间隔着 dump 本身的 1.3~1.8 秒, // 界面只要在动(信息流/视频/加载),框就会落在旧位置上("抓取错位")。 @@ -888,7 +965,17 @@ var _stepEditor={ info.textContent=d.width+'x'+d.height+' · '+self._elData.length+' 个元素 · ' +(d.cost_ms||0)+'ms'+(d.unstable?' · ⚠ 界面在变化':''); } + // 图片显示尺寸一变(窗口缩放、面板宽度变化、滚动条出现…)就重算所有框 —— + // 这是"完全不错位"的保证:框永远跟着图片走,不留旧坐标 + if(window.ResizeObserver){ + if(self._ro)self._ro.disconnect(); + self._ro=new ResizeObserver(function(){ self._renderOverlays(); }); + self._ro.observe(img); + } + self._colorCanvas=document.createElement('canvas'); self._renderOverlays(); + self._renderAttrs(null); + self._renderColors(null); }; img.src=d.image; self._renderEl(self._elData); @@ -952,6 +1039,7 @@ var _stepEditor={ var a=+mb[1],b=+mb[2],c=+mb[3],dd=+mb[4]; if(px>=a&&px<=c&&py>=b&&py<=dd){var ar=(c-a)*(dd-b); if(ar✓ 测选择器' : '') + +'' +''; - return '
'+ + return '
'+ ''+(el.depth||0)+''+ '
'+indent+esc(el.name||el.class||'')+'
'+ '
'+attrs.join('')+'
'+ @@ -1080,7 +1172,12 @@ var _stepEditor={ }); this._renderEl(f); }, + // 点层级条目 = **选中**(看属性/颜色/高亮),不关窗不回填 _pickEl:function(i){ + this._highlightEl(i); + }, + // 「✓ 填入」才回填选择器并关窗 + _fillEl:function(i){ var el=this._elData[i]; if(!el||!this._elTargetInput)return; if(el.suggested&&el.suggested.invalid){ diff --git a/templates/admin/monitor.html b/templates/admin/monitor.html index ee7f4a7..214a6a7 100644 --- a/templates/admin/monitor.html +++ b/templates/admin/monitor.html @@ -298,16 +298,26 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14 .el-shot-container img{max-width:100%;height:auto;border-radius:4px;display:block} .el-shot-container .el-shot-loading{color:#8a93a0;font-size:13px;padding:40px} .el-shot-overlay{position:absolute;border:1px solid rgba(103,232,249,.45);background:transparent;cursor:pointer;border-radius:2px;transition:all .1s} -.el-shot-hover{position:absolute;border:2px solid rgba(248,113,113,.9);background:rgba(248,113,113,.15);pointer-events:none;border-radius:2px;z-index:5} +.el-shot-hover{position:absolute;outline:2px solid rgba(248,113,113,.9);outline-offset:-1px;background:rgba(248,113,113,.15);pointer-events:none;border-radius:2px;z-index:5} .el-shot-warn{background:#fff3cd;border-bottom:1px solid #ffda6a;color:#7a5b00;font-size:12px;padding:6px 10px} .el-shot-overlay:hover{border-color:#67e8f9;background:rgba(103,232,249,.18)} -.el-shot-overlay.active{border:2px solid #f87171;background:rgba(248,113,113,.22)} -.el-list-wrap{flex:0 0 400px;display:flex;flex-direction:column;overflow:hidden;border-left:1px solid var(--border)} +.el-shot-overlay.active{outline:2px solid #f87171;outline-offset:-1px;background:rgba(248,113,113,.22)} +.el-list-wrap{flex:0 0 460px;display:flex;flex-direction:column;overflow:hidden;border-left:1px solid var(--border)} +.el-tabs{display:flex;border-bottom:1px solid var(--border);background:var(--ink-2);flex:none} +.el-tab{padding:7px 14px;font-size:12.5px;color:var(--text-light);cursor:pointer;border:none;background:none;border-bottom:2px solid transparent} +.el-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600} +.el-pane{flex:1;overflow:auto;display:none} +.el-pane.active{display:block} +.el-attr{display:flex;gap:8px;padding:5px 12px;border-bottom:1px solid var(--border);font-size:12px} +.el-attr b{color:var(--text-light);font-weight:500;flex:0 0 96px;font-family:var(--mono)} +.el-attr span{color:var(--text);word-break:break-all;flex:1} +.el-color-row{display:flex;align-items:center;gap:8px;padding:6px 12px;font-size:12px;border-bottom:1px solid var(--border)} +.el-swatch{width:22px;height:22px;border-radius:4px;border:1px solid var(--border);flex:none} .el-picker-search{width:100%;padding:8px 12px;border:none;border-bottom:1px solid var(--border);font-size:13px;outline:none;font-family:var(--body)} .el-picker-list{flex:1;overflow-y:auto} .el-picker-item{padding:8px 12px;border-bottom:1px solid var(--border);cursor:pointer;font-size:12px;display:flex;gap:8px;align-items:flex-start} .el-picker-item:hover{background:var(--primary-soft)} -.el-picker-item.active{background:var(--primary-soft);border-left:3px solid var(--primary)} +.el-picker-item.active{background:var(--primary-soft);box-shadow:inset 3px 0 0 var(--primary)} .el-picker-item:last-child{border-bottom:none} .el-picker-item .ep-depth{color:var(--text-light);font-family:var(--mono);flex-shrink:0;width:28px} .el-picker-item .ep-info{flex:1;min-width:0} @@ -1044,12 +1054,12 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14