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

98 lines
8.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-631
title: 商品套图原图自适应展示与批量选择删除
phase: 7
deps: [T-627]
status: TODO
created: 2026-07-14
---
## 问题 / 背景
⑥「商品套图」的商品原图列表当前使用固定 `210px` 高度的 `QListWidget`。当原图超过当前可见行数时,列表内部出现纵向滚动;其外层配置面板本身又是 `QScrollArea`,形成嵌套滚动,用户不容易一次看清最多 16 张原图。
原图管理当前还存在以下限制:
- 每次只能通过悬停删除按钮、右键菜单或 Delete 键删除当前一张图片,不能全选、反选或批量移除。
- `ProductOriginalList` 同时承担单击下载、双击预览和拖拽排序;直接改成 Qt 多行选择会让预览、当前项和拖拽语义混在一起。
- 后端只有 `image_studio.remove_asset_if_unused(asset_id)` 单张删除。它会在图片被生成任务或终选引用时拒绝删除,并且故意保留本地文件;若 GUI 简单循环删除多张,可能出现前几张已删除、后一张失败的部分成功状态。
- 远程蝦皮原图可能仍在后台下载。下载过程中删除对应资产会造成 worker 回写已删除记录或遗留孤立文件的竞态。
顾客需要商品原图区域取消内部上下滚动、按图片数量自然向下展开;每张真实图片增加勾选框,标题右侧增加「全选」「反选」,并能从任意已勾选图片的右键菜单一次删除多张。
## 方案
### 1. 商品原图列表按内容自然展开
- 取消商品原图列表当前固定 `210px` 高度;关闭列表自身的横向和纵向滚动条。
- 根据列表当前可用宽度、`gridSize`、间距和可见 tile 数动态计算列数、行数与内容高度,并更新列表高度。计算须包含现有最少 6 个展示位语义,最多按 16 张真实原图计算。
- 图片数量变化、窗口或 splitter 宽度变化、缩略图下载完成、拖拽排序及任务切换后都要重新计算高度;不得只在首次创建控件时计算一次。
- 商品原图列表自然向下撑开后,由左侧外层 `suiteConfigScroll` 负责整页滚动;不得通过撑大主窗口、恢复列表内部滚动或制造横向滚动来容纳图片。
- 动态高度必须有明确上限并保持布局稳定,不能因占位图、悬停删除按钮、勾选框或图片下载完成而造成行高跳变。
### 2. 用勾选状态管理批量选择
- 只为具有真实 `asset_id` 的原图项显示复选框;「添加」占位项不得参与全选、反选、批量删除或已选数量。
- 批量选择以每项 `Qt.CheckStateRole` 或等价的自定义 delegate 状态为唯一事实源,不把 `QListWidget` 改成容易影响拖拽的多行选择模式,也不使用会破坏现有 `InternalMove` 排序的 `setItemWidget()` 堆叠子控件方案。
- 单击复选框只切换批量选择;单击缩略图继续执行现有的按需后台下载/当前项行为;双击继续预览;从缩略图区域拖动继续调整原图顺序。复选框点击和拖拽手势不得互相误触。
- 在「商品原图」标题右侧依次显示 `已选 N 张`、`全选`、`反选`。全选与反选只处理当前项目中可见的真实原图;无真实原图时禁用。
- 勾选状态是当前界面的临时操作状态,不写 SQLite 或 `config.json`。普通刷新和拖拽排序时按 `asset_id` 保留仍存在图片的勾选状态;切换账号、商品或套图任务及删除成功后清空,避免跨项目误删。
- 生成中继续沿用现有禁止删除边界;选中集合中存在正在后台下载的图片时,批量删除须整体阻断并给中文提示,不能边下载边删除。
### 3. 右键与键盘删除交互
- 右键已勾选图片且已选数量大于 1 时,菜单显示 `删除选中的 N 张图片…`;数量为 1 时显示 `删除选中图片…`。
- 右键未勾选图片时,不得静默清空或扩展已有勾选集合;菜单保留单张 `删除这张图片…`,如同时已有其他勾选图片,可另外显示明确带数量的批量删除动作。
- Delete/Backspace 键在存在勾选图片时按勾选集合执行批量删除;没有勾选图片时保持删除当前单张的既有语义。所有路径统一进入同一确认与服务层逻辑。
- 删除确认框必须显示准确数量,并明确:只从当前商品的原图列表移除,不删除蝦皮线上图片,也不删除本地源文件;以后重新拉取蝦皮主图时,线上仍存在的图片可能重新出现。
- 如果勾选集合包含当前第 1 张主图,确认框额外提示“删除后下一张图片将成为主图”。取消确认不得改变勾选状态或排序。
- 现有悬停 `×` 继续作为单张快捷删除入口,但也必须复用相同的确认和后端校验,不另写一套删除逻辑。
### 4. 后端事务级批量移除
- 在 `app/image_studio.py` 增加面向商品原图的批量服务,例如 `remove_original_assets_if_unused(project_id, asset_ids, ...)`;GUI 不得直接循环调用单张删除函数。
- 服务入口先归一化并去重 ID,然后一次性校验:项目存在且未删除、所有 ID 均属于该项目、资产类型均为 `original`、所有资产均未被 job 或 selection 引用。不得删除其他项目或生成结果资产。
- 任意一张校验失败时,整批不删除,并返回可供 GUI 中文说明的阻断原因;禁止部分成功。
- 全部校验通过后,在同一个 SQLite 事务中删除对应资产行,并按剩余顺序连续更新 `source_order`。本地图片文件继续保留,遵守现有 `remove_asset_if_unused()` 的非破坏性文件语义。
- 单张删除入口可复用批量服务的单元素路径,或保留兼容包装;不能让单张与批量删除形成不同的引用校验和文件处理规则。
- UI 层在调用服务前检查当前任务生成状态和所选资产下载状态;服务层负责项目归属、资产类型、引用和事务完整性,两层职责不得混淆。
### 5. 测试与界面验证
- `tests/test_product_suite_gui.py` 覆盖:
- 原图列表横向/纵向滚动条关闭,0、6、7、16 张及宽度变化后高度和行数重新计算;
- 只有真实图片有勾选状态,全选、反选和 `已选 N 张` 结果正确,空列表按钮禁用;
- 普通刷新和排序保留有效 asset ID 的勾选,项目/任务切换及删除成功清空;
- 复选框、单击下载、双击预览和拖拽排序语义互不覆盖;
- 右键已选/未选图片及 Delete 键产生正确的单张或批量动作,确认文案包含数量和主图变化提示;
- 生成中或选中图片下载中时批量删除被整体阻断。
- `tests/test_image_studio.py` 覆盖:
- 多张未引用原图在单事务中删除,剩余 `source_order` 连续;
- 选择中混入其他项目 ID、非原图 ID、不存在 ID 或已被 job/selection 引用的图片时全部不删除;
- 删除资产行后本地文件仍存在;单张兼容入口与批量入口校验语义一致。
- 在最小支持窗口、常规窗口和 Windows 100% / 125% / 150% 显示缩放下人工检查 6、9、16 张原图:列表不出现内部滚动条,外层滚动自然,复选框不遮挡缩略图或文字,标题栏操作不重叠。
## 验收要点
- 商品原图列表不再固定高度或显示内部上下滚动条,最多 16 张图片按当前宽度自然换行并向下撑开,左侧配置区仍可正常滚动。
- 每张真实原图都有可操作的复选框,标题右侧可全选、反选并显示准确已选数量;占位项不参与选择。
- 预览、按需下载、拖拽排序、悬停单张删除与新增批量选择互不回归。
- 从已勾选图片右键或按 Delete 可一次删除选中集合;右键未勾选图片不会暗中改变批量范围。
- 批量删除只有全部校验通过才生效,引用中、跨项目、下载中或生成中的集合不会出现部分删除。
- 删除后下一张正确成为主图、剩余顺序连续;本地文件和蝦皮线上图片不被删除,重新拉取线上原图的语义有明确提示。
- 自动验证:
- `py -3.10 -m unittest tests.test_product_suite_gui`
- `py -3.10 -m unittest tests.test_image_studio`
- `py -3.10 -m unittest discover -s tests`
- `python -m ruff check app tests main.py`
- `py -3.10 -m compileall app main.py`
- `git diff --check`
## 边界(不改什么)
- 不删除本地图片文件,不删除或修改蝦皮线上商品图片;本任务的“删除”只表示从当前本地商品项目的原图资产列表移除。
- 不修改拉取蝦皮主图的 CDP 选择器、Chrome 前后台策略、登录检测和 Shopee 页面操作流程。
- 不修改 cmhub 生图提交、轮询、下载、并发、重试或计费逻辑,不修改生成结果/历史结果删除语义。
- 不把原图勾选状态持久化,不新增 SQLite 字段;只扩展现有图片资产服务的事务级批量操作。
- 不修改①~⑤模块 UI,不修改主窗口全局样式。