资源模块:新建资源时附件区带着上一次的附件(弹窗复用但暂存清单从不复位) #19

Closed
opened 2026-10-28 09:25:47 +00:00 by laoyang · 2 comments
Owner

资源模块:新建资源时附件区带着上一次的附件(弹窗复用但暂存清单没复位)

现象

用户原话:「资源模块,新建资源,会把之前的附件带进来,就是说,附件那里不是空的。」

新建资源的弹窗里,「附件」那块不是空的 —— 显示着上一轮/上一条资源上传过的文件缩略图。

影响面(三种表现,严重程度不同)

因为 #resAddCard 是新建 / 编辑共用同一个弹窗(v42 第 7 条),
而关闭时只清输入框、不清暂存清单,所以:

触发路径 用户看到 实际数据
上传附件后点「取消」/遮罩/Esc 关闭,再点「新建资源」 上一批缩略图还在 保存新资源时,令牌若是旧的则可能被一起提交(见下)
编辑 A(灌入了 A 的附件?见争议点),关掉再新建 可能带着 A 的 需实测
保存成功后弹窗自动关闭,再点「新建资源」 上一批还在 上一次已认领(暂存目录已删)⇒ 只是视觉残留,不会再存一遍

根因(已读码定位)

templates/resources/index.php 的 resetToCreate()(第 654 行)在点「新建资源」时复位:

function resetToCreate() {
  ...清 id / action / 标题文字 / rs-title / rs-content / 底栏按钮...
  setPickerValues('share_ids[]', []);   // ← 共享人清了
}

附件(.stage-list)和它里面的隐藏令牌 <input name="stage_token[]"> 一行没清。

而附件令牌是 assets/js/app.js 的 queueDrop() 在上传成功时动态插进
.stage-cell 里的(第 668-670 行):

hid.name = box.getAttribute('data-stage-field') || 'stage_token[]';
hid.value = d.stoken;
cell.appendChild(hid);

这些节点只在用户点卡片上的「×」时才被移除(第 700 行附近)。
弹窗关闭 / 重新打开都没有任何代码碰它 —— setResOpen(false) 只做 addCard.hidden = true。

所以 DOM 一直留着 ⇒ 下次打开就看见上一次的附件。

为什么"有时候还会真的带上数据"

StagedUpload::claim()(app/StagedUpload.php:170)认领后会 rmDir 暂存目录,
第二次认领时 !is_dir($dir) → continue 跳过。

⇒ 上一批已经被保存认领过的令牌,残留下来只是视觉噪音(提交了也无害);
但没保存就被关掉的那批(用户点了取消 / 按了 Esc),暂存目录还在,
令牌也还在 DOM 里 ⇒ 下次新建资源点确定,这批文件会被认领到新资源上。
用户会看到"新建的资源里莫名多了照片"。

验收标准

  1. 上传附件后关闭弹窗(取消 / 遮罩 / Esc 三条路径都要试),再点「新建资源」⇒ 附件区为空
  2. 保存成功后再点「新建资源」⇒ 附件区为空
  3. 编辑 A 再新建 ⇒ 附件区为空(不带 A 的)
  4. 附件区为空时,不残留 stage_token[] 隐藏字段(否则下次保存会带上旧令牌)
  5. 新建 / 编辑共用一个弹窗这个设计不变(不能靠"分成两个弹窗"绕过去)
