fix(web): 图片缩放 load 即量基准尺寸 + 双击复位提示 + bump 0.12.12

- _captureBase:图片加载完即量贴合尺寸做基准,避免首次缩放时还没渲染量到
  0px 把图片塌成 0;量不到则本次跳过不破坏。
- 双击复位徽标显式提示「已复位 · 100%」(停留 1.4s)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-06-15 09:53:00 +08:00
parent 12171a4bdf
commit eb1027b040
3 changed files with 27 additions and 9 deletions

View File

@ -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 约束重新夹回,视觉无变化(用户实测"还是不能放大")。

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。
__version__ = "0.12.11"
__version__ = "0.12.12"

View File

@ -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) {