From e733c4f05711eace9c6e4c63fdf2d8c3392b6402 Mon Sep 17 00:00:00 2001 From: laoyang Date: Wed, 28 Oct 2026 17:50:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(resources):=20=E6=96=B0=E5=BB=BA=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E6=97=B6=E5=B8=A6=E7=9D=80=E4=B8=8A=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=E7=9A=84=E9=99=84=E4=BB=B6=EF=BC=88=E6=9A=82=E5=AD=98=E6=B8=85?= =?UTF-8?q?=E5=8D=95=E4=BB=8E=E4=B8=8D=E5=A4=8D=E4=BD=8D=EF=BC=89=EF=BC=88?= =?UTF-8?q?#19=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户原话:「资源模块,新建资源,会把之前的附件带进来,就是说,附件那里不是空的。」 ## 根因 `#resAddCard` 是新建/编辑**共用**同一个弹窗(v42 第 7 条), 复位函数 `resetToCreate()` 清了标题/正文/归属人/共享人,**唯独漏了附件**: - 缩略图卡片与隐藏令牌 `stage_token[]` 是 queueDrop() 上传成功后 **动态插进 `.stage-list`** 的(assets/js/app.js:668),只有点卡片上的「×」才移除; - 关弹窗只做 `addCard.hidden = true`,DOM 原样留着 ⇒ 下次打开就看见上一次的附件。 ## 不只是"看着脏" 上传后**没保存就关掉**的那批,暂存目录还在(没被认领),令牌残留在表单里 ⇒ 下次点「确定」时被 `StagedUpload::claim()` 认领到**新资源**上。 实测(`_tooling/_b36_res_attach_reset.py` 的 D1): 修复前新建的资源 id=3 附件数 **=1** —— 用户会看到"新建的资源莫名多了张照片"。 修复后同一路径附件数 **=0**。 ## 改法 新增 `clearStaged()`:清 `.stage-cell` 卡片 + 兜底清 `input[name="stage_token[]"]` + 把 `.stage-list` 藏回 hidden(与模板初始状态一致)。 两处调用: - `resetToCreate()` —— 点「新建资源」时 - `openEdit()` —— 点「编辑」时(openEdit 并不灌附件,已有附件走列表里独立的 「附件」弹窗,所以此刻表单里的卡片一定是"上一次没做完的残留") ⚠️ 兜底清令牌这步不能省:用户看不见令牌,但它才是真正会被提交的东西 (DOM 删了、数据还在 —— #456 同族)。 ## 验证 `_tooling/_b36_res_attach_reset.py`(走真实 UI,三条关闭路径 A/B/C + 数据层 D): | | 修复前 | 修复后 | |---|---|---| | A 取消后再新建(卡片/令牌) | 1 / 1 | **0 / 0** | | B 保存成功后再新建 | 1 / 1 | **0 / 0** | | C Esc 关闭后再新建 | 1 / 1 | **0 / 0** | | D 新建资源的实际附件数 | **1**(带进来了) | **0** | | 合计 | 通过 6 / **失败 7** | **通过 13 / 失败 0** | **判据有牙齿**:`_tooling/_b36_falsify.py` 把 `clearStaged()` 注入成第一行 `return;` (等价修复前),判据立刻红回 7 条,恢复后 13/0。锚点命中数断言 =1。 `_tooling/_b36_res_regress.py` —— 回归 13/0,确认没弄坏既有能力: 编辑仍能灌入标题/正文/id、底栏「共享」「附件」两颗按钮仍显示、 「新建+上传+保存」附件照旧真的挂上(附件数=1)、 列表里独立的附件面板组件(`sd-attach-panel`)仍在。 顺带查了其它用 `_stage_drop.php` 的模块(books/calendar/movies/people/tasks/trips), 都没有"共用弹窗 + 复位"这套结构,同一个坑不存在。 工单 #19 --- templates/resources/index.php | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/templates/resources/index.php b/templates/resources/index.php index c47058e..1d7a32f 100644 --- a/templates/resources/index.php +++ b/templates/resources/index.php @@ -532,6 +532,47 @@ $myPerson = (int)\App\Auth::personId(); 会丢掉滚动位置;既然列表页已经有 `#resListHost` 这样的容器就顺手接上。 */ var addBtn = document.getElementById('resAddBtn'); var addCard = document.getElementById('resAddCard'); + + /* 🔴 工单 #19(用户 2026-10-28):「资源模块,新建资源,会把之前的附件带进来, + 就是说,附件那里不是空的。」 + 【根因】`#resAddCard` 是新建/编辑**共用**同一个弹窗(v42 第 7 条), + 但附件区**从来没被复位过**: + · 缩略图卡片与隐藏令牌 `stage_token[]` 是 queueDrop() 上传成功后 + **动态插进 `.stage-list`** 的(assets/js/app.js:668), + 只有点卡片上那个「×」才会移除; + · 关弹窗只做 `addCard.hidden = true`,DOM 原样留着 ⇒ 下次打开就看见上一次的附件。 + ⇒ 复位函数 `resetToCreate()` 清了标题/正文/归属人/共享人,**唯独漏了附件**。 + + ⚠️ 这不只是"看着脏":上传后**没保存就关掉**的那批,暂存目录还在(没被认领), + 令牌残留在表单里 ⇒ 下次点「确定」时会被 `StagedUpload::claim()` 认领到 + **新资源**上 —— 实测「新建的资源莫名多了 1 张照片」 + (见 `_tooling/_b36_res_attach_reset.py` 的 D1)。 + ⚠️ 编辑态**也要清**:`openEdit()` 并不灌附件(已有附件走列表里独立的「附件」弹窗), + 所以编辑时看到的任何附件都是"上一次没做完的残留" ⇒ 保存时会被挂到这条资源上。 + ⚠️ 为什么"只清 DOM"就够了:卡片在**拖入时**就已经 append 进 `.stage-list` + (app.js:600,同步执行),上传完成的回调是在那个**已脱离文档**的节点上 + 改样式、塞令牌,不会再回到表单里 ⇒ 不需要额外加"代/epoch"计数器。 + ⚠️ 除了卡片,还要兜底清 `input[name="stage_token[]"]`: + 用户看不见令牌,但它才是真正会被提交的东西(#456 同族:DOM 删了、数据还在)。 */ + function clearStaged() { + var form = document.getElementById('resForm'); + if (!form) { return; } + var list = form.querySelector('[data-stage-list]'); + if (list) { + Array.prototype.forEach.call(list.querySelectorAll('.stage-cell'), function (c) { + if (c.parentNode) { c.parentNode.removeChild(c); } + }); + // 一张不剩就藏回去(模板初始就 hidden,别留一条空边框) + list.hidden = true; + } + Array.prototype.forEach.call(form.querySelectorAll('input[name="stage_token[]"]'), + function (i) { + if (i.parentNode) { i.parentNode.removeChild(i); } + }); + var tip = form.querySelector('[data-stage-tip]'); + if (tip) { tip.textContent = ''; tip.hidden = true; } + } + function setResOpen(on) { if (!addCard) { return; } addCard.hidden = !on; @@ -611,6 +652,10 @@ $myPerson = (int)\App\Auth::personId(); ids = arr.map(function (o) { return o.id; }); } catch (e) { ids = []; } setPickerValues('share_ids[]', ids); + /* 🔴 工单 #19:编辑态**也要清**暂存区 —— openEdit 并不灌附件(已有附件走 + 列表里独立的「附件」弹窗,见下面的 openPane),所以此刻表单里若还有卡片/令牌, + 那一定是"上一次拖进来没保存"的残留 ⇒ 不清的话会被挂到**这条资源**上。 */ + clearStaged(); setResOpen(true); /* 🔴 v42:编辑态才显示底栏的「共享」「附件」两颗按钮(新建时资源还不存在, 没有"共享给谁/挂哪些附件"可说)。 */ @@ -666,6 +711,8 @@ $myPerson = (int)\App\Auth::personId(); if (el) { el.hidden = true; el.removeAttribute('data-res-id'); } }); setPickerValues('share_ids[]', []); + /* 🔴 工单 #19:附件也要清 —— 否则新建时带着上一次的附件,见 clearStaged 的注释 */ + clearStaged(); } /* 事件委托:列表里的「编辑」按钮(AJAX 换过列表后仍然有效) */