资源模块:新建资源时附件区带着上一次的附件(弹窗复用但暂存清单没复位) ## 现象 用户原话:「资源模块,新建资源,会把之前的附件带进来,就是说,附件那里不是空的。」 新建资源的弹窗里,「附件」那块不是空的 —— 显示着上一轮/上一条资源上传过的文件缩略图。 ## 影响面(三种表现,严重程度不同) 因为 `#resAddCard` 是**新建 / 编辑共用同一个弹窗**(v42 第 7 条), 而关闭时只清输入框、不清暂存清单,所以: | 触发路径 | 用户看到 | 实际数据 | |---|---|---| | 上传附件后点「取消」/遮罩/Esc 关闭,再点「新建资源」 | 上一批缩略图还在 | 保存新资源时,**令牌**若是旧的则可能被一起提交(见下) | | 编辑 A(灌入了 A 的附件?见争议点),关掉再新建 | 可能带着 A 的 | 需实测 | | 保存成功后弹窗自动关闭,再点「新建资源」 | 上一批还在 | 上一次已认领(暂存目录已删)⇒ 只是**视觉残留**,不会再存一遍 | ## 根因(已读码定位) `templates/resources/index.php` 的 `resetToCreate()`(第 654 行)在点「新建资源」时复位: ```js function resetToCreate() { ...清 id / action / 标题文字 / rs-title / rs-content / 底栏按钮... setPickerValues('share_ids[]', []); // ← 共享人清了 } ``` **附件(`.stage-list`)和它里面的隐藏令牌 `<input name="stage_token[]">` 一行没清。** 而附件令牌是 `assets/js/app.js` 的 `queueDrop()` 在上传成功时动态插进 `.stage-cell` 里的(第 668-670 行): ```js hid.name = box.getAttribute('data-stage-field') || 'stage_token[]'; hid.value = d.stoken; cell.appendChild(hid); ``` 这些节点**只在用户点卡片上的「×」时才被移除**(第 700 行附近)。 弹窗关闭 / 重新打开**都没有任何代码碰它** —— `setResOpen(false)` 只做 `addCard.hidden = true`。 所以 DOM 一直留着 ⇒ 下次打开就看见上一次的附件。 ## 为什么"有时候还会真的带上数据" `StagedUpload::claim()`(app/StagedUpload.php:170)认领后会 `rmDir` 暂存目录, 第二次认领时 `!is_dir($dir)` → `continue` 跳过。 ⇒ 上一批**已经被保存认领过**的令牌,残留下来只是视觉噪音(提交了也无害); 但**没保存就被关掉**的那批(用户点了取消 / 按了 Esc),暂存目录还在, 令牌也还在 DOM 里 ⇒ 下次新建资源点确定,这批文件会被**认领到新资源上**。 用户会看到"新建的资源里莫名多了照片"。 ## 验收标准 1. 上传附件后关闭弹窗(取消 / 遮罩 / Esc 三条路径都要试),再点「新建资源」⇒ 附件区为空 2. 保存成功后再点「新建资源」⇒ 附件区为空 3. 编辑 A 再新建 ⇒ 附件区为空(不带 A 的) 4. 附件区为空时,**不残留** `stage_token[]` 隐藏字段(否则下次保存会带上旧令牌) 5. 新建 / 编辑共用一个弹窗这个设计**不变**(不能靠"分成两个弹窗"绕过去)
Author
Owner

已修复并部署(commit e733c4f)

根因

#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     ← 用户看到"新建的资源莫名多了张照片"
修复后:新资源 id=5  附件数=0

改法

