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

58 lines
3.4 KiB
Markdown

---
id: T-692
title: 商品套图原图拖放重复项修复
phase: 7
deps: [T-691]
status: TODO
created: 2026-07-23
---
## 问题 / 背景
T-691 为使参考图可以拖到第一位,给每个 `QListWidgetItem` 添加了 `Qt.ItemIsDropEnabled` 并继续调用 `QListWidget.InternalMove` 的默认 `dropEvent`。在图标列表中,拖到已有图片上会变成“投放到该项”的模型插入/复制语义,而非单纯的行移动;同一商品原图 ID 因此短暂出现两次。
`ProductOriginalList` 随后把重复的 `asset_ids()` 传给 `image_studio.reorder_original_assets()`。服务层的唯一性校验正确阻止了 SQLite 写入,显示“商品原图排序不能包含重复图片”,但用户无法完成主图调整。
## 方案
### 1. 受控单项内部移动
- 真实原图项恢复为可选择、可勾选、可拖动,不再用 `ItemIsDropEnabled` 把“拖到图片上”交给 Qt 默认复制式模型处理。
- `ProductOriginalList` 自行管理内部拖拽源:开始拖拽时记录当前原图项及其 asset ID;拖放时只接受来源为当前列表、且排序未锁定的单项移动。
- 根据 drop 坐标和目标缩略图中心计算插入行;使用 `takeItem()` + `insertItem()` 移动同一个 `QListWidgetItem`,不调用 Qt 默认 `super().dropEvent()` 的内部复制路径。
- 只在移动后最终 `asset_ids` 与移动前不同、ID 数量与集合数量一致时发出一次 `orderChanged`;异常、无效位置、跨列表、锁定状态或重复 ID 一律不写库。
### 2. 既有行为保持
- 外部文件 URL 拖入继续发送 `filesDropped` 并导入图片;剪贴板、勾选、预览、删除、悬停删除和主图样式不变。
- 生成中、拉取 URL 或图片下载未完成时继续拒绝内部排序;完成后可重新拖放。
- 成功排序仍复用 `reorder_originals()` 与 SQLite 单事务服务,刷新后首项显示“主图”;服务层的重复 ID 校验保留为最后防线。
### 3. 自动回归
- 为列表内部移动增加无需系统原生拖放平台的 Qt 单元回归:第二项移动到首项前后,列表顺序唯一且只发一次 `orderChanged`。
- 覆盖向下移动、原地 drop、无拖拽源、锁定状态、外部 URL 拖入和重复 ID 防御;项目级回归验证最终排序同步到 SQLite,首项更新为主图。
## 验收要点
- 将任意参考图拖到第一张位置后,列表仅保留每张图片一次,参考图成为“主图”,不再出现重复图片错误。
- 向其它位置拖动同样只移动原项,不复制项;原地释放不写库、不提示成功。
- `orderChanged` 只携带唯一 asset 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、图片文件、已生成 job 的主图/参考图快照、提示词、cmhub、Shopee CDP 或①至⑤模块。
- 不放宽 `reorder_original_assets()` 的重复 ID 校验,不改变外部文件导入来源和原图上限。
- 不实现跨商品、跨任务或多项拖放;本任务只处理当前列表单张原图的可靠排序。
## 执行记录
- 待实现。