From eb1027b040261c3b018d7229511442ae1bd728d1 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 15 Jun 2026 09:53:00 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E5=9B=BE=E7=89=87=E7=BC=A9?= =?UTF-8?q?=E6=94=BE=20load=20=E5=8D=B3=E9=87=8F=E5=9F=BA=E5=87=86?= =?UTF-8?q?=E5=B0=BA=E5=AF=B8=20+=20=E5=8F=8C=E5=87=BB=E5=A4=8D=E4=BD=8D?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=20+=20bump=200.12.12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _captureBase:图片加载完即量贴合尺寸做基准,避免首次缩放时还没渲染量到 0px 把图片塌成 0;量不到则本次跳过不破坏。 - 双击复位徽标显式提示「已复位 · 100%」(停留 1.4s)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- PROGRESS.md | 5 +++++ core/__init__.py | 2 +- web/static/js/preview.js | 29 +++++++++++++++++++++-------- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index efe3285..72523c3 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -21,6 +21,11 @@ ## 已完成关键能力 +### 2026-06-15 / 文件预览缩放加固 + 双击复位提示 + +- 图片 load 完即量基准尺寸(`_captureBase`,免首次缩放时还没渲染量到 0px 导致塌成 0);基准未量到时本次缩放跳过不破坏;双击复位时徽标显式提示「已复位 · 100%」(停留 1.4s)。bump 0.12.11 → 0.12.12。 +- 排查提示:左栏底部版本号 = `core/__init__.py __version__`,用户报"缩放完全没动静"且本地 8765 无服务 → 多半是**远端实例未 pull/重启**,版本号对不上即旧代码。 + ### 2026-06-15 / 文件预览缩放改显式 px:修 CSS zoom 放不大 - 接上一条:CSS `zoom` 对带 `max-width/height:100%` 的 flex item 不生效 —— zoom 放大后被百分比 max 约束重新夹回,视觉无变化(用户实测"还是不能放大")。 diff --git a/core/__init__.py b/core/__init__.py index fac00a8..eee5419 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.12.11" +__version__ = "0.12.12" diff --git a/web/static/js/preview.js b/web/static/js/preview.js index ee193da..4a9b2c5 100644 --- a/web/static/js/preview.js +++ b/web/static/js/preview.js @@ -67,7 +67,16 @@ let _fpCurrentRel = null; // 缩放目标用 _zoomState 记录,避免每次预览重复 addEventListener 泄漏。 const _zoomState = new WeakMap(); // bodyEl -> { img, scale, badge, timer } -function _applyZoom(z) { +function _captureBase(z) { + const img = z.img; + if (!z.baseW) { + z.baseW = img.clientWidth || img.naturalWidth || 0; + z.baseH = img.clientHeight || img.naturalHeight || 0; + } + return z.baseW > 0; +} + +function _applyZoom(z, hint) { const img = z.img; if (z.scale === 1) { // 复位:还原 CSS 里的 max-width/height:100% 自适应贴合 @@ -80,20 +89,17 @@ function _applyZoom(z) { // 以 scale=1 时的"贴合显示"尺寸为基准,给显式像素尺寸。 // 不用 CSS zoom:图片是带 max-width/height:100% 的 flex item,zoom 放大后会被 // 百分比 max 约束重新夹回 → 视觉不变;显式 px + max:none 才真正撑大并让 body 出滚动条。 - if (!z.baseW) { - z.baseW = img.clientWidth || img.naturalWidth; - z.baseH = img.clientHeight || img.naturalHeight; - } + if (!_captureBase(z)) return; // 图片还没量到尺寸(未加载完),本次跳过,下次再缩 img.style.maxWidth = "none"; img.style.maxHeight = "none"; img.style.width = Math.round(z.baseW * z.scale) + "px"; img.style.height = Math.round(z.baseH * z.scale) + "px"; img.style.cursor = "zoom-out"; } - z.badge.textContent = Math.round(z.scale * 100) + "%"; + z.badge.textContent = hint || (Math.round(z.scale * 100) + "%"); z.badge.classList.add("show"); if (z.timer) clearTimeout(z.timer); - z.timer = setTimeout(() => z.badge.classList.remove("show"), 1000); + z.timer = setTimeout(() => z.badge.classList.remove("show"), hint ? 1400 : 1000); } // 给某 body 内的图片接上 Ctrl+滚轮缩放(badge + 双击复位),记录到 _zoomState。 @@ -106,7 +112,14 @@ function _makeImageZoomable(bodyEl, img) { const z = { img, scale: 1, badge, timer: null, baseW: 0, baseH: 0 }; _zoomState.set(bodyEl, z); img.style.cursor = "zoom-in"; - img.addEventListener("dblclick", () => { z.scale = 1; _applyZoom(z); }); + // 图片一加载完就量好贴合尺寸做基准,避免首次缩放时还没渲染量到 0 + if (img.complete) _captureBase(z); + else img.addEventListener("load", () => _captureBase(z), { once: true }); + img.addEventListener("dblclick", () => { + if (z.scale === 1) return; // 本来就 100%,无需提示 + z.scale = 1; + _applyZoom(z, "已复位 · 100%"); + }); } function _clearZoom(bodyEl) {