feat: track settings changes and enforce Chinese UI copy

This commit is contained in:
chengma
2026-07-06 11:32:02 +08:00
parent 26e59c8cea
commit 19eaa794cf
10 changed files with 430 additions and 19 deletions
+47
View File
@@ -27,6 +27,9 @@ class MainWindow(QMainWindow):
)
self.setWindowTitle("cmshopee")
self.resize(1180, 760)
self._settings_tab_index = TAB_TITLES.index("⑤ 设置")
self._last_tab_index = 0
self._reverting_tab_change = False
self.tabs = QTabWidget()
self.tabs.setObjectName("mainTabs")
self.tabs.setStyleSheet(TAB_STYLE)
@@ -82,8 +85,52 @@ class MainWindow(QMainWindow):
widget.refresh_tasks()
def _on_tab_changed(self, index):
if self._reverting_tab_change:
self._last_tab_index = index
self.statusBar().showMessage(f"当前:{self.tabs.tabText(index)}")
return
previous = self._last_tab_index
if previous == self._settings_tab_index and index != previous:
if not self._confirm_leave_settings_tab():
self._reverting_tab_change = True
try:
self.tabs.setCurrentIndex(previous)
finally:
self._reverting_tab_change = False
self._last_tab_index = previous
self.statusBar().showMessage(f"当前:{self.tabs.tabText(previous)}")
return
self._last_tab_index = index
self.statusBar().showMessage(f"当前:{self.tabs.tabText(index)}")
def _settings_tab(self):
return self.tabs.widget(self._settings_tab_index)
def _confirm_leave_settings_tab(self):
settings_tab = self._settings_tab()
if not hasattr(settings_tab, "is_dirty") or not settings_tab.is_dirty():
return True
box = QMessageBox(self)
box.setWindowTitle("未保存更改")
box.setText("⑤ 设置有未保存更改。要先保存再离开吗?")
save_button = box.addButton("保存", QMessageBox.AcceptRole)
discard_button = box.addButton("放弃", QMessageBox.DestructiveRole)
cancel_button = box.addButton("取消", QMessageBox.RejectRole)
box.setDefaultButton(save_button)
box.exec()
clicked = box.clickedButton()
if clicked is save_button:
return bool(settings_tab.save_app_settings())
if clicked is discard_button:
return bool(settings_tab.discard_unsaved_changes())
return False
def closeEvent(self, event):
if self._confirm_leave_settings_tab():
event.accept()
return
event.ignore()
def open_accounts_tab(self):
self.tabs.setCurrentIndex(TAB_TITLES.index("④ 账号管理"))
+118 -6
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from contextlib import contextmanager
from ..widgets import *
from ..workers import AIModelTestWorker as _RealAIModelTestWorker
from ..workers import CMHubSettingsWorker as _RealCMHubSettingsWorker
@@ -63,6 +65,8 @@ class SettingsTab(QWidget):
self._loaded_cmhub_api_key = ""
self._cmhub_auto_refresh_done = False
self._compat_test_item_id = ""
self._dirty = False
self._suspend_dirty = 0
self.backend_combo = QComboBox()
self.backend_combo.setObjectName("aiBackendCombo")
@@ -173,6 +177,10 @@ class SettingsTab(QWidget):
self.cdp_ready_timeout_spin.setObjectName("cdpReadyTimeoutSpin")
self.cdp_ready_timeout_spin.setRange(1, 3600)
self.save_config_button = QPushButton("保存设置")
self.unsaved_changes_label = QLabel("● 未保存更改")
self.unsaved_changes_label.setObjectName("settingsUnsavedChangesLabel")
self.unsaved_changes_label.setStyleSheet("color: #bc4c00; font-weight: 600;")
self.unsaved_changes_label.setVisible(False)
self.allow_real_submit_checkbox = QCheckBox("允许真实提交线上商品")
self.allow_real_submit_checkbox.setObjectName("allowRealSubmitCheckbox")
self.allow_cover_update_checkbox = QCheckBox("允许更新封面")
@@ -351,7 +359,12 @@ class SettingsTab(QWidget):
panel_layout.addSpacing(18)
panel_layout.addWidget(self.infrastructure_section_title)
panel_layout.addLayout(path_form)
panel_layout.addWidget(self.save_config_button)
save_settings_layout = QHBoxLayout()
save_settings_layout.setContentsMargins(0, 0, 0, 0)
save_settings_layout.addWidget(self.save_config_button)
save_settings_layout.addWidget(self.unsaved_changes_label)
save_settings_layout.addStretch(1)
panel_layout.addLayout(save_settings_layout)
panel_layout.addStretch(1)
scroll = QScrollArea()
@@ -380,9 +393,12 @@ class SettingsTab(QWidget):
self._update_response_timeout_label
)
self.save_config_button.clicked.connect(self.save_app_settings)
self._connect_dirty_signals()
self.refresh_models()
self._populate_app_settings()
with self._dirty_tracking_suspended():
self.refresh_models()
self._populate_app_settings()
self._set_dirty(False)
def _three_column_form(self, fields):
layout = QGridLayout()
@@ -436,6 +452,92 @@ class SettingsTab(QWidget):
if self.status_callback is not None:
self.status_callback(message)
@contextmanager
def _dirty_tracking_suspended(self):
self._suspend_dirty += 1
try:
yield
finally:
self._suspend_dirty -= 1
def _connect_dirty_signals(self):
line_edits = (
self.cmhub_base_url_edit,
self.cmhub_api_key_edit,
self.name_edit,
self.model_id_edit,
self.url_edit,
self.api_key_edit,
self.chrome_path_edit,
self.user_data_root_edit,
self.image_dir_edit,
self.db_path_edit,
)
combos = (
self.backend_combo,
self.cmhub_title_alias_combo,
self.cmhub_image_alias_combo,
self.category_combo,
self.api_type_combo,
self.default_text_model_combo,
self.default_image_model_combo,
self.resolution_combo,
)
spin_boxes = (
self.cmhub_connect_timeout_spin,
self.connect_timeout_spin,
self.title_concurrency_spin,
self.image_concurrency_spin,
self.retry_spin,
self.jpg_quality_spin,
self.default_debug_port_spin,
self.debug_port_start_spin,
self.debug_port_end_spin,
self.cdp_ready_timeout_spin,
self.max_items_per_run_spin,
self.max_parallel_accounts_spin,
)
checkboxes = (
self.cmhub_check_balance_checkbox,
self.enabled_checkbox,
self.allow_real_submit_checkbox,
self.allow_cover_update_checkbox,
self.close_success_tab_checkbox,
self.parallel_accounts_checkbox,
)
for widget in line_edits:
widget.textChanged.connect(self._mark_dirty)
for widget in combos:
widget.currentIndexChanged.connect(self._mark_dirty)
for widget in spin_boxes:
widget.valueChanged.connect(self._mark_dirty)
for widget in checkboxes:
widget.toggled.connect(self._mark_dirty)
def _mark_dirty(self, *args):
if self._suspend_dirty > 0:
return
self._set_dirty(True)
def _set_dirty(self, dirty):
self._dirty = bool(dirty)
self.unsaved_changes_label.setVisible(self._dirty)
def is_dirty(self):
return self._dirty
def discard_unsaved_changes(self):
try:
saved = appconfig.load_config(self.config_path)
except Exception as exc:
self._show_error(exc)
return False
self._replace_config(saved)
self._populate_app_settings()
self._set_dirty(False)
self._set_status("已放弃未保存更改")
return True
def _maybe_auto_refresh_cmhub_models(self):
if self._cmhub_auto_refresh_done:
return
@@ -594,7 +696,7 @@ class SettingsTab(QWidget):
def save_app_settings(self, checked=False):
settings = self._app_settings_values()
if settings is None:
return
return False
cmhub_key = self.cmhub_api_key_edit.text()
if self._should_warn_plaintext_cmhub_api_key(cmhub_key):
self._show_plaintext_cmhub_api_key_warning()
@@ -606,11 +708,13 @@ class SettingsTab(QWidget):
saved = appconfig.save_config(settings, path=self.config_path)
except Exception as exc:
self._show_error(exc)
return
return False
self._replace_config(saved)
self._populate_app_settings()
self._set_dirty(False)
self._set_status("设置已保存")
QMessageBox.information(self, "保存设置", "设置已保存")
return True
def _app_settings_values(self):
start_port = self.debug_port_start_spin.value()
@@ -700,6 +804,10 @@ class SettingsTab(QWidget):
self.config.update(internal)
def _populate_app_settings(self):
if self._suspend_dirty <= 0:
with self._dirty_tracking_suspended():
self._populate_app_settings()
return
self._populate_role_model_combos()
ai_cfg = appconfig.ai_config(self.config)
self._set_combo_by_data(self.backend_combo, "cmhub")
@@ -1011,7 +1119,7 @@ class SettingsTab(QWidget):
image_count = self._cmhub_alias_count("image")
balance = payload.get("points_balance")
balance_text = f";余额 {balance}" if balance is not None else ""
message = f"cmhub 连接成功:生文别名 {title_count} 个,生图别名 {image_count} 个{balance_text}"
message = f"cmhub 连接成功:生文别名 {title_count} 个,生图别名 {image_count} 个{balance_text};别名已拉取,记得点『保存设置』持久化"
self.cmhub_result_label.setText(message)
self._set_status(message)
@@ -1021,6 +1129,10 @@ class SettingsTab(QWidget):
self._set_status(message)
def _populate_cmhub_alias_combos(self, models, title_selected="", image_selected=""):
if self._suspend_dirty <= 0:
with self._dirty_tracking_suspended():
self._populate_cmhub_alias_combos(models, title_selected, image_selected)
return
self._populate_cmhub_alias_combo(
self.cmhub_title_alias_combo,
models,