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

66 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 仍有字体目录和插件能力告警,但无测试失败。