feat(product-suite): persist context and confirm actions
This commit is contained in:
+143
-19
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user