T-569 增加封面画廊任务切换

This commit is contained in:
chengma
2026-07-09 12:19:29 +08:00
parent ae4daacbbf
commit 4e98a86fb3
3 changed files with 447 additions and 18 deletions
+168 -15
View File
@@ -86,12 +86,19 @@ class CoverGalleryDialog(QDialog):
bulk_running_checker=None,
regenerate_running_callback=None,
gallery_updated_callback=None,
visible_tasks=None,
task_index=None,
account_by_alias=None,
):
super().__init__(parent)
self.task = task
self.image_root = image_root
self.db_path = db_path
self.account = account
self.visible_tasks = list(visible_tasks or [task])
self.task_index = self._initial_task_index(task, task_index)
self.task = self.visible_tasks[self.task_index] if self.visible_tasks else task
self.account_by_alias = dict(account_by_alias or {})
self.fallback_account = account
self.account = self._account_for_task(self.task) or account
self.config = config or {}
self.prompt_values = dict(prompt_values or {})
self.diagnostic_log_dir = diagnostic_log_dir
@@ -106,8 +113,9 @@ class CoverGalleryDialog(QDialog):
self.button_group.setExclusive(True)
self.regenerate_worker = None
self.regenerate_thread = None
self.regenerating_task_id = None
self._regenerate_last_error = ""
self.candidates = image_paths.list_task_cover_candidates(image_root, task, account=account)
self.candidates = image_paths.list_task_cover_candidates(image_root, self.task, account=self.account)
self.setWindowTitle(f"封面画廊:{getattr(task, 'item_id', '')}")
layout = QVBoxLayout(self)
@@ -117,6 +125,12 @@ class CoverGalleryDialog(QDialog):
layout.addLayout(body_layout, 1)
button_layout = QHBoxLayout()
self.previous_button = QPushButton("上一条")
self.next_button = QPushButton("下一条")
self.previous_button.clicked.connect(lambda checked=False: self.switch_task(-1))
self.next_button.clicked.connect(lambda checked=False: self.switch_task(1))
button_layout.addWidget(self.previous_button)
button_layout.addWidget(self.next_button)
self.regenerate_button = QPushButton("重新生成")
self.regenerate_button.clicked.connect(self.regenerate_cover)
button_layout.addWidget(self.regenerate_button)
@@ -131,6 +145,7 @@ class CoverGalleryDialog(QDialog):
self._sync_initial_selection()
self._update_status()
self._update_navigation_buttons()
self._update_regenerate_button_state()
self._fit_to_screen()
@@ -138,17 +153,29 @@ class CoverGalleryDialog(QDialog):
panel = QWidget()
layout = QVBoxLayout(panel)
layout.addWidget(QLabel("旧封面"))
old_path = getattr(self.task, "old_cover_path", None)
layout.addWidget(
_build_image_label(
old_path,
self.OLD_THUMBNAIL_SIZE,
empty_text="暂无旧封面",
)
self.old_cover_label = _build_image_label(
getattr(self.task, "old_cover_path", None),
self.OLD_THUMBNAIL_SIZE,
empty_text="暂无旧封面",
)
layout.addWidget(self.old_cover_label)
layout.addStretch(1)
return panel
def _initial_task_index(self, task, task_index):
if self.visible_tasks:
if task_index is not None and 0 <= int(task_index) < len(self.visible_tasks):
return int(task_index)
task_id = getattr(task, "id", None)
for index, visible_task in enumerate(self.visible_tasks):
if getattr(visible_task, "id", None) == task_id:
return index
return 0
def _account_for_task(self, task):
alias = str(getattr(task, "alias", "") or "").strip()
return self.account_by_alias.get(alias) or self.fallback_account
def _candidate_panel(self):
panel = QWidget()
layout = QVBoxLayout(panel)
@@ -207,6 +234,7 @@ class CoverGalleryDialog(QDialog):
thumbnail.setMinimumSize(self.THUMBNAIL_SIZE, self.THUMBNAIL_SIZE)
radio = QRadioButton(_candidate_label(candidate_path, self.current_path))
radio.setObjectName(f"coverCandidateRadio{index}")
radio.setFocusPolicy(Qt.ClickFocus)
radio.toggled.connect(
lambda checked, path=candidate_path: self._select_candidate(path) if checked else None
)
@@ -235,6 +263,9 @@ class CoverGalleryDialog(QDialog):
self._update_status()
def _update_status(self):
if self._is_current_task_regenerating():
self.status_label.setText("封面重新生成中,请稍候")
return
if not self.candidates:
self.status_label.setText("暂无生成封面图片")
self.save_button.setEnabled(False)
@@ -254,12 +285,16 @@ class CoverGalleryDialog(QDialog):
self.status_label.setText("已选择新的生成封面,保存后用于后续更新蝦皮")
def save_selection(self, checked=False):
return self._save_selected_cover(close_on_success=True)
def _save_selected_cover(self, close_on_success=False):
if not self.selected_path:
QMessageBox.warning(self, "封面画廊", "请先选择一张生成封面")
return False
if _same_file(self.selected_path, self.current_path):
self.changed = False
self.accept()
if close_on_success:
self.accept()
return True
if self._needs_committed_confirmation() and not self._confirm_committed_save():
return False
@@ -268,9 +303,17 @@ class CoverGalleryDialog(QDialog):
except Exception as exc:
QMessageBox.warning(self, "封面画廊", f"保存当前封面失败:{exc}")
return False
fresh_task = db.get_task(self.task.id, path=self.db_path)
if fresh_task is not None:
self.task = fresh_task
self.visible_tasks[self.task_index] = fresh_task
self.current_path = _normalize_file_path(self.selected_path)
self.changed = True
self.accept()
self._update_status()
if self.gallery_updated_callback is not None:
self.gallery_updated_callback(self.task)
if close_on_success:
self.accept()
return True
def regenerate_cover(self, checked=False):
@@ -301,6 +344,7 @@ class CoverGalleryDialog(QDialog):
QMessageBox.warning(self, "重新生成封面", "任务不存在或已删除")
self.status_label.setText("重新生成封面失败:任务不存在或已删除")
return False
self.regenerating_task_id = getattr(fresh_task, "id", None)
self._reload_task_and_candidates(fresh_task)
self.status_label.setText(f"开始重新生成商品 {getattr(fresh_task, 'item_id', '')} 的封面图片")
worker = GenerateWorker(
@@ -349,6 +393,96 @@ class CoverGalleryDialog(QDialog):
box.exec()
return box.clickedButton() is save_button
def switch_task(self, offset):
target_index = self.task_index + int(offset)
if target_index < 0 or target_index >= len(self.visible_tasks):
return False
if not self._confirm_unsaved_selection():
return False
self._load_task_at_index(target_index)
return True
def _load_task_at_index(self, target_index):
self.task_index = int(target_index)
target_task = self.visible_tasks[self.task_index]
fresh_task = db.get_task(getattr(target_task, "id", None), path=self.db_path) or target_task
self.visible_tasks[self.task_index] = fresh_task
self.task = fresh_task
self.account = self._account_for_task(fresh_task)
self.setWindowTitle(f"封面画廊:{getattr(fresh_task, 'item_id', '')}")
_set_image_label(
self.old_cover_label,
getattr(fresh_task, "old_cover_path", None),
self.OLD_THUMBNAIL_SIZE,
"暂无旧封面",
)
self._reload_task_and_candidates(fresh_task)
self._update_navigation_buttons()
self._update_regenerate_button_state()
def _update_navigation_buttons(self):
self.previous_button.setEnabled(self.task_index > 0)
self.next_button.setEnabled(self.task_index < len(self.visible_tasks) - 1)
def _has_unsaved_selection(self):
if not self.selected_path:
return False
return not _same_file(self.selected_path, self.current_path)
def _confirm_unsaved_selection(self):
if not self._has_unsaved_selection():
return True
box = QMessageBox(self)
box.setWindowTitle("未保存封面选择")
box.setText("当前已选择另一张生成封面,但尚未保存。")
save_button = box.addButton("保存", QMessageBox.AcceptRole)
discard_button = box.addButton("不保存", QMessageBox.DestructiveRole)
box.addButton("取消", QMessageBox.RejectRole)
box.setDefaultButton(save_button)
box.exec()
clicked = box.clickedButton()
if clicked is save_button:
return self._save_selected_cover(close_on_success=False)
if clicked is discard_button:
return True
return False
def reject(self):
if self._confirm_unsaved_selection():
super().reject()
def closeEvent(self, event):
if self._confirm_unsaved_selection():
event.accept()
return
event.ignore()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Up and self._should_consume_navigation_key():
self.switch_task(-1)
event.accept()
return
if event.key() == Qt.Key_Down and self._should_consume_navigation_key():
self.switch_task(1)
event.accept()
return
super().keyPressEvent(event)
def _should_consume_navigation_key(self):
focus = QApplication.focusWidget()
if focus is None or focus is self:
return True
return not isinstance(
focus,
(
QPushButton,
QLineEdit,
QPlainTextEdit,
QComboBox,
QSpinBox,
),
)
def open_original_image(self, image_path):
dialog = OriginalImageDialog(image_path, self)
dialog.exec()
@@ -356,6 +490,13 @@ class CoverGalleryDialog(QDialog):
def _bulk_generate_running(self):
return bool(self.bulk_running_checker and self.bulk_running_checker())
def _is_current_task_regenerating(self):
return (
self.regenerate_thread is not None
and self.regenerating_task_id is not None
and getattr(self.task, "id", None) == self.regenerating_task_id
)
def _set_regenerate_running(self, running):
self.save_button.setEnabled(not running and self.selected_path is not None)
self.regenerate_button.setEnabled(not running and not self._bulk_generate_running())
@@ -379,6 +520,7 @@ class CoverGalleryDialog(QDialog):
def _reload_task_and_candidates(self, task=None):
if task is not None:
self.task = task
self.account = self._account_for_task(task)
self.current_path = _normalize_file_path(getattr(self.task, "new_cover_path", None))
self.selected_path = None
self.candidates = image_paths.list_task_cover_candidates(self.image_root, self.task, account=self.account)
@@ -431,6 +573,7 @@ class CoverGalleryDialog(QDialog):
self._finish_regenerate(summary, cancelled=True)
def _finish_regenerate(self, summary, cancelled=False):
self.regenerating_task_id = None
fresh_task = db.get_task(self.task.id, path=self.db_path)
if fresh_task is not None:
self._reload_task_and_candidates(fresh_task)
@@ -450,6 +593,7 @@ class CoverGalleryDialog(QDialog):
if self.regenerate_thread is thread:
self.regenerate_thread = None
self.regenerate_worker = None
self.regenerating_task_id = None
self._set_regenerate_running(False)
def _fit_to_screen(self):
@@ -488,10 +632,16 @@ def _build_image_label(image_path, size, empty_text):
label = QLabel()
label.setAlignment(Qt.AlignCenter)
label.setMinimumSize(size, size)
_set_image_label(label, image_path, size, empty_text)
return label
def _set_image_label(label, image_path, size, empty_text):
label.clear()
image = _load_image(image_path) if image_path else QImage()
if image.isNull():
label.setText(empty_text)
return label
return
label.setPixmap(
QPixmap.fromImage(
image.scaled(
@@ -502,7 +652,6 @@ def _build_image_label(image_path, size, empty_text):
)
)
)
return label
def _candidate_label(candidate_path, current_path):
@@ -1416,7 +1565,8 @@ class GenerateTab(QWidget):
def show_task_images(self, index):
if index.isValid() and index.column() == 3:
return
task = self.model.task_at(index.row()) if index.isValid() else self._selected_task()
row = index.row() if index.isValid() else self.task_table.currentIndex().row()
task = self.model.task_at(row) if row >= 0 else self._selected_task()
if task is None:
self._set_status("没有可预览的任务")
return
@@ -1436,6 +1586,9 @@ class GenerateTab(QWidget):
bulk_running_checker=lambda: self.generate_thread is not None,
regenerate_running_callback=self._set_cover_gallery_regenerate_running,
gallery_updated_callback=self._on_cover_gallery_updated,
visible_tasks=list(self.model.tasks),
task_index=row if row >= 0 else 0,
account_by_alias=self.model.account_by_alias,
)
if dialog.exec() == QDialog.Accepted:
self.refresh_tasks()