From 888824ba850ace68a44381d42fa0979c0ab5d132 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 15 Jun 2026 10:00:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E5=9B=BE=E7=89=87=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E6=94=BE=E5=A4=A7=E5=90=8E=E5=B7=A6=E9=94=AE=E6=8B=96?= =?UTF-8?q?=E5=8A=A8=E5=B9=B3=E7=A7=BB=20+=20=E5=85=89=E6=A0=87=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E6=94=B9=E6=AD=A3=20+=20bump=200.12.13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 光标:100% 改普通箭头(原 zoom-in 放大镜误导,左键不缩放);放大后 grab、 拖动中 grabbing。 - 左键拖动平移:放大态 mousedown 记起点+滚动位,mousemove 改 body scrollLeft/Top 平移;img.draggable=false 关原生拖拽。document move/up 监听存 z._onMove/_onUp,_clearZoom 时移除避免泄漏。 Co-Authored-By: Claude Opus 4.8 (1M context) --- PROGRESS.md | 5 +++++ core/__init__.py | 2 +- web/static/js/preview.js | 31 ++++++++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index 72523c3..902aacf 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -21,6 +21,11 @@ ## 已完成关键能力 +### 2026-06-15 / 图片预览:左键拖动平移 + 光标语义改正 + +- 光标:100% 时改回普通箭头(原 `zoom-in` 放大镜误导 —— 左键不缩放,缩放是 Ctrl+滚轮);放大后改 `grab`、拖动中 `grabbing`,贴合"可拖"语义。 +- 左键拖动平移:放大态下 mousedown 记起点 + body 滚动位,mousemove 改 `bodyEl.scrollLeft/Top` 平移看局部(替代拖滚动条);`img.draggable=false` 关原生 ghost 拖拽。document 上的 move/up 监听存 `z._onMove/_onUp`,`_clearZoom` 时移除避免泄漏。bump 0.12.12 → 0.12.13。 + ### 2026-06-15 / 文件预览缩放加固 + 双击复位提示 - 图片 load 完即量基准尺寸(`_captureBase`,免首次缩放时还没渲染量到 0px 导致塌成 0);基准未量到时本次缩放跳过不破坏;双击复位时徽标显式提示「已复位 · 100%」(停留 1.4s)。bump 0.12.11 → 0.12.12。 diff --git a/core/__init__.py b/core/__init__.py index eee5419..bec3845 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,3 @@ # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # 改版本只动这一行。 -__version__ = "0.12.12" +__version__ = "0.12.13" diff --git a/web/static/js/preview.js b/web/static/js/preview.js index 4a9b2c5..fe5bef9 100644 --- a/web/static/js/preview.js +++ b/web/static/js/preview.js @@ -84,7 +84,7 @@ function _applyZoom(z, hint) { img.style.maxHeight = ""; img.style.width = ""; img.style.height = ""; - img.style.cursor = "zoom-in"; + img.style.cursor = ""; // 100%:普通箭头(左键不缩放,放大镜会误导) } else { // 以 scale=1 时的"贴合显示"尺寸为基准,给显式像素尺寸。 // 不用 CSS zoom:图片是带 max-width/height:100% 的 flex item,zoom 放大后会被 @@ -94,7 +94,7 @@ function _applyZoom(z, hint) { 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"; + img.style.cursor = "grab"; // 放大后可左键拖动平移 } z.badge.textContent = hint || (Math.round(z.scale * 100) + "%"); z.badge.classList.add("show"); @@ -111,7 +111,7 @@ function _makeImageZoomable(bodyEl, img) { card.appendChild(badge); const z = { img, scale: 1, badge, timer: null, baseW: 0, baseH: 0 }; _zoomState.set(bodyEl, z); - img.style.cursor = "zoom-in"; + img.draggable = false; // 关掉浏览器原生图片拖拽(否则拖出 ghost image) // 图片一加载完就量好贴合尺寸做基准,避免首次缩放时还没渲染量到 0 if (img.complete) _captureBase(z); else img.addEventListener("load", () => _captureBase(z), { once: true }); @@ -120,12 +120,37 @@ function _makeImageZoomable(bodyEl, img) { z.scale = 1; _applyZoom(z, "已复位 · 100%"); }); + + // 放大后左键拖动平移:本质是改 body 的滚动位置(图片此时已撑大、body 可滚) + let dragging = false, sx = 0, sy = 0, sl = 0, st = 0; + img.addEventListener("mousedown", (e) => { + if (e.button !== 0 || z.scale === 1) return; // 仅左键、仅放大态 + e.preventDefault(); + dragging = true; + sx = e.clientX; sy = e.clientY; + sl = bodyEl.scrollLeft; st = bodyEl.scrollTop; + img.style.cursor = "grabbing"; + }); + z._onMove = (e) => { + if (!dragging) return; + bodyEl.scrollLeft = sl - (e.clientX - sx); + bodyEl.scrollTop = st - (e.clientY - sy); + }; + z._onUp = () => { + if (!dragging) return; + dragging = false; + img.style.cursor = z.scale === 1 ? "" : "grab"; + }; + document.addEventListener("mousemove", z._onMove); + document.addEventListener("mouseup", z._onUp); } function _clearZoom(bodyEl) { const z = _zoomState.get(bodyEl); if (!z) return; if (z.timer) clearTimeout(z.timer); + if (z._onMove) document.removeEventListener("mousemove", z._onMove); + if (z._onUp) document.removeEventListener("mouseup", z._onUp); z.badge.remove(); _zoomState.delete(bodyEl); }