T-576 增加生成和更新内容模式

This commit is contained in:
chengma
2026-07-09 17:44:03 +08:00
parent 9827c8c6d8
commit ab87be8223
17 changed files with 679 additions and 183 deletions
+131 -24
View File
@@ -17,6 +17,11 @@ def WriteBackWorker(*args, **kwargs):
class ApplyTab(QWidget):
"""Tab 3: list generated tasks and confirm the update scope."""
UPDATE_MODE_ITEMS = [
("只更新标题", "title"),
("只更新封面", "cover"),
("更新标题和封面", "title_cover"),
]
STATUS_FILTERS = [
("已生成", "generated"),
("失败", "failed"),
@@ -36,6 +41,7 @@ class ApplyTab(QWidget):
):
super().__init__(parent)
self.config = appconfig.load_config() if config is None else config
self.config_path = self.config.get("config_path") or appconfig.CONFIG_PATH
self.db_path = _database_path(db_path, self.config)
self.status_callback = status_callback
self.open_accounts_callback = open_accounts_callback
@@ -110,8 +116,20 @@ class ApplyTab(QWidget):
self.write_back_button = QPushButton("回写结果到 Excel")
self.stop_update_button.setEnabled(False)
self.write_back_button.setEnabled(False)
self.update_mode_combo = QComboBox()
self.update_mode_combo.setObjectName("applyUpdateModeCombo")
self.update_mode_combo.setToolTip("选择本轮要更新标题、封面,或同时更新标题和封面")
for label, value in self.UPDATE_MODE_ITEMS:
self.update_mode_combo.addItem(label, value)
self._set_combo_by_data(
self.update_mode_combo,
self._shopee_update_config().get("update_mode", "title"),
)
action_layout = QHBoxLayout()
action_layout.addWidget(QLabel("更新内容"))
action_layout.addWidget(self.update_mode_combo)
action_layout.addSpacing(16)
action_layout.addWidget(self.preview_update_button)
action_layout.addWidget(self.start_update_button)
action_layout.addWidget(self.stop_update_button)
@@ -137,6 +155,7 @@ class ApplyTab(QWidget):
self.refresh_button.clicked.connect(self.refresh_tasks)
self.preview_update_button.clicked.connect(self.preview_update)
self.start_update_button.clicked.connect(self.start_update)
self.update_mode_combo.currentIndexChanged.connect(self._on_update_mode_changed)
self.stop_update_button.clicked.connect(self.stop_update)
self.reset_update_button.clicked.connect(self.reset_apply_status)
self.task_table.customContextMenuRequested.connect(self.show_task_context_menu)
@@ -227,12 +246,18 @@ class ApplyTab(QWidget):
return
tasks = [
task for task in self.model.tasks
if self._is_actionable_task(task)
if self._is_update_candidate_task(task)
]
if not tasks:
self._set_status("当前筛选结果没有可更新任务")
return
update_cfg = self._shopee_update_config()
update_mode = self._current_update_mode()
content_error = self._update_content_error(tasks, update_mode)
if content_error:
QMessageBox.warning(self, "更新内容未生成", content_error)
self._set_status(content_error.replace("\n", " "))
return
dry_run = bool(dry_run)
safety_error = self._update_safety_error(tasks, dry_run=dry_run)
if safety_error:
@@ -256,6 +281,7 @@ class ApplyTab(QWidget):
config=self.config,
close_success_tab=bool(update_cfg.get("close_success_tab", False)),
dry_run=dry_run,
update_mode=update_mode,
parallel_accounts=bool(update_cfg.get("parallel_accounts", False)),
max_parallel_accounts=max(
1,
@@ -368,11 +394,10 @@ class ApplyTab(QWidget):
return self.model.task_at(0)
return None
def _is_actionable_task(self, task):
def _is_update_candidate_task(self, task):
return (
getattr(task, "stage", None) == "generated"
and getattr(task, "status", None) in {"success", "pending", "failed"}
and bool(getattr(task, "new_title", None) or getattr(task, "new_cover_path", None))
)
def _populate_batch_filter(self, batches, selected_batch):
@@ -464,7 +489,9 @@ class ApplyTab(QWidget):
def _confirmation_message(self, tasks, dry_run=False):
update_cfg = self._shopee_update_config()
cover_text = "允许" if update_cfg.get("allow_cover_update") else "不允许"
update_mode = self._current_update_mode()
update_mode_text = self._update_mode_label(update_mode)
cover_warning = "\n本轮将更新线上封面,请确认封面内容无误。" if appconfig.update_mode_includes_cover(update_mode) else ""
close_text = "是" if update_cfg.get("close_success_tab") else "否"
batch_size = max(1, int(update_cfg.get("max_items_per_run", 1) or 1))
batch_count = (len(tasks) + batch_size - 1) // batch_size if tasks else 0
@@ -484,13 +511,15 @@ class ApplyTab(QWidget):
+ f"店铺:{self._shop_filter_label()}\n"
+ f"商品ID:{self._item_filter_label()}\n"
+ f"状态:{self._status_label()}\n"
+ f"更新内容:{update_mode_text}\n"
+ f"任务数:{len(tasks)}\n"
+ f"预计批次:{batch_count}\n\n"
+ "安全设置:"
+ f"封面更新={cover_text},"
+ f"每批最大更新条数={batch_size},"
+ f"成功后关闭新页={close_text},"
+ f"多账号并行={parallel_text}\n\n"
+ cover_warning
+ ("\n\n" if cover_warning else "")
+ (
"检查只写运行日志,不打开蝦皮、不点击「更新」、不改任务状态。"
if dry_run
@@ -507,17 +536,6 @@ class ApplyTab(QWidget):
"设置未开启「允许真实提交线上商品」,已阻止本次更新。\n"
"请到⑤设置 > 蝦皮更新安全开启该开关后再开始更新。"
)
if not update_cfg.get("allow_cover_update", False):
cover_tasks = [
str(getattr(task, "item_id", ""))
for task in tasks
if getattr(task, "new_cover_path", None)
]
if cover_tasks:
return (
"设置未开启「允许更新封面」,当前任务包含新封面路径,已阻止本次更新。\n"
"请到⑤设置 > 蝦皮更新安全开启该开关,或先筛掉含新封面的任务。"
)
return None
def _show_update_safety_error(self, message):
@@ -534,13 +552,99 @@ class ApplyTab(QWidget):
self.open_settings_callback()
def _shopee_update_config(self):
defaults = appconfig.default_config().get("shopee_update", {})
loaded = self.config.get("shopee_update", {})
if not isinstance(loaded, dict):
loaded = {}
merged = dict(defaults)
merged.update(loaded)
return merged
return appconfig.shopee_update_config(self.config)
def _current_update_mode(self):
return appconfig.normalize_update_mode(
self.update_mode_combo.currentData(),
allow_cover_update=False,
)
def _update_mode_label(self, mode):
mode = appconfig.normalize_update_mode(mode)
return {
"title": "只更新标题",
"cover": "只更新封面",
"title_cover": "更新标题和封面",
}.get(mode, "只更新标题")
def _set_combo_by_data(self, combo, value):
index = combo.findData(value)
combo.setCurrentIndex(index if index >= 0 else 0)
def _on_update_mode_changed(self, index=None):
previous = self._shopee_update_config().get("update_mode", "title")
if self._save_update_mode_setting(show_status=True):
return
self.update_mode_combo.blockSignals(True)
self._set_combo_by_data(self.update_mode_combo, previous)
self.update_mode_combo.blockSignals(False)
def _save_update_mode_setting(self, show_status=True):
update_mode = self._current_update_mode()
update_cfg = self._shopee_update_config()
update_cfg["update_mode"] = update_mode
update_cfg["allow_cover_update"] = appconfig.update_mode_includes_cover(update_mode)
payload = {
key: value
for key, value in self.config.items()
if key not in {"config_path", "ai_models_path", "cmhub_config_path", "data_dir"}
}
payload["shopee_update"] = update_cfg
try:
saved = appconfig.save_config(payload, path=self.config_path)
except Exception as exc:
self._set_status(f"更新内容设置保存失败:{exc}")
return False
internal = {
key: value
for key, value in self.config.items()
if key in {"config_path", "ai_models_path", "cmhub_config_path", "data_dir"}
}
self.config.clear()
self.config.update(saved)
self.config.update(internal)
if self.config_path != appconfig.CONFIG_PATH:
self.config["config_path"] = self.config_path
if show_status:
self._set_status(f"更新内容已设置为:{self._update_mode_label(update_mode)}")
return True
def _update_content_error(self, tasks, update_mode):
missing_title = [
task for task in tasks
if appconfig.update_mode_includes_title(update_mode)
and not str(getattr(task, "new_title", "") or "").strip()
]
missing_cover = [
task for task in tasks
if appconfig.update_mode_includes_cover(update_mode)
and not str(getattr(task, "new_cover_path", "") or "").strip()
]
if not missing_title and not missing_cover:
return None
lines = []
mode_text = self._update_mode_label(update_mode)
if missing_title:
lines.append(f"当前筛选结果中有 {len(missing_title)} 条缺少新标题,不能执行“{mode_text}”。")
lines.append("请先回到②AI生成选择“只生成标题”或“生成标题和封面”。")
lines.append(f"示例商品ID:{self._sample_item_ids(missing_title)}")
if missing_cover:
if lines:
lines.append("")
lines.append(f"当前筛选结果中有 {len(missing_cover)} 条缺少新封面,不能执行“{mode_text}”。")
lines.append("请先回到②AI生成选择“只生成封面”或“生成标题和封面”。")
lines.append(f"示例商品ID:{self._sample_item_ids(missing_cover)}")
return "\n".join(lines)
def _sample_item_ids(self, tasks):
values = [
str(getattr(task, "item_id", "") or "").strip()
for task in tasks[:5]
]
values = [value for value in values if value]
suffix = f" 等 {len(tasks)} 条" if len(tasks) > 5 else ""
return "、".join(values) + suffix if values else "无"
def _set_apply_running(self, running):
self.preview_update_button.setEnabled(not running)
@@ -552,6 +656,7 @@ class ApplyTab(QWidget):
self.shop_filter.setEnabled(not running)
self.item_filter.setEnabled(not running)
self.status_filter.setEnabled(not running)
self.update_mode_combo.setEnabled(not running)
self._update_write_back_button()
def _set_result_write_back_running(self, running):
@@ -563,6 +668,7 @@ class ApplyTab(QWidget):
self.shop_filter.setEnabled(not running)
self.item_filter.setEnabled(not running)
self.status_filter.setEnabled(not running)
self.update_mode_combo.setEnabled(not running)
self.write_back_button.setEnabled(False if running else bool(self._active_batch_ids()))
def _forget_apply_thread(self, thread):
@@ -637,8 +743,9 @@ class ApplyTab(QWidget):
def _reset_run_log(self, tasks, dry_run=False, batch_size=1):
self.run_log_view.clear()
action = "检查" if dry_run else "更新"
mode_text = self._update_mode_label(self._current_update_mode())
self._append_run_log(
f"本轮{action}开始:任务 {len(tasks)} 条,每批 {batch_size} 条"
f"本轮{action}开始:任务 {len(tasks)} 条,更新内容:{mode_text},每批 {batch_size} 条"
)
def _load_latest_run_log(self):