feat(product-suite): persist context and confirm actions

This commit is contained in:
chengma
2026-07-16 19:12:21 +08:00
parent 717ed2e35f
commit c9afaad24b
8 changed files with 459 additions and 36 deletions
+143 -19
View File
@@ -1221,13 +1221,21 @@ class ProductSuiteTab(QWidget):
box.setText(str(message))
box.exec()
def _confirm(self, title, message, *, destructive=False):
def _confirm(
self,
title,
message,
*,
destructive=False,
confirm_text="确认",
cancel_text="取消",
):
box = QMessageBox(self)
box.setIcon(QMessageBox.Warning if destructive else QMessageBox.Question)
box.setWindowTitle(str(title))
box.setText(str(message))
confirm_button = box.addButton("确认", QMessageBox.AcceptRole)
box.addButton("取消", QMessageBox.RejectRole)
confirm_button = box.addButton(str(confirm_text), QMessageBox.AcceptRole)
box.addButton(str(cancel_text), QMessageBox.RejectRole)
if not destructive:
box.setDefaultButton(confirm_button)
box.exec()
@@ -1302,15 +1310,20 @@ class ProductSuiteTab(QWidget):
if source is not None
else appconfig.product_suite_last_settings(self.config)
)
valid_aliases = {account.alias for account in self.accounts}
recent_alias = appconfig.product_suite_last_account_alias(self.config)
account_alias = source.account_alias if source is not None else recent_alias
if account_alias not in valid_aliases:
account_alias = self.accounts[0].alias if self.accounts else ""
if source is None and account_alias != recent_alias:
self._persist_last_account_alias(account_alias)
state = SuiteTaskState(
key=self._next_key,
serial=self._next_serial,
account_alias=(source.account_alias if source is not None else ""),
account_alias=account_alias,
prompt=(source.prompt if source is not None else ""),
settings=product_suite.normalize_suite_settings(initial_settings),
)
if not state.account_alias and self.accounts:
state.account_alias = self.accounts[0].alias
self._next_key += 1
self._next_serial += 1
return self._append_task_state(state)
@@ -1496,6 +1509,7 @@ class ProductSuiteTab(QWidget):
return
self._clear_project_binding(state)
state.account_alias = alias
self._persist_last_account_alias(alias)
self._update_context_actions(state)
def _update_account_tooltip(self):
@@ -1597,6 +1611,14 @@ class ProductSuiteTab(QWidget):
def _account_for_alias(self, alias):
return next((account for account in self.accounts if account.alias == alias), None)
def _account_context_label(self, state):
account = self._account_for_alias(getattr(state, "account_alias", ""))
alias = str(getattr(state, "account_alias", "") or "")
name = str(getattr(account, "account_name", "") or "").strip()
if name and name != alias:
return "%s(%s)" % (name, alias)
return name or alias or "未选择店铺"
def _state_project(self, state, *, include_deleted=False):
if state is None or state.project_id is None:
return None
@@ -1687,7 +1709,7 @@ class ProductSuiteTab(QWidget):
self._flush_prompt_save(state)
return project
def _create_draft_project(self, state):
def _create_draft_project(self, state, *, announce=True):
if not self._has_account_context(state):
return None
account = self._account_for_alias(state.account_alias)
@@ -1708,7 +1730,8 @@ class ProductSuiteTab(QWidget):
self._set_task_title(state)
if state is self._displayed_state:
self._update_context_actions(state)
self._status("已创建临时草稿,可继续添加本地图片", "info")
if announce:
self._status("已创建临时草稿,可继续添加本地图片", "info")
return project
def _prompt_save_timer(self, state):
@@ -1724,7 +1747,17 @@ class ProductSuiteTab(QWidget):
return timer
def _schedule_prompt_save(self, state):
if state is None or state.project_id is None:
if state is None:
return
if state.project_id is None:
if (
not str(state.prompt or "").strip()
or not state.account_alias
or self._account_for_alias(state.account_alias) is None
):
self._cancel_prompt_save(state)
return
self._prompt_save_timer(state).start()
return
if state.prompt == state.last_saved_prompt:
self._cancel_prompt_save(state)
@@ -1752,9 +1785,19 @@ class ProductSuiteTab(QWidget):
self._persist_prompt(state)
def _persist_prompt(self, state):
if state is None or state.project_id is None:
if state is None:
return True
prompt = str(state.prompt or "")
if state.project_id is None:
if not prompt.strip():
return True
if (
not state.account_alias
or self._account_for_alias(state.account_alias) is None
):
return True
if self._create_draft_project(state, announce=False) is None:
return False
if prompt == state.last_saved_prompt:
return True
try:
@@ -1820,6 +1863,29 @@ class ProductSuiteTab(QWidget):
self.config.clear()
self.config.update(saved)
def _persist_last_account_alias(self, alias):
value = str(alias or "").strip()
if value == appconfig.product_suite_last_account_alias(self.config):
return
try:
if os.path.exists(self.config_path):
saved = appconfig.update_config(
{"product_suite": {"last_account_alias": value}},
path=self.config_path,
)
else:
base = dict(self.config)
suite = base.get("product_suite", {})
suite = dict(suite) if isinstance(suite, dict) else {}
suite["last_account_alias"] = value
base["product_suite"] = suite
saved = appconfig.save_config(base, path=self.config_path)
except Exception as exc:
self._status("商品套图最近店铺保存失败:%s" % _user_error(exc), "danger")
return
self.config.clear()
self.config.update(saved)
def _on_prompt_changed(self):
if self._loading or self._displayed_state is None:
return
@@ -2190,9 +2256,24 @@ class ProductSuiteTab(QWidget):
self._status("当前任务正在拉取蝦皮主图", "info")
return
existing = [asset for asset in self._original_assets(state, include_missing=False) if _asset_usable(asset)]
if existing and not self._confirm(
"覆盖拉取蝦皮主图",
"本地已有商品原图。继续拉取会刷新蝦皮原图列表,本地上传图片会保留。确认继续吗?",
message = (
"店铺:%s\n"
"商品ID:%s\n"
"当前可用商品原图:%d张\n\n"
"将读取蝦皮主图并在后台下载,不会修改蝦皮线上商品。"
% (
self._account_context_label(state),
state.item_id,
len(existing),
)
)
if existing:
message += "\n继续拉取会刷新蝦皮原图列表,本地手动添加图片会保留。"
if not self._confirm(
"确认拉取蝦皮主图",
message,
confirm_text="确认拉取",
cancel_text="取消",
):
return
worker = ImageStudioPullImagesWorker(
@@ -2564,6 +2645,7 @@ class ProductSuiteTab(QWidget):
return False
retry_job_id = int(retry_job_id) if retry_job_id is not None else None
retrying = retry_job_id is not None
confirm_batch = specs is None and not retrying
if state is self._displayed_state:
self._save_controls_to_state(state)
template_text = None
@@ -2597,12 +2679,17 @@ class ProductSuiteTab(QWidget):
if not specs:
self._message("生成数量为0", "请至少把一个套图分类的数量设为1。")
return False
if len(specs) > product_suite.MAX_GENERATION_COUNT_WITHOUT_CONFIRM:
if not self._confirm(
"确认生成数量",
"本轮将生成%d张图片,预计耗时和点数较多。确认继续吗?" % len(specs),
):
return False
if confirm_batch and not self._confirm(
"确认生成商品套图",
self._generation_confirmation_message(
state,
local_assets,
specs,
),
confirm_text="确认生成",
cancel_text="返回修改",
):
return False
self._persist_state(state)
run_token = uuid.uuid4().hex
worker = ProductSuiteGenerateWorker(
@@ -2661,6 +2748,43 @@ class ProductSuiteTab(QWidget):
)
return True
def _generation_confirmation_message(self, state, local_assets, specs):
counts = {}
for spec in specs:
category = str(
spec.get("category")
or spec.get("job_type")
or "未分类"
)
counts[category] = counts.get(category, 0) + 1
ordered = list(product_suite.category_order(state.settings))
ordered.extend(name for name in counts if name not in ordered)
category_lines = [
"%s:%d张" % (name, counts[name])
for name in ordered
if counts.get(name, 0) > 0
]
return (
"店铺:%s\n"
"商品ID:%s\n"
"可用商品原图:%d张\n"
"每张上传图分别作为主图生成:%s\n"
"%s\n"
"图片比例:%s\n"
"生成总数:%d张\n"
"商品卖点:已填写\n\n"
"本次生成会消耗 cmhub 点数。"
% (
self._account_context_label(state),
state.item_id or "未绑定商品",
len(local_assets),
"是" if state.settings.get("per_image_primary") else "否",
"\n".join(category_lines),
state.settings.get("ratio") or "1:1",
len(specs),
)
)
def _generation_signal_token(self, payload=None):
token = str((payload or {}).get("run_token") or "")
sender = self.sender()