T-569 增加封面画廊任务切换
This commit is contained in:
+168
-15
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user