T-576 增加生成和更新内容模式
This commit is contained in:
+131
-24
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user