feat(product-suite): clarify primary image sorting
This commit is contained in:
@@ -22,6 +22,7 @@ from PySide6.QtCore import (
|
||||
)
|
||||
from PySide6.QtGui import (
|
||||
QColor,
|
||||
QFont,
|
||||
QIcon,
|
||||
QImage,
|
||||
QImageReader,
|
||||
@@ -301,6 +302,7 @@ class ProductOriginalList(QListWidget):
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setAcceptDrops(True)
|
||||
self._reordering_enabled = True
|
||||
self.setDragEnabled(True)
|
||||
self.setDropIndicatorShown(True)
|
||||
self.setDragDropMode(QListWidget.InternalMove)
|
||||
@@ -318,7 +320,31 @@ class ProductOriginalList(QListWidget):
|
||||
self._hover_remove_button.setFixedSize(28, 28)
|
||||
self._hover_remove_button.clicked.connect(self._remove_hovered)
|
||||
self._hover_remove_button.hide()
|
||||
self.setToolTip("可拖入或粘贴图片;已有缩略图可拖动排序")
|
||||
self._update_reordering_tooltip()
|
||||
|
||||
def reordering_enabled(self):
|
||||
return self._reordering_enabled
|
||||
|
||||
def set_reordering_enabled(self, enabled):
|
||||
self._reordering_enabled = bool(enabled)
|
||||
self.setDragEnabled(self._reordering_enabled)
|
||||
self.setDropIndicatorShown(self._reordering_enabled)
|
||||
self._update_reordering_tooltip()
|
||||
item_tooltip = (
|
||||
"拖动到第一位即可设为主图"
|
||||
if self._reordering_enabled
|
||||
else "正在拉取蝦皮主图,完成后可调整图片顺序"
|
||||
)
|
||||
for row in range(self.count()):
|
||||
item = self.item(row)
|
||||
if item.data(Qt.UserRole) is not None:
|
||||
item.setToolTip(item_tooltip)
|
||||
|
||||
def _update_reordering_tooltip(self):
|
||||
if self._reordering_enabled:
|
||||
self.setToolTip("可拖入或粘贴图片;拖动图片到第一位即可设为主图")
|
||||
else:
|
||||
self.setToolTip("正在拉取蝦皮主图,完成后可调整图片顺序")
|
||||
|
||||
def paintEvent(self, event):
|
||||
super().paintEvent(event)
|
||||
@@ -343,6 +369,9 @@ class ProductOriginalList(QListWidget):
|
||||
if event.mimeData().hasUrls():
|
||||
event.acceptProposedAction()
|
||||
return
|
||||
if not self._reordering_enabled:
|
||||
event.ignore()
|
||||
return
|
||||
super().dragMoveEvent(event)
|
||||
|
||||
def dropEvent(self, event):
|
||||
@@ -351,6 +380,9 @@ class ProductOriginalList(QListWidget):
|
||||
self.filesDropped.emit(paths)
|
||||
event.acceptProposedAction()
|
||||
return
|
||||
if not self._reordering_enabled:
|
||||
event.ignore()
|
||||
return
|
||||
super().dropEvent(event)
|
||||
self.orderChanged.emit(self.asset_ids())
|
||||
|
||||
@@ -3344,6 +3376,11 @@ class ProductSuiteTab(QWidget):
|
||||
for index, asset in enumerate(assets, 1):
|
||||
label = "主图" if index == 1 else "参考%d" % (index - 1)
|
||||
item = QListWidgetItem(label)
|
||||
if index == 1:
|
||||
font = QFont(item.font())
|
||||
font.setBold(True)
|
||||
item.setFont(font)
|
||||
item.setForeground(QColor("#1f5fbf"))
|
||||
item.setData(Qt.UserRole, int(asset.id))
|
||||
item.setData(
|
||||
ORIGINAL_CHECK_STATE_ROLE,
|
||||
@@ -3351,10 +3388,26 @@ class ProductSuiteTab(QWidget):
|
||||
)
|
||||
if _asset_usable(asset):
|
||||
item.setIcon(QIcon(_image_pixmap(asset.local_path, QSize(82, 64))))
|
||||
item.setToolTip("%s,勾选可批量删除;双击预览;拖动可调整顺序" % label)
|
||||
item.setToolTip(
|
||||
"%s,勾选可批量删除;双击预览;%s"
|
||||
% (
|
||||
label,
|
||||
"拖动到第一位即可设为主图"
|
||||
if self.original_list.reordering_enabled()
|
||||
else "正在拉取蝦皮主图,完成后可调整图片顺序",
|
||||
)
|
||||
)
|
||||
else:
|
||||
item.setIcon(QIcon(_placeholder_pixmap("待下载", QSize(82, 64))))
|
||||
item.setToolTip("%s尚未下载;勾选可批量删除,单击缩略图后台拉取" % label)
|
||||
item.setToolTip(
|
||||
"%s尚未下载;勾选可批量删除,单击缩略图后台拉取;%s"
|
||||
% (
|
||||
label,
|
||||
"拖动到第一位即可设为主图"
|
||||
if self.original_list.reordering_enabled()
|
||||
else "正在拉取蝦皮主图,完成后可调整图片顺序",
|
||||
)
|
||||
)
|
||||
self.original_list.addItem(item)
|
||||
finally:
|
||||
self.original_list.blockSignals(previous)
|
||||
@@ -3547,16 +3600,31 @@ class ProductSuiteTab(QWidget):
|
||||
state = self._displayed_state
|
||||
if state is None or state.project_id is None:
|
||||
return
|
||||
if state.generation_running():
|
||||
self._refresh_originals(state)
|
||||
self._status("生成中不能调整商品原图顺序", "warning")
|
||||
return
|
||||
if state.pull_running():
|
||||
self._refresh_originals(state)
|
||||
self._status("正在拉取蝦皮主图,完成后可调整图片顺序", "warning")
|
||||
return
|
||||
all_assets = self._original_assets(state, include_missing=True)
|
||||
hidden_ids = [int(asset.id) for asset in all_assets if int(asset.id) not in set(visible_ids)]
|
||||
ordered_ids = list(visible_ids) + hidden_ids
|
||||
before_ids = [int(asset.id) for asset in all_assets]
|
||||
if ordered_ids == before_ids:
|
||||
self._refresh_originals(state)
|
||||
return
|
||||
try:
|
||||
image_studio.reorder_original_assets(
|
||||
state.project_id,
|
||||
list(visible_ids) + hidden_ids,
|
||||
ordered_ids,
|
||||
path=self.db_path,
|
||||
)
|
||||
except Exception as exc:
|
||||
self._message("原图排序失败", _user_error(exc))
|
||||
else:
|
||||
self._status("已更新商品原图顺序,第一张图片为主图", "success")
|
||||
self._refresh_originals(state)
|
||||
|
||||
def _asset_for_item(self, item):
|
||||
@@ -5752,6 +5820,7 @@ class ProductSuiteTab(QWidget):
|
||||
self.item_id_edit.setEnabled(not generation_running and not pull_running)
|
||||
self._refresh_add_images_action(state)
|
||||
self.original_list.setEnabled(not generation_running)
|
||||
self.original_list.set_reordering_enabled(not generation_running and not pull_running)
|
||||
self._refresh_original_selection_controls()
|
||||
for widget in (
|
||||
self.platform_combo,
|
||||
|
||||
@@ -461,14 +461,14 @@ data/images/<batch_id>/<slug>/<task_id>_<item_id>_new.<ext> # AI 生成的新
|
||||
- 套图比例输出固定映射为 `1:1 -> 1024x1024`、`3:4/9:16 -> 1024x1536`、`4:3/16:9 -> 1536x1024`。`image_studio_assets.aspect_ratio` 保持用户原选项,新增 `requested_output_size`、`rendered_width`、`rendered_height` 记录请求尺寸与真实像素;旧记录保持空值,SQLite 迁移仅附加且幂等。服务返回近似比例结构化结果,T-679b 在确认界面呈现。
|
||||
- 商品套图的生图来源在 T-679 组全部验收前不得进入发布版本;图片理解「AI帮写」仍仅使用默认网关 `vision_alias`。
|
||||
- T-658a 后商品套图异步生图提交统一使用 `images` 数组,单图也使用单元素 `{"image_base64": ...}`,不再提交顶层 `image_base64`。提交层最多传 8 张本地图片,单图原文件上限10MiB、编码后总输入上限32MiB;超限在请求前以中文错误阻断,后续多图参考的资产快照和提示词语义由 T-658b/T-658c 负责。
|
||||
- T-658b 后 `image_studio_jobs.reference_asset_ids` 以有序 JSON 图片 ID 列表冻结每个商品套图 job 的参考图;新一轮未勾选逐图主图时,第1张为主图、后续最多7张写入快照,勾选时写空数组。历史 `NULL` 行继续按单图任务处理。恢复或重试只读 job 快照,不回读当前原图列表;参考图资产或本地文件缺失时提交前失败,不静默减少提交数量。
|
||||
- T-658b 后 `image_studio_jobs.reference_asset_ids` 以有序 JSON 图片 ID 列表冻结每个商品套图 job 的参考图;新一轮未勾选逐图主图时,第1张为主图、后续最多7张写入快照,勾选时写空数组。历史 `NULL` 行继续按单图任务处理。商品原图 UI 的第1张明确显示「主图」,用户拖动图片到第一位后由事务更新 `source_order`,排序只影响之后新建的一轮 job,不回写既有 job 快照。恢复或重试只读 job 快照,不回读当前原图列表;参考图资产或本地文件缺失时提交前失败,不静默减少提交数量。
|
||||
- T-658d 后逐图主图 checkbox 右侧显示可换行中文 helper,说明多SKU应逐图生成、同商品多角度应作为参考图一同提交。常规生成确认框从最终 specs 计算主图/参考图数量;未开启逐图主图且可用原图超过8张时,明确第1张主图加前7张参考图的上限和忽略数量。确认总点数仍只按 job 请求数计算,不因单请求图片数量重复计算。
|
||||
- T-647 后商品套图中的「AI帮写」不是②标题生成的复用入口:它使用 `vision_alias` 调 `POST /api/v1/analyze/images`,把当前项目中1至8张可用本地原图按 `source_order` 在一次请求中发送;超过8张时明确只使用前8张,原图勾选状态不改变该输入集合。视觉提示词将这些图片定义为同一商品项目的多角度、细节、包装或场景证据集,要求产出一份商品级联合分析,不按“图1/图2”逐图罗列;结果按商品概述、可确认卖点、适用人群与场景、套图画面要求、待确认或避免编造的信息组织,并把可见差异明确为待确认项,不强行合并为事实。单图上限10MiB、总计上限32MiB,缺图、未下载、超限或未配置别名时不发请求且保留用户现有卖点。读取等待固定120秒、连接等待沿用设置;读超时或网络中断只提示“结果未确认”,不自动重发。成功只显示图片张数、扣点和余额,不显示路径、base64、接口URL、完整提示词或上游原始响应;取消只在请求前/返回后协作生效,不强杀网络线程。
|
||||
- `product_suite.render_product_suite_prompt()` 是弹窗预览和真实生成的唯一 renderer;`build_job_specs()` 建立本轮 specs 前只读取一次用户模板并冻结,每个 job 保存最终完整 prompt,运行中修改模板只影响下一轮。新模板使用 `{生成目标}`,其值为内置分类固定目标描述或含实际名称的“生成自定义分类图片:分类名称。”;同时保留 `{套图名称}`、`{补充描述}` 及平台/地区/语言/比例、可选商品ID/主参考图序号、参考图规则、商品卖点和四个只读规则。旧模板必须同时包含 `{套图名称}` 与 `{补充描述}` 才能按兼容路径继续使用;必需变量缺失、未知/未闭合花括号、只读规则变量未独占一行都视为无效。模板校验仅针对模板文本;替换值是业务数据,商品卖点与要求、分类名等可原样包含 `{}`,不再二次作为变量解析。`product_suite_reference_rule()` 是 `{参考图规则}` 的唯一事实来源,变量值都以 `参考图规则:` 开头:逐图主图开启时为单图主参考规则;未开启时固定为“使用第一張上傳圖作為主商品圖,其餘圖片只作為參考。”,不随实际参考图数量变化。提示词弹窗与 job 共用 renderer;实际提交的参考图数量仍由冻结的 `reference_asset_ids` 决定。默认模板独占一行,不额外重复标签或序号。四个只读规则覆盖尺寸与长图(含禁止多宫格拼接)、政治标识、价格和尺码;商品主体一致性与禁止编造并入参考图规则。比例仍同时传入 `image_studio_generation.run_jobs(aspect_ratio=...)`,进入 cmhub 请求与输出资产元数据。
|
||||
- `image_studio_projects.suite_settings_json` 持久化套图设置,旧数据库由 `db.init_db()` 原位补列,默认 `{}`;`draft_prompt` 继续保存卖点文本。`image_studio_assets` 中有效商品原图最多16张,历史 missing 记录不占有效名额;手工原图不会因再次同步蝦皮 URL 被误标 missing。商品套图原图列表的批量勾选只保存在当前 `SuiteTaskState` 对应的界面上下文,不写库;批量移除由 `remove_original_assets_if_unused()` 一次校验项目归属、原图类型和 job/终选引用,并在单个 SQLite 事务中删除资产行、连续重排 `source_order`。服务不删除本地文件或蝦皮线上图片,任一资产校验失败时整批回滚。
|
||||
- T-636 起,`image_studio_projects` 增加 `binding_state`(`draft` / `bound`)和稳定 `storage_key`。既有项目迁移为 `bound`,并以原 `item_id` 回填 `storage_key`;项目目录改用 `storage_key`,因此临时草稿绑定正式商品 ID 后不移动目录、不改写已有资产路径。草稿内部使用 `draft_<uuid>` 作为仅数据库可见的非空 `item_id`,GUI 输入框始终保持空白,用户日志和 cmhub 提示词只使用“临时草稿”或“未绑定商品”,不得暴露该内部值。
|
||||
- 在商品套图中,已选账号但未填写商品 ID 时允许输入卖点、导入、拖入或粘贴本地图片。非空卖点在现有防抖稳定后会创建一个可恢复临时草稿并保存到 `image_studio_projects.draft_prompt`;首次有效图片导入也会创建草稿。空白卖点、取消选择和全部导入失败不保留空草稿。只有卖点的草稿同样属于可恢复业务内容;卖点清空且没有资产/job 时仍可按空草稿规则清理。草稿可管理本地图片、AI 帮写、生成套图、查看历史和打开结果目录,但在创建 worker、启动 Chrome 或执行 CDP 前禁止「拉取蝦皮主图」。输入合法数字商品 ID 后,经确认原地绑定同一个 `project_id`;资产、job、selection、提示词、套图设置和 `storage_key` 均保持不变。若同账号目标 ID(含软删除项目)已存在则拒绝覆盖或合并。
|
||||
- 商品套图拉取蝦皮主图使用独立内存 `pull_run_token` 覆盖 URL 读取和本轮后台下载。运行中按钮提供「停止并保留 / 停止并清除本次新增 / 继续拉取」;停止采用协作式安全边界,不强杀线程、Chrome 或已发出的 CDP/HTTP 请求。选择清除时只移除本轮新增、属于当前项目且未被 job/selection 引用的远程原图,并恢复拉取前已有原图的状态和顺序;本地手动导入图片、拉取前已有图片、用户文件和蝦皮线上图片不删除。旧 token 的 URL/下载迟到结果不得覆盖新一轮状态。
|
||||
- 商品套图拉取蝦皮主图使用独立内存 `pull_run_token` 覆盖 URL 读取和本轮后台下载。运行中按钮提供「停止并保留 / 停止并清除本次新增 / 继续拉取」;停止采用协作式安全边界,不强杀线程、Chrome 或已发出的 CDP/HTTP 请求。选择清除时只移除本轮新增、属于当前项目且未被 job/selection 引用的远程原图,并恢复拉取前已有原图的状态和顺序;本地手动导入图片、拉取前已有图片、用户文件和蝦皮线上图片不删除。拉取期间仅关闭商品原图的内部拖拽排序,避免同步刷新与用户排序竞争,文件导入、预览和其他非冲突操作仍保持可用;`reorder_originals()` 也会防御性拒绝迟到排序信号并按数据库顺序刷新。旧 token 的 URL/下载迟到结果不得覆盖新一轮状态。
|
||||
- 启动时恢复未软删除、至少含一条资产或生成任务的草稿为独立中文“临时草稿”标签,按最近更新时间排序。关闭非空草稿可选择保留、软删除或取消;软删除不物理删除图片目录。③「更新蝦皮」只处理正式任务,不接受临时草稿。
|
||||
- 第六 Tab 的多个 `SuiteTaskState` 各自保留 generation/pull/import/AI/download worker 与线程引用;切换任务不取消任务。多个任务可并行,但 `image_studio_generation` 使用进程级来源中立 semaphore,保证默认网关与自定义网关生图合计最多5个在途 job。线程还在运行时关闭任务只请求协作式停止,模块级引用保留到 `QThread.finished`,不得提前销毁线程对象;下载前后均检查停止信号,停止后的临时文件不入资产库。
|
||||
- T-639 后每轮套图生成使用仅存在内存的 `run_token` 隔离迟到信号,progress/finished/cancelled/failed 通过主线程绑定槽统一处理;正常 worker 结果、`QThread.finished` 和本轮 job 连续两次全部终态看门狗共同进入幂等 finalize。GUI 只按本轮明确 `job_ids` 判断完成,不用历史图片数量;即使最终信号丢失也会恢复按钮,旧线程引用仍保留到真实结束。停止为协作式:调度循环约每200ms检查标记并取消未开始 future,提交/轮询在有界请求返回后停止;requests 在流式数据块边界取消,Windows curl 由隐藏窗口 `Popen` 有界 terminate/kill。已有 `task_id` 的停止任务保留 resume,不假设服务端任务被取消或点数退回。
|
||||
|
||||
+2
-2
@@ -217,9 +217,9 @@
|
||||
- 二级套图任务标签使用独立紧凑样式,不继承主模块 Tab 的大尺寸点击区。上下文栏左侧集中「历史生成 / 添加图片」,右侧集中账号、商品 ID 和拉取入口;常见 11~13 位商品 ID 不得裁切,长账号可通过 tooltip 查看完整名称。内部生成目录不提供顶部直达入口,正式取图统一通过历史窗口的「导出本轮」选择外部目录。
|
||||
- 项目仍以 `账号别名 + 商品ID` 唯一,复用 `image_studio_projects/assets/jobs`。`suite_settings_json` 保存平台、国家地区、语言、比例、逐图主图模式和分类数量;卖点文本继续使用 `draft_prompt`,用户停止输入约 500ms 后自动保存,切换任务、关闭任务或程序前同步补保存。未创建项目时只保留在当前任务内存,不因输入文字自动创建临时草稿。
|
||||
- 「商品卖点与要求」关闭内部横向和纵向滚动条,输入框按完整换行内容自然增高;文本删除后可缩回最低高度,页面过长时统一由左侧 `suiteConfigScroll` 滚动。
|
||||
- 商品原图最多16张。前6个槽位固定显示主图与参考1~5;列表关闭内部滚动条,按可用宽度换行并自然向下展开,由左侧配置区统一滚动。支持文件选择、外部拖入、剪贴板粘贴和列表内排序。历史失效远程图不占有效名额;第1张是主参考图。
|
||||
- 商品原图最多16张。前6个槽位固定显示主图与参考1~5;列表关闭内部滚动条,按可用宽度换行并自然向下展开,由左侧配置区统一滚动。支持文件选择、外部拖入、剪贴板粘贴和列表内排序;第1张以「主图」文字、粗体和语义色标识,拖动任意图片到第一位即可设为主图,排序成功提示“已更新商品原图顺序,第一张图片为主图”。历史失效远程图不占有效名额;第1张是主参考图。
|
||||
- 每张真实原图左上角提供独立勾选框,标题行显示「已选 N 张 / 全选 / 反选」;添加占位图不参与选择。勾选只在当前任务界面内临时保留,普通刷新和排序按资产 ID 保留,切换任务或删除成功后清空。右键或 Delete 可批量移除,确认框说明准确数量、主图变化及非破坏性边界;生成中或勾选项仍在下载时整批阻断。移除只删除当前项目的本地资产记录,不删除本地源文件或蝦皮线上图片。
|
||||
- 「拉取蝦皮主图」复用只读 CDP,读取 URL 后由最多2个下载 worker 后台落盘;不改标题/封面、不拖拽、不点击更新。拉取、下载期间其余界面和其他任务仍可操作。
|
||||
- 「拉取蝦皮主图」复用只读 CDP,读取 URL 后由最多2个下载 worker 后台落盘;不改标题/封面、不拖拽、不点击更新。拉取、下载期间其余界面和其他任务仍可操作,但商品原图列表仅锁定内部拖拽排序,并用 tooltip 说明“正在拉取蝦皮主图,完成后可调整图片顺序”;文件拖入、预览等非冲突操作不因此整页禁用。
|
||||
- 套图按分类配置生成图片,固定分类顺序为白底图、场景图、模特场景图、细节说明图、卖点图,之后是自定义分类;默认数量为白底图1、场景图2、模特场景图0、细节说明图0、卖点图2。新增分类默认数量为0,用户未主动配置时不增加生成任务或点数消耗。自定义分类名称非空、无空格、最多10字且不可重名。逐图主图开启后,白底图只生成一次,其余分类按每张有效原图展开。
|
||||
- 套图提示词设置弹窗默认模板首行使用“生成目标:{套图名称},{生成目标}”。五个内置分类使用统一固定目标描述;自定义分类使用“生成自定义分类图片:分类名称。”,实际分类名称必须进入最终提示词。旧模板若仍使用 `{套图名称}` 与 `{补充描述}` 会继续读取和渲染,不静默覆盖用户文件;新模板与旧模板都由同一个 renderer 提供预览和 job prompt。模板变量只在替换前校验,商品卖点与要求、分类名等业务文本允许原样包含 `{}`,不会再次按变量解析;发生其他渲染异常时显示「无法生成商品套图」,不创建 job、worker、价格查询或网关请求。
|
||||
- `{参考图规则}` 由实际提交图片数决定:逐图主图开启、或当前只有1张可用图时,使用“参考图规则:当前上传图片是本任务唯一主参考图;保持商品主体、款式、颜色和关键细节准确;不编造用户与参考图均未提供的信息。”;未开启且有2至8张可用图时,第1张为主商品图,第2至N张仅作风格、构图、场景或排版参考,N 为本次实际提交总数。提示词弹窗按当前可用原图数预览,`build_job_specs()` 按冻结的参考图快照渲染;默认模板中的 `{参考图规则}` 独占一行,不额外重复标签或序号。
|
||||
|
||||
+7
-2
@@ -3,7 +3,7 @@ id: T-688
|
||||
title: 商品套图主图拖拽排序反馈与拉取保护
|
||||
phase: 7
|
||||
deps: [T-631, T-658b]
|
||||
status: TODO
|
||||
status: DONE
|
||||
created: 2026-07-22
|
||||
---
|
||||
|
||||
@@ -52,4 +52,9 @@ created: 2026-07-22
|
||||
|
||||
## 执行记录
|
||||
|
||||
- 待实施。
|
||||
- 2026-07-22 完成。
|
||||
- `ProductOriginalList` 增加独立的内部排序开关;拉取蝦皮主图期间保留外部文件拖入、预览和其它界面操作,但关闭内部拖拽、落点指示和排序信号接收,并以中文 tooltip 提示等待拉取完成。
|
||||
- 商品原图第1张继续作为业务主图,现以「主图」文字、粗体和语义色标识;每张图片和列表 tooltip 明确说明拖到第一位即可设为主图。事务排序成功后状态栏提示“已更新商品原图顺序,第一张图片为主图”。
|
||||
- `reorder_originals()` 对生成中和拉取中的迟到排序信号防御性拒绝,并刷新数据库顺序;排序只影响以后新建的 job,既有 `source_asset_id/reference_asset_ids` 快照不变。
|
||||
- 已同步 `docs/04-architecture.md` 与 `docs/routes.md`,新增/更新 GUI 测试覆盖主图反馈、排序持久化、拉取锁定、直接调用防御与恢复排序。
|
||||
- 验证通过:`py -3.10 -m unittest tests.test_product_suite tests.test_product_suite_gui`(78项)、`py -3.10 -m unittest tests.test_image_studio`(24项)、`py -3.10 -m unittest discover -s tests`(669项)、`py -3.10 -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`git diff --check`。离屏 PySide6 输出字体目录/插件告警,但无测试失败。
|
||||
|
||||
@@ -2895,8 +2895,15 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
tab.original_list.set_checked_asset_ids([assets[1].id])
|
||||
tab._refresh_originals(state)
|
||||
self.assertEqual([assets[1].id], tab.original_list.checked_asset_ids())
|
||||
self.assertEqual("主图", tab.original_list.item(0).text())
|
||||
self.assertTrue(tab.original_list.item(0).font().bold())
|
||||
self.assertIn("拖动到第一位即可设为主图", tab.original_list.item(0).toolTip())
|
||||
statuses = []
|
||||
tab._status = lambda message, level=None: statuses.append((message, level))
|
||||
tab.reorder_originals([assets[1].id, assets[0].id])
|
||||
self.assertEqual([assets[1].id], tab.original_list.checked_asset_ids())
|
||||
self.assertEqual([assets[1].id, assets[0].id], tab.original_list.asset_ids())
|
||||
self.assertEqual(("已更新商品原图顺序,第一张图片为主图", "success"), statuses[-1])
|
||||
|
||||
tab.add_task(inherit=True)
|
||||
self.assertEqual([], tab.original_list.checked_asset_ids())
|
||||
@@ -3138,6 +3145,10 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
self.assertTrue(state.pull_running())
|
||||
self.assertEqual("停止拉取蝦皮", tab.pull_button.text())
|
||||
self.assertTrue(tab.pull_button.isEnabled())
|
||||
self.assertTrue(tab.original_list.isEnabled())
|
||||
self.assertFalse(tab.original_list.reordering_enabled())
|
||||
self.assertFalse(tab.original_list.dragEnabled())
|
||||
self.assertIn("完成后可调整图片顺序", tab.original_list.toolTip())
|
||||
worker = state.pull_worker
|
||||
|
||||
with mock.patch.object(tab, "_pull_stop_action", return_value="continue"):
|
||||
@@ -3163,6 +3174,40 @@ class ProductSuiteGuiTests(TempDirMixin, unittest.TestCase):
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_pull_locks_original_reordering_without_changing_saved_order(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
project, assets = self._create_project_with_assets(temp_dir, config, 2)
|
||||
tab = ProductSuiteTab(config=config, db_path=config["db_path"])
|
||||
self.addCleanup(tab.close)
|
||||
state = tab._displayed_state
|
||||
state.account_alias = "alias-a"
|
||||
state.item_id = project.item_id
|
||||
state.project_id = project.id
|
||||
state.project_binding_state = project.binding_state
|
||||
tab._load_state(state)
|
||||
statuses = []
|
||||
tab._status = lambda message, level=None: statuses.append((message, level))
|
||||
|
||||
state.pull_run_token = "pull-in-progress"
|
||||
tab._apply_running_state(state)
|
||||
self.assertTrue(tab.original_list.isEnabled())
|
||||
self.assertFalse(tab.original_list.reordering_enabled())
|
||||
tab.reorder_originals([assets[1].id, assets[0].id])
|
||||
self.assertEqual([assets[0].id, assets[1].id], tab.original_list.asset_ids())
|
||||
self.assertEqual(
|
||||
("正在拉取蝦皮主图,完成后可调整图片顺序", "warning"),
|
||||
statuses[-1],
|
||||
)
|
||||
|
||||
state.pull_run_token = ""
|
||||
tab._apply_running_state(state)
|
||||
self.assertTrue(tab.original_list.reordering_enabled())
|
||||
tab.reorder_originals([assets[1].id, assets[0].id])
|
||||
self.assertEqual([assets[1].id, assets[0].id], tab.original_list.asset_ids())
|
||||
|
||||
self.assert_removed(temp_dir)
|
||||
|
||||
def test_stopping_pull_cancels_only_current_pull_downloads(self):
|
||||
with self.make_temp_dir() as temp_dir:
|
||||
config = self._config(temp_dir)
|
||||
|
||||
Reference in New Issue
Block a user