Files
cmshoppe/docs/tasks/T-693.md
T

66 lines
5.5 KiB
Markdown
Raw Normal View History

---
id: T-693
title: 商品套图连续拖放丢失显示修复
phase: 7
deps: [T-692]
status: DONE
created: 2026-07-23
---
## 问题 / 背景
T-692 已避免 Qt 默认内部投放复制同一图片项,但用户连续把参考1、参考2、参考3、参考4等图片拖到第一位时,商品原图列表仍可能突然看不到“参考1”,后续拖放也不能稳定写入排序。
现场 SQLite 检查确认相关项目的原图记录、状态和本地文件路径仍完整,未执行删除;异常属于 GUI 列表状态失配。当前 `ProductOriginalList._move_dragged_item_to()` 在 `dropEvent()` 尚未退出时同步发送 `orderChanged`,槽函数 `ProductSuiteTab.reorder_originals()` 随即写库并调用 `_refresh_originals()`,通过 `clear()` 删除全部旧 `QListWidgetItem` 后重新创建。此时 Qt 的 `QDrag` 仍持有本轮拖拽源和模型状态,连续拖动会让旧拖拽对象与新列表项发生重入,出现缩略图临时消失、标签缺位或后续排序未提交。
“主图 / 参考1 / 参考2”仍是当前位置标签,不是图片永久名称;每次排序后应连续重编号。只要原图不少于两张,界面刷新后必须存在“参考1”。
## 方案
### 1. 拖拽结束后再提交顺序
- `startDrag()` 开始前保存唯一的拖拽项和排序快照,并清空上一轮待提交状态。
- `dropEvent()` 只用 T-692 的受控 `takeItem()` + `insertItem()` 完成当前列表内移动;不在该事件内发送 `orderChanged`,也不触发列表刷新。
- 移动成功后仅保存待提交的唯一 asset ID 顺序;原地释放、无效目标、跨列表、锁定状态或重复 ID 不建立待提交顺序。
- 等 `super().startDrag()` 返回,确认 Qt 拖拽事务完全结束后,再清理拖拽上下文并发送一次 `orderChanged`。不使用固定毫秒延时猜测拖拽完成。
### 2. 保存、失败恢复与显示一致性
- `ProductSuiteTab.reorder_originals()` 继续复用 `image_studio.reorder_original_assets()` 的 SQLite 单事务写入和唯一性校验;成功或失败后的完整刷新发生在拖拽事务之外。
- 写库成功后按数据库顺序重建列表,并连续显示“主图、参考1、参考2……”;保留勾选状态、图片 ID 和缩略图。
- 写库失败时重新读取数据库恢复原顺序并显示中文错误,不保留仅存在于 GUI 的临时顺序。
- 不删除、复制或新增原图资产,不修改本地文件;现有删除确认、生成/拉取锁定及历史 job 快照保持不变。
### 3. 回归测试
- 扩展 `ProductOriginalList` Qt 测试,验证 `dropEvent()` 阶段只记录待提交顺序,不发送 `orderChanged`;拖拽结束后只发送一次。
- 连续模拟把不同参考图移到第一位,接收排序后重建列表,验证每轮图片数量不变、asset ID 唯一且集合不变,标签始终连续包含“主图、参考1……”;最终顺序与 SQLite 一致。
- 覆盖原地释放、重复 ID、无拖拽源和被锁定时不提交;外部文件 URL 拖入仍只走 `filesDropped`。
## 验收要点
- 对同一商品连续把参考1、参考2、参考3、参考4拖到第一位,每次均只改变顺序,不丢失或复制任何缩略图。
- 原图不少于两张时,每轮刷新后都显示“参考1”,其余参考图按位置连续编号。
- 每次有效拖拽只写库一次;无效拖拽不写库、不显示成功。
- 切换商品或重启程序后,SQLite 中原图数量、图片 ID 集合和最终顺序与界面一致。
- 自动验证:
- `py -3.10 -m unittest tests.test_product_suite_gui tests.test_image_studio tests.test_product_suite`
- `py -3.10 -m unittest discover -s tests`
- `py -3.10 -m ruff check app tests main.py`
- `py -3.10 -m compileall app main.py`
- `git diff --check`
## 边界(不改什么)
- 不改 SQLite schema、图片文件、原图删除规则、生成任务、提示词、cmhub、蝦皮 CDP 或①至⑤模块。
- 不改变“主图 / 参考N”按当前位置动态编号的产品语义,不给图片增加永久参考编号。
- 不放宽服务层对重复 ID、缺失 ID 和跨项目 ID 的校验,不实现跨列表或多项拖放。
## 执行记录
- 2026-07-23 完成。
- `ProductOriginalList` 新增单轮拖拽生命周期状态:开始时冻结原图 ID 集合,`dropEvent()` 仅以受控移动更新列表并保存待提交顺序,无效投放直接拒绝;`super().startDrag()` 返回后先清除旧拖拽项引用,再发送一次 `orderChanged`。因此页面写库和 `_refresh_originals()` 清空重建列表时,Qt 已不再持有上一轮拖拽源。
- 提交前同时校验当前列表、待提交顺序和拖拽开始快照的数量、唯一性与 ID 集合;原地释放、无拖拽源、重复 ID、列表中途变化和排序锁定均不提交。SQLite 服务层的完整集合与重复 ID 校验保持不变。
- 扩展列表级测试,确认移动发生时不会提前发信号,拖拽结束才发送一次;新增项目级连续换主图回归,依次把4张参考图移到第一位,每轮确认5张原图全部保留、标签连续、数据库在结束拖拽前不变且结束后与界面一致。同步更新 `docs/04-architecture.md` 与 `docs/routes.md`。
- 验证通过:`py -3.10 -m unittest tests.test_product_suite_gui tests.test_image_studio tests.test_product_suite`(109项)、`py -3.10 -m unittest discover -s tests`(676项)、`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`。离屏 PySide6 仍有字体目录和插件能力告警,但无测试失败。