--- id: T-644 title: 商品套图历史生成按轮次弹窗展示 phase: 7 deps: [T-643] status: DONE created: 2026-07-16 --- ## 问题 / 背景 T-643 完成后,⑥「商品套图」主结果区默认只展示当前生成轮次,并可在软件重启后恢复。原「历史生成」按钮当前是可选中按钮,会直接把当前项目全部 job 混入主结果网格: - 主界面在“本轮”和“全部历史”之间切换,用户容易失去当前工作上下文; - 历史 job 没有按生成轮次、时间和状态分组; - 旧失败、单张重试和后续新一轮图片混排,难以对比; - “返回本轮”模式依赖 `SuiteTaskState.show_history` 内存状态,增加生成、重试和刷新时的分支。 参考 `D:\chengma\虾皮圈电商图生成器源码` 的「历史生成」模块,其按批次倒序展示、数量统计、缩略图网格、预览和打开目录的交互值得复用;但当前项目必须以 SQLite 的项目、轮次、job 和 asset 为事实来源,不能照搬扫描文件夹和直接删除文件的实现。 ## 方案 ### 1. 历史按钮改为打开弹窗 修改 `app/gui/tabs/product_suite.py`: - 「历史生成」改为普通按钮,不再 `setCheckable(True)`; - 点击后打开当前商品项目的「历史生成记录」弹窗; - 删除主结果区的 `show_history` 模式切换和「返回本轮」文案; - 主结果区始终读取 T-643 的当前轮次,生成、单张重试和刷新不再切换历史状态; - 未选择店铺、未绑定商品项目或没有生成记录时,给出明确中文提示,不打开空白弹窗。 按钮仍位于顶部项目操作区,不改变「打开结果文件夹」「添加图片」和账号/商品 ID 控件顺序。 ### 2. 弹窗限定当前店铺和商品 新增独立 PySide6 历史对话框组件,标题和上下文至少显示: - 店铺名称/账号别名; - 商品 ID;临时草稿显示“临时草稿”,不伪造商品 ID; - 轮次数量和可用图片数量; - 最近刷新时间或刷新按钮。 弹窗只查询当前 `project_id`,不得展示其他店铺、商品或已软删除项目的数据。切换商品任务后再次打开,应使用新项目上下文;同一窗口不得残留上一商品缩略图。 ### 3. 按轮次倒序展示 使用 T-643 的结构化轮次查询,不扫描图片目录: - 轮次按创建时间倒序; - 每轮标题显示生成时间、总数、成功/失败/取消数量; - 当前使用轮次显示「当前」标签,便于与历史轮次对比; - 旧版迁移记录显示「旧版历史记录」,不伪造生成时间或批次; - 每轮图片按稳定槽位顺序展示; - 单张重试默认展示该槽位当前版本;可在 tooltip 或详情中说明重试次数,不把同一槽位的每次失败尝试都平铺成多张主图片; - 全失败或无可用图片的轮次仍保留轮次头和状态说明,不能静默消失。 参考项目的“类型标签 + 时间 + 数量 + 图片网格”视觉层级可以借鉴,但保持当前桌面应用的颜色、8px 以内圆角、字体和卡片密度,不复制 Web CSS。 ### 4. 图片卡片和预览 第一版历史弹窗为只读查看: - 单击选中图片,双击打开现有自适应大图预览; - 右键提供「预览」「复制路径」「打开所在文件夹」; - 本地文件缺失时显示「图片文件不可用」占位,保留生成时间、类型和状态; - tooltip 可显示图片类型、轮次时间、状态和重试次数; - 不显示接口 URL、完整提示词、Cookie、密钥或裸英文技术错误。 历史弹窗第一版不提供删除、撤销删除、重新生成、设为当前轮或替换终选图。参考项目的直接文件删除不能照搬,因为当前资产可能被 job、终选或导出流程引用。 ### 5. 性能和窗口生命周期 - 历史轮次和 job 从 SQLite 分页或分段读取,初次建议加载最近 20 轮,底部提供「加载更多」; - 缩略图按可见区域延迟加载或限制解码尺寸,不直接把原始大图全部载入内存; - 刷新时保持滚动位置或当前轮展开状态,不重复创建无法回收的图片对象; - 同一商品重复点击按钮时复用或聚焦已有弹窗,避免打开多个相同窗口; - 关闭弹窗必须释放缩略图、信号和线程引用,不出现 `QThread: Destroyed while thread is still running`; - 历史弹窗打开期间生成任务可以继续运行;点击刷新后读取新状态,不阻塞 cmhub worker。 若第一版没有引入后台缩略图线程,也必须使用受限尺寸的同步加载并验证常见历史数量下不会明显冻结 GUI。 ### 6. 空态与异常边界 - 无历史:显示「暂无历史生成记录,完成套图生成后会自动出现在这里」; - 只有当前轮:正常显示当前轮,不使用“没有历史”误导用户; - 文件缺失:只标记图片不可用,不删除 DB; - 数据库读取失败:弹窗保留并显示中文错误与「重试」; - 当前项目被软删除或任务关闭:关闭弹窗或切换为不可操作状态,不继续读取错误项目; - 用户可见文案全部使用中文。 同步更新 `docs/04-architecture.md`、`docs/api.md`、`docs/routes.md`;如新增效果图,统一保存到 `docs/ui/` 并登记 `docs/ui/README.md`。 ## 验收要点 - [ ] 点击「历史生成」后弹出当前店铺、当前商品的历史生成窗口,主结果区仍保持当前轮。 - [ ] 按钮不再出现选中状态或「返回本轮」文字。 - [ ] 历史按轮次倒序分组,显示时间、数量、状态和当前轮标签。 - [ ] 同一轮单张重试不会平铺成新的整轮;当前槽位图片和重试次数表达清楚。 - [ ] 软件重启后可查看 T-643 持久化的当前轮和更早历史轮。 - [ ] 不会展示其他店铺、商品或软删除项目的图片。 - [ ] 双击图片自适应预览;复制路径、打开所在文件夹正常。 - [ ] 文件缺失显示占位,不删除 job/asset 或导致弹窗崩溃。 - [ ] 历史较多时可加载更多,打开和滚动不会一次解码全部原图导致 GUI 长时间冻结。 - [ ] 生成任务运行时可打开和刷新历史弹窗,生成停止、重试和结果刷新不回归。 - [ ] 历史弹窗没有删除、设为当前轮或重新生成等破坏性入口。 ## 测试 - `tests/test_image_studio.py`: - 项目轮次分页和倒序; - 当前轮标签、状态统计和槽位去重; - 跨项目隔离、旧版历史和缺失资产。 - `tests/test_product_suite_gui.py`: - 历史按钮为普通按钮并打开弹窗; - 主结果区不再切换 `show_history`; - 商品上下文、轮次分组、当前标签、空态和错误态; - 双击预览、复制路径、打开目录; - 重复点击复用窗口、关闭任务后的窗口收尾; - 生成期间刷新历史不影响 worker。 - 如拆出独立对话框测试文件,覆盖分页/加载更多和缩略图受限加载。 - 运行: ```bash 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 ``` 在包含多轮、部分失败、单张重试和缺失文件的测试项目上人工检查窗口尺寸、滚动、预览和 Windows 100% / 125% / 150% 显示缩放。 ## 边界(不改什么) - 不扫描生成目录推断历史轮次,不把文件系统当作主数据源。 - 不在历史弹窗删除图片、清空轮次、恢复历史轮为当前轮或触发重新生成。 - 不修改生成提示词、模型、并发、扣点、超时、轮询、下载或文件保存格式。 - 不修改 CDP、蝦皮主图拉取、①导入采集、②AI生成、③更新蝦皮、④账号管理或⑤设置。 - 不照搬参考项目的 Web 技术栈、全局历史导航或直接文件删除逻辑。 ## 执行记录 - 2026-07-16:将⑥顶部「历史生成」从可选中切换按钮改为普通按钮;主结果区移除 `show_history` 分支,始终按项目持久化的当前轮次展示。 - 2026-07-16:新增 `ProductSuiteHistoryDialog` 和只读缩略图卡片。窗口按当前 `project_id` 分页读取最近20轮 SQLite 记录,按轮次时间倒序展示当前/旧版标签、状态统计、稳定槽位图片、重试次数和本地文件缺失占位;只提供预览、复制路径和打开所在文件夹。 - 2026-07-16:任务关闭时关闭对应历史窗口;同一商品重复点击复用已打开窗口。生成 worker、当前轮和主结果区不受历史窗口影响。 - 2026-07-16:`list_generation_rounds()` 增加创建时间倒序作为轮次排序主键,并保留 ID 作为同时间的稳定次序。 - 2026-07-16:补充轮次分页、跨项目隔离、单槽位重试去重、缺失资产、只读操作、窗口复用、关闭任务和无历史空态测试。 - 验证通过: - `py -3.10 -m unittest discover -s tests`(558 项通过;Qt 离屏环境仅输出字体/窗口插件提示) - `py -3.10 -m ruff check app tests main.py` - `py -3.10 -m compileall app main.py` - `git diff --check`