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

64 lines
4.9 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-691
title: 商品套图原图真实拖放排序修复
phase: 7
deps: [T-688]
status: DONE
created: 2026-07-23
---
## 问题 / 背景
商品套图的商品原图列表已显示“拖动到第一位即可设为主图”,但用户无法把参考图拖到第一张位置。列表开启了 `QListWidget.InternalMove`,真实原图项却只具备拖拽源标记,没有 `Qt.ItemIsDropEnabled`;拖到已有第一张图片时不是合法的内部放置目标,Qt 不会触发可用的排序结果和 `orderChanged` 回写。列表默认 drop action 仍为复制,也没有明确表达内部排序必须移动。
此前 T-688 的测试只直接调用 `reorder_originals()`,验证了 SQLite 排序持久化,未覆盖实际 Qt 拖放事件,因此没有发现界面手势失效。
拉取蝦皮主图尚未完全结束(含后台下载)或正在生成时,排序仍应锁定,这是既有并发保护,不属于本 bug;界面必须继续给出明确原因。
## 方案
### 1. 修复真实内部拖放条件
- 创建真实商品原图项时,显式保留可选中、可勾选、可拖动并增加 `Qt.ItemIsDropEnabled`,使任意原图都能作为内部排序的放置目标。
- 原图列表明确将内部 drop action 设为 `Qt.MoveAction`;外部本地文件 URL 拖入仍沿用既有导入路径,不能被内部排序逻辑误处理。
- 内部 drop 只在 Qt 已接受移动后发出 `orderChanged`,避免无效 drop、锁定状态或外部拖入触发错误的数据库排序。
### 2. 保留安全边界与用户反馈
- 生成中、拉取 URL 或拉取图片下载尚未完成时继续禁用原图内部排序;完成后自动恢复可拖放状态。
- 排序成功继续复用 `reorder_originals()` 的单事务服务写入和“已更新商品原图顺序,第一张图片为主图”提示;第一项的主图文字、粗体和语义色按刷新后的顺序更新。
- 不改历史 job 的 `source_asset_id/reference_asset_ids` 快照;排序只影响之后新建的生成轮次。
### 3. 自动回归
- 增加 Qt 级回归,构造原图列表并模拟已接受的内部移动结果,验证第二张变为第一张时 `orderChanged` 只发出一次、顺序正确且服务层写回 SQLite。
- 覆盖原图 item flags 含拖动/放置、默认 action 为移动、外部文件 URL 拖入仍仅走 `filesDropped`,以及拉取锁定时不触发排序。
## 验收要点
- 非生成、非拉取状态下,用户可将任意参考图拖到第一张位置,刷新后该图显示“主图”,数据库 `source_order` 同步更新。
- 拖到其它位置也可正常重排;无效 drop 不写数据库、不提示排序成功。
- 外部图片拖入继续进入“添加商品原图”,不被当作内部排序。
- 生成中或拉取未完成时仍不能排序,并保留中文原因提示;任务结束后可立即排序。
- 自动验证:
- `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 生图、下载并发或 Shopee CDP。
- 不解除生成中或拉取未完成时的排序锁定,不增加新的主图选择按钮。
- 不影响①至⑤模块及商品原图的删除、预览、勾选和外部文件导入语义。
## 执行记录
- 2026-07-23 完成。
- `ProductOriginalList` 明确使用 `Qt.MoveAction` 作为内部拖放默认动作;处理非 URL 的内部 drop 时会先强制移动语义,并且只在来源为当前原图列表、Qt 已接受移动后才发送 `orderChanged`。外部文件 URL 拖入仍直接走 `filesDropped`,不会触发排序。
- `_refresh_originals()` 创建每张真实商品原图时显式附加 `Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled`,因此参考图和第一张主图都可作为拖放源和合法放置目标。排序仍复用既有 `reorder_originals()` 和 SQLite 单事务持久化,刷新后第一张继续按“主图”文字、粗体和语义色展示。
- 拉取未完成及生成中的排序锁定未改;内部无效 drop 不会发送排序信号。已同步 `docs/04-architecture.md` 与 `docs/routes.md`。
- 新增 GUI 回归:验证从项目刷新出的真实原图同时具备拖动/放置 flag,列表默认动作为移动;外部文件 URL drop 只触发导入、不触发排序。已有排序回归继续验证第二张变为第一张后的 SQLite 顺序、主图展示和成功提示。
- 验证通过:`py -3.10 -m unittest tests.test_product_suite_gui tests.test_image_studio tests.test_product_suite`(107项)、`py -3.10 -m unittest discover -s tests`(674项)、`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`git diff --check`。离屏 PySide6 有字体目录/插件告警,但无测试失败。