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

63 lines
4.8 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-692
title: 商品套图原图拖放重复项修复
phase: 7
deps: [T-691]
status: DONE
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 校验,不改变外部文件导入来源和原图上限。
- 不实现跨商品、跨任务或多项拖放;本任务只处理当前列表单张原图的可靠排序。
## 执行记录
- 2026-07-23 完成。
- 移除 T-691 对真实原图项追加的 `ItemIsDropEnabled`,避免 Qt 将拖到缩略图上的动作走默认复制/插入路径。`ProductOriginalList` 现在在 `startDrag()` 记录单一拖拽项,在内部 `dragEnter/dragMove/drop` 中只接受本列表来源的移动。
- 内部 drop 不再调用 `super().dropEvent()`:按目标缩略图和坐标计算插入行,以 `takeItem()` + `insertItem()` 移动原 `QListWidgetItem`;移动前后均检查 asset ID 唯一性,仅顺序实际变化时发送一次 `orderChanged`。原地释放、无拖拽源、跨列表和已存在异常重复 ID 都不回写排序。
- 外部文件 URL 拖入继续只发送 `filesDropped`;拉取或生成中的锁定逻辑、SQLite 服务层重复 ID 最后防线、主图刷新样式和历史 job 快照均未改动。已同步 `docs/04-architecture.md` 与 `docs/routes.md`。
- 新增 GUI 回归覆盖第二张移到第一张、向后移动、原地释放、无拖拽源和已有重复 ID 防御;现有项目级测试继续验证 SQLite 排序、主图标签和外部文件导入隔离。
- 验证通过:`py -3.10 -m unittest tests.test_product_suite_gui tests.test_image_studio tests.test_product_suite`(108项)、`py -3.10 -m unittest discover -s tests`(675项)、`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`git diff --check`。离屏 PySide6 有字体目录/插件告警,但无测试失败。