新增 clearStaged(),做三件事:

  1. 删掉 .stage-list 里所有 .stage-cell 卡片;
  2. 兜底删 input[name="stage_token[]"] —— 用户看不见令牌,但它才是真正会被提交的东西
    (DOM 删了、数据还在是 #456 那类坑);
  3. 把 .stage-list / .stage-tip 藏回 hidden(与模板初始状态一致)。

两处调用:

入口 为什么也要清
resetToCreate() 点「新建资源」时,清掉上一次的
openEdit() openEdit 并不灌附件(已有附件走列表里独立的「附件」弹窗),所以此刻表单里的卡片一定是"上一次没做完的残留"

验证

主判据 _tooling/_b36_res_attach_reset.py(走真实 UI,三条关闭路径 + 数据层)

用例 修复前 修复后
A 上传 → 点「取消」→ 再新建 卡片 1 / 令牌 1 0 / 0
B 上传 → 保存成功(自动关窗)→ 再新建 卡片 1 / 令牌 1 0 / 0
C 上传 → 按 Esc → 再新建 卡片 1 / 令牌 1 0 / 0
D 新建资源的实际附件数 1(带进来了) 0
合计 通过 6 / 失败 7 通过 13 / 失败 0

判"附件区为空"同时看三样:.stage-cell 数量、.stage-list 是否 hidden、
stage_token[] 数量 —— 单看卡片会漏掉"卡片删了但令牌还在"。

判据有牙齿:_tooling/_b36_falsify.py 把 clearStaged() 注入成第一行 return;
(等价修复前行为),锚点唯一性断言通过,判据立刻红回7 条,恢复后回到 13/0。

回归 _tooling/_b36_res_regress.py —— 13 通过 / 0 失败,确认没弄坏既有能力:

[OK] ② 新建+上传+保存,附件真的挂上了(附件数=1)
[OK] ④ 标题灌进来了          [OK] ⑤ 正文灌进来了
[OK] ⑥ id 隐藏框填了 A 的 id
[OK] ⑦ 底栏「共享」「附件」两颗按钮显示出来
[OK] ⑧ 编辑态附件区为空(不灌历史附件)
[OK] ⑩ 编辑 A 后新建:标题清空、id 清空(新建态)
[OK] ⑪ 编辑用的附件面板组件仍在(sd-attach-panel)

顺带查了其它用 _stage_drop.php 的模块(books / calendar / movies / people /
tasks / trips),都没有"共用弹窗 + 复位函数"这套结构 ⇒ 同一个坑不存在。

部署

  • git push → fc81347..e733c4f
  • 线上 md5 与本地一致:95a933e2666684623d56e5f1326a6b1c
  • 属主保持 family、mtime 为部署时刻、php -l 通过、grep -c clearStaged = 4

顺带完成

本地 fb_test 测试数据已清理:资源 3 → 0,对应 3 张测试图片与 4 个暂存孤儿目录
都移到 _tooling/_fb_trash/20261028-res19/(含库表备份),没有直接删。

线上登录态的冒烟按分工留给您。

## 已修复并部署(commit `e733c4f`) ### 根因 `#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 ← 用户看到"新建的资源莫名多了张照片" 修复后:新资源 id=5 附件数=0 ``` ### 改法 新增 `clearStaged()`,做三件事: 1. 删掉 `.stage-list` 里所有 `.stage-cell` 卡片; 2. **兜底删** `input[name="stage_token[]"]` —— 用户看不见令牌,但它才是真正会被提交的东西 (DOM 删了、数据还在是 #456 那类坑); 3. 把 `.stage-list` / `.stage-tip` 藏回 hidden(与模板初始状态一致)。 两处调用: | 入口 | 为什么也要清 | |---|---| | `resetToCreate()` | 点「新建资源」时,清掉上一次的 | | `openEdit()` | openEdit **并不灌附件**(已有附件走列表里独立的「附件」弹窗),所以此刻表单里的卡片一定是"上一次没做完的残留" | ### 验证 **主判据 `_tooling/_b36_res_attach_reset.py`**(走真实 UI,三条关闭路径 + 数据层) | 用例 | 修复前 | 修复后 | |---|---|---| | A 上传 → 点「取消」→ 再新建 | 卡片 1 / 令牌 1 | **0 / 0** | | B 上传 → 保存成功(自动关窗)→ 再新建 | 卡片 1 / 令牌 1 | **0 / 0** | | C 上传 → 按 Esc → 再新建 | 卡片 1 / 令牌 1 | **0 / 0** | | D 新建资源的**实际附件数** | **1**(带进来了) | **0** | | 合计 | 通过 6 / **失败 7** | **通过 13 / 失败 0** | 判"附件区为空"同时看三样:`.stage-cell` 数量、`.stage-list` 是否 hidden、 `stage_token[]` 数量 —— 单看卡片会漏掉"卡片删了但令牌还在"。 **判据有牙齿**:`_tooling/_b36_falsify.py` 把 `clearStaged()` 注入成第一行 `return;` (等价修复前行为),锚点唯一性断言通过,判据立刻红回**7 条**,恢复后回到 13/0。 **回归 `_tooling/_b36_res_regress.py` —— 13 通过 / 0 失败**,确认没弄坏既有能力: ``` [OK] ② 新建+上传+保存,附件真的挂上了(附件数=1) [OK] ④ 标题灌进来了 [OK] ⑤ 正文灌进来了 [OK] ⑥ id 隐藏框填了 A 的 id [OK] ⑦ 底栏「共享」「附件」两颗按钮显示出来 [OK] ⑧ 编辑态附件区为空(不灌历史附件) [OK] ⑩ 编辑 A 后新建:标题清空、id 清空(新建态) [OK] ⑪ 编辑用的附件面板组件仍在(sd-attach-panel) ``` 顺带查了其它用 `_stage_drop.php` 的模块(books / calendar / movies / people / tasks / trips),都没有"共用弹窗 + 复位函数"这套结构 ⇒ 同一个坑不存在。 ### 部署 - `git push` → `fc81347..e733c4f` - 线上 md5 与本地一致:`95a933e2666684623d56e5f1326a6b1c` - 属主保持 `family`、mtime 为部署时刻、`php -l` 通过、`grep -c clearStaged` = 4 ### 顺带完成 本地 `fb_test` 测试数据已清理:资源 3 → 0,对应 3 张测试图片与 4 个暂存孤儿目录 都**移到** `_tooling/_fb_trash/20261028-res19/`(含库表备份),没有直接删。 线上登录态的冒烟按分工留给您。
Author
Owner

修复已提交(e733c4f)并部署到 family.yangyq.net,线上 md5 与本地一致(95a933e...),clearStaged 线上 4 处。

三组验证:主判据从「通过 6 / 失败 7」变成「13 / 0」;注入回 bug 形态立刻红回 7 条(判据有牙齿);回归 13/0 确认编辑/共享/新建带附件主路径都没坏。

最关键的 D1:修复前新建资源会真的带进上次未保存的附件(附件数=1),修复后 =0。

本地测试数据已清理(资源 3→0),3 张图 + 4 个暂存孤儿目录移到 _tooling/_fb_trash/20261028-res19/。

线上登录态冒烟按分工留给用户。关闭。

修复已提交(`e733c4f`)并部署到 family.yangyq.net,线上 md5 与本地一致(`95a933e...`),`clearStaged` 线上 4 处。 三组验证:主判据从「通过 6 / 失败 7」变成「13 / 0」;注入回 bug 形态立刻红回 7 条(判据有牙齿);回归 13/0 确认编辑/共享/新建带附件主路径都没坏。 最关键的 D1:修复前新建资源会真的带进上次未保存的附件(附件数=1),修复后 =0。 本地测试数据已清理(资源 3→0),3 张图 + 4 个暂存孤儿目录移到 `_tooling/_fb_trash/20261028-res19/`。 线上登录态冒烟按分工留给用户。关闭。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: laoyang/FamilyBook#19