2026-07-23 08:44:33 +08:00
|
|
|
|
---
|
|
|
|
|
|
id: T-691
|
|
|
|
|
|
title: 商品套图原图真实拖放排序修复
|
|
|
|
|
|
phase: 7
|
|
|
|
|
|
deps: [T-688]
|
2026-07-23 08:47:32 +08:00
|
|
|
|
status: DONE
|
2026-07-23 08:44:33 +08:00
|
|
|
|
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 08:47:32 +08:00
|
|
|
|
- 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 有字体目录/插件告警,但无测试失败。
|