feat(product-suite): compact controls and remember settings
This commit is contained in:
@@ -112,6 +112,14 @@ DEFAULT_CONFIG = {
|
||||
"dry_run": False,
|
||||
"max_parallel_accounts": 1,
|
||||
},
|
||||
"product_suite": {
|
||||
"last_settings": {
|
||||
"platform": "Shopee",
|
||||
"country": "中国台湾",
|
||||
"language": "繁体中文",
|
||||
"ratio": "1:1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_AI_MODELS_CONFIG = {
|
||||
@@ -426,9 +434,22 @@ def _normalize_config_values(config, migrate_old_cmhub_connect_timeout=False):
|
||||
9999,
|
||||
DEFAULT_CONFIG["shopee_update"]["max_items_per_run"],
|
||||
)
|
||||
suite = config.get("product_suite")
|
||||
if not isinstance(suite, dict):
|
||||
suite = {}
|
||||
config["product_suite"] = suite
|
||||
suite["last_settings"] = _normalize_product_suite_last_settings(
|
||||
suite.get("last_settings")
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def _normalize_product_suite_last_settings(value):
|
||||
from . import product_suite
|
||||
|
||||
return product_suite.last_suite_settings(value)
|
||||
|
||||
|
||||
def _clamp_int(value, minimum, maximum, default):
|
||||
try:
|
||||
number = int(value)
|
||||
@@ -673,6 +694,14 @@ def ai_config(config=None) -> dict:
|
||||
return copy.deepcopy(_config_or_load(config).get("ai", DEFAULT_CONFIG["ai"]))
|
||||
|
||||
|
||||
def product_suite_last_settings(config=None) -> dict:
|
||||
cfg = _config_or_load(config)
|
||||
suite = cfg.get("product_suite", {})
|
||||
if not isinstance(suite, dict):
|
||||
suite = {}
|
||||
return _normalize_product_suite_last_settings(suite.get("last_settings"))
|
||||
|
||||
|
||||
def normalize_generate_mode(value=None, generate_cover=None) -> str:
|
||||
text = str(value or "").strip().lower()
|
||||
if text in AI_GENERATE_MODES:
|
||||
|
||||
+145
-35
@@ -30,6 +30,7 @@ from PySide6.QtWidgets import (
|
||||
QProgressBar,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QSplitter,
|
||||
QTabBar,
|
||||
QToolButton,
|
||||
@@ -381,9 +382,9 @@ class SuiteTaskState:
|
||||
class ProductSuiteTab(QWidget):
|
||||
"""Native PySide6 product-suite UI backed by image_studio services."""
|
||||
|
||||
PLATFORM_OPTIONS = ("Shopee", "Lazada", "TikTok Shop", "Amazon")
|
||||
COUNTRY_OPTIONS = ("中国台湾", "新加坡", "马来西亚", "菲律宾", "泰国", "越南")
|
||||
LANGUAGE_OPTIONS = ("繁体中文", "简体中文", "英文", "泰文", "越南文")
|
||||
PLATFORM_OPTIONS = product_suite.PLATFORMS
|
||||
COUNTRY_OPTIONS = product_suite.COUNTRIES
|
||||
LANGUAGE_OPTIONS = product_suite.LANGUAGES
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -443,6 +444,31 @@ class ProductSuiteTab(QWidget):
|
||||
self.task_tabs.setTabsClosable(True)
|
||||
self.task_tabs.setMovable(True)
|
||||
self.task_tabs.setExpanding(False)
|
||||
self.task_tabs.setFixedHeight(36)
|
||||
self.task_tabs.setStyleSheet(
|
||||
"""
|
||||
QTabBar#suiteTaskTabs::tab {
|
||||
min-width: 96px;
|
||||
min-height: 26px;
|
||||
max-height: 26px;
|
||||
padding: 3px 10px;
|
||||
margin-right: 4px;
|
||||
border: 1px solid #c9d1d9;
|
||||
border-bottom-color: #b8c0ca;
|
||||
background: #f4f6f8;
|
||||
color: #24292f;
|
||||
}
|
||||
QTabBar#suiteTaskTabs::tab:selected {
|
||||
background: #ffffff;
|
||||
border-color: #687785;
|
||||
border-bottom-color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
QTabBar#suiteTaskTabs::tab:hover:!selected {
|
||||
background: #eaf2ff;
|
||||
}
|
||||
"""
|
||||
)
|
||||
layout.addWidget(self.task_tabs, 1)
|
||||
self.add_task_button = QToolButton()
|
||||
self.add_task_button.setObjectName("suiteAddTaskButton")
|
||||
@@ -457,20 +483,38 @@ class ProductSuiteTab(QWidget):
|
||||
frame = QFrame()
|
||||
frame.setObjectName("suiteContextBar")
|
||||
layout = QHBoxLayout(frame)
|
||||
self.context_bar_layout = layout
|
||||
layout.setContentsMargins(10, 7, 10, 7)
|
||||
layout.setSpacing(8)
|
||||
self.history_button = QPushButton("历史生成")
|
||||
self.history_button.setObjectName("suiteHistoryButton")
|
||||
self.history_button.setCheckable(True)
|
||||
layout.addWidget(self.history_button)
|
||||
self.open_folder_button = QPushButton("打开结果文件夹")
|
||||
self.open_folder_button.setObjectName("suiteOpenFolderButton")
|
||||
layout.addWidget(self.open_folder_button)
|
||||
self.add_images_button = QPushButton("添加图片")
|
||||
self.add_images_button.setObjectName("suiteAddImagesButton")
|
||||
layout.addWidget(self.add_images_button)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(QLabel("账号"))
|
||||
self.account_combo = QComboBox()
|
||||
self.account_combo.setObjectName("suiteAccountCombo")
|
||||
self.account_combo.setMinimumWidth(180)
|
||||
self.account_combo.setMinimumWidth(120)
|
||||
self.account_combo.setMaximumWidth(160)
|
||||
self.account_combo.setMinimumContentsLength(10)
|
||||
layout.addWidget(self.account_combo)
|
||||
layout.addWidget(QLabel("商品ID"))
|
||||
self.item_id_edit = QLineEdit()
|
||||
self.item_id_edit.setObjectName("suiteItemIdEdit")
|
||||
self.item_id_edit.setPlaceholderText("请输入蝦皮商品ID")
|
||||
self.item_id_edit.setMinimumWidth(150)
|
||||
self.item_id_edit.setPlaceholderText("输入商品ID")
|
||||
item_width = max(
|
||||
120,
|
||||
min(140, self.item_id_edit.fontMetrics().horizontalAdvance("0" * 13) + 30),
|
||||
)
|
||||
self.item_id_edit.setMinimumWidth(item_width)
|
||||
self.item_id_edit.setMaximumWidth(item_width)
|
||||
layout.addWidget(self.item_id_edit)
|
||||
layout.addStretch(1)
|
||||
self.pull_button = QPushButton("拉取蝦皮主图")
|
||||
self.pull_button.setObjectName("suitePullButton")
|
||||
layout.addWidget(self.pull_button)
|
||||
@@ -478,6 +522,7 @@ class ProductSuiteTab(QWidget):
|
||||
|
||||
def _build_config_panel(self):
|
||||
panel = QWidget()
|
||||
panel.setMinimumWidth(400)
|
||||
layout = QVBoxLayout(panel)
|
||||
layout.setContentsMargins(0, 0, 4, 0)
|
||||
layout.setSpacing(7)
|
||||
@@ -524,9 +569,6 @@ class ProductSuiteTab(QWidget):
|
||||
self.original_count_label.setStyleSheet("color: #6b7280;")
|
||||
title_row.addWidget(self.original_count_label)
|
||||
title_row.addStretch(1)
|
||||
self.add_images_button = QPushButton("添加图片")
|
||||
self.add_images_button.setObjectName("suiteAddImagesButton")
|
||||
title_row.addWidget(self.add_images_button)
|
||||
layout.addLayout(title_row)
|
||||
self.original_list = ProductOriginalList()
|
||||
self.original_list.setFixedHeight(210)
|
||||
@@ -541,28 +583,69 @@ class ProductSuiteTab(QWidget):
|
||||
title.setStyleSheet("font-weight: 600;")
|
||||
layout.addWidget(title)
|
||||
grid = QGridLayout()
|
||||
self.settings_grid = grid
|
||||
grid.setContentsMargins(0, 0, 0, 0)
|
||||
grid.setSpacing(6)
|
||||
self.platform_combo = self._prefixed_combo("suitePlatformCombo", "平台", self.PLATFORM_OPTIONS)
|
||||
self.country_combo = self._prefixed_combo("suiteCountryCombo", "国家", self.COUNTRY_OPTIONS)
|
||||
self.language_combo = self._prefixed_combo("suiteLanguageCombo", "语言", self.LANGUAGE_OPTIONS)
|
||||
self.ratio_combo = self._prefixed_combo("suiteRatioCombo", "比例", product_suite.RATIOS)
|
||||
grid.addWidget(self.platform_combo, 0, 0)
|
||||
grid.addWidget(self.country_combo, 0, 1)
|
||||
grid.addWidget(self.language_combo, 1, 0)
|
||||
grid.addWidget(self.ratio_combo, 1, 1)
|
||||
grid.setHorizontalSpacing(6)
|
||||
grid.setVerticalSpacing(3)
|
||||
settings = (
|
||||
(
|
||||
"platform_label",
|
||||
"platform_combo",
|
||||
"平台",
|
||||
"suitePlatformLabel",
|
||||
"suitePlatformCombo",
|
||||
self.PLATFORM_OPTIONS,
|
||||
),
|
||||
(
|
||||
"country_label",
|
||||
"country_combo",
|
||||
"站点",
|
||||
"suiteCountryLabel",
|
||||
"suiteCountryCombo",
|
||||
self.COUNTRY_OPTIONS,
|
||||
),
|
||||
(
|
||||
"language_label",
|
||||
"language_combo",
|
||||
"语言",
|
||||
"suiteLanguageLabel",
|
||||
"suiteLanguageCombo",
|
||||
self.LANGUAGE_OPTIONS,
|
||||
),
|
||||
(
|
||||
"ratio_label",
|
||||
"ratio_combo",
|
||||
"比例",
|
||||
"suiteRatioLabel",
|
||||
"suiteRatioCombo",
|
||||
product_suite.RATIOS,
|
||||
),
|
||||
)
|
||||
for column, setting in enumerate(settings):
|
||||
label_attr, combo_attr, text, label_name, combo_name, values = setting
|
||||
label = QLabel(text)
|
||||
label.setObjectName(label_name)
|
||||
setattr(self, label_attr, label)
|
||||
combo = self._value_combo(combo_name, text, values)
|
||||
setattr(self, combo_attr, combo)
|
||||
grid.addWidget(label, 0, column)
|
||||
grid.addWidget(combo, 1, column)
|
||||
grid.setColumnStretch(column, 1)
|
||||
layout.addLayout(grid)
|
||||
self.per_image_checkbox = QCheckBox("每张上传图分别作为主图生成")
|
||||
self.per_image_checkbox.setObjectName("suitePerImageCheckbox")
|
||||
layout.addWidget(self.per_image_checkbox)
|
||||
return frame
|
||||
|
||||
def _prefixed_combo(self, object_name, prefix, values):
|
||||
def _value_combo(self, object_name, label, values):
|
||||
combo = QComboBox()
|
||||
combo.setObjectName(object_name)
|
||||
combo.setToolTip("%s设置" % prefix)
|
||||
combo.setToolTip("%s设置" % label)
|
||||
combo.setAccessibleName("%s设置" % label)
|
||||
combo.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
||||
for value in values:
|
||||
combo.addItem("%s %s" % (prefix, value), value)
|
||||
combo.addItem(value, value)
|
||||
combo.setItemData(combo.count() - 1, value, Qt.ToolTipRole)
|
||||
return combo
|
||||
|
||||
def _build_prompt_section(self):
|
||||
@@ -628,6 +711,7 @@ class ProductSuiteTab(QWidget):
|
||||
layout.setContentsMargins(4, 0, 0, 0)
|
||||
layout.setSpacing(7)
|
||||
toolbar = QHBoxLayout()
|
||||
self.results_toolbar_layout = toolbar
|
||||
title = QLabel("生成结果")
|
||||
title.setStyleSheet("font-weight: 600; font-size: 15px;")
|
||||
toolbar.addWidget(title)
|
||||
@@ -642,13 +726,6 @@ class ProductSuiteTab(QWidget):
|
||||
self.undo_button.setObjectName("suiteUndoButton")
|
||||
self.undo_button.hide()
|
||||
toolbar.addWidget(self.undo_button)
|
||||
self.history_button = QPushButton("历史生成")
|
||||
self.history_button.setObjectName("suiteHistoryButton")
|
||||
self.history_button.setCheckable(True)
|
||||
toolbar.addWidget(self.history_button)
|
||||
self.open_folder_button = QPushButton("打开文件夹")
|
||||
self.open_folder_button.setObjectName("suiteOpenFolderButton")
|
||||
toolbar.addWidget(self.open_folder_button)
|
||||
self.more_button = QToolButton()
|
||||
self.more_button.setText("⋯")
|
||||
self.more_button.setToolTip("更多操作")
|
||||
@@ -750,9 +827,12 @@ class ProductSuiteTab(QWidget):
|
||||
try:
|
||||
self.account_combo.clear()
|
||||
for account in self.accounts:
|
||||
self.account_combo.addItem(
|
||||
"%s(%s)" % (account.account_name, account.alias),
|
||||
account.alias,
|
||||
text = "%s(%s)" % (account.account_name, account.alias)
|
||||
self.account_combo.addItem(text, account.alias)
|
||||
self.account_combo.setItemData(
|
||||
self.account_combo.count() - 1,
|
||||
text,
|
||||
Qt.ToolTipRole,
|
||||
)
|
||||
if not self.accounts:
|
||||
self.account_combo.addItem("暂无账号,请先到④账号管理添加", "")
|
||||
@@ -761,17 +841,21 @@ class ProductSuiteTab(QWidget):
|
||||
self.account_combo.setCurrentIndex(index)
|
||||
finally:
|
||||
self._loading = False
|
||||
self._update_account_tooltip()
|
||||
|
||||
def add_task(self, checked=False, inherit=True):
|
||||
source = self._displayed_state if inherit else None
|
||||
initial_settings = (
|
||||
source.settings
|
||||
if source is not None
|
||||
else appconfig.product_suite_last_settings(self.config)
|
||||
)
|
||||
state = SuiteTaskState(
|
||||
key=self._next_key,
|
||||
serial=self._next_serial,
|
||||
account_alias=(source.account_alias if source is not None else ""),
|
||||
prompt=(source.prompt if source is not None else ""),
|
||||
settings=product_suite.normalize_suite_settings(
|
||||
source.settings if source is not None else None
|
||||
),
|
||||
settings=product_suite.normalize_suite_settings(initial_settings),
|
||||
)
|
||||
if not state.account_alias and self.accounts:
|
||||
state.account_alias = self.accounts[0].alias
|
||||
@@ -877,6 +961,7 @@ class ProductSuiteTab(QWidget):
|
||||
return product_suite.normalize_suite_settings(settings)
|
||||
|
||||
def _on_account_changed(self, index):
|
||||
self._update_account_tooltip()
|
||||
if self._loading or self._displayed_state is None:
|
||||
return
|
||||
state = self._displayed_state
|
||||
@@ -896,6 +981,9 @@ class ProductSuiteTab(QWidget):
|
||||
state.account_alias = alias
|
||||
self._update_context_actions(state)
|
||||
|
||||
def _update_account_tooltip(self):
|
||||
self.account_combo.setToolTip(self.account_combo.currentText())
|
||||
|
||||
def _on_item_finished(self):
|
||||
if self._loading or self._displayed_state is None:
|
||||
return
|
||||
@@ -1000,9 +1088,31 @@ class ProductSuiteTab(QWidget):
|
||||
return
|
||||
state = self._displayed_state
|
||||
state.settings = self._settings_from_controls()
|
||||
self._persist_last_settings(state.settings)
|
||||
self._persist_state(state)
|
||||
self._refresh_totals(state)
|
||||
|
||||
def _persist_last_settings(self, settings):
|
||||
last_settings = product_suite.last_suite_settings(settings)
|
||||
try:
|
||||
if os.path.exists(self.config_path):
|
||||
saved = appconfig.update_config(
|
||||
{"product_suite": {"last_settings": last_settings}},
|
||||
path=self.config_path,
|
||||
)
|
||||
else:
|
||||
base = dict(self.config)
|
||||
suite = base.get("product_suite", {})
|
||||
suite = dict(suite) if isinstance(suite, dict) else {}
|
||||
suite["last_settings"] = last_settings
|
||||
base["product_suite"] = suite
|
||||
saved = appconfig.save_config(base, path=self.config_path)
|
||||
except Exception as exc:
|
||||
self._status("商品套图最近设置保存失败:%s" % _user_error(exc), "danger")
|
||||
return
|
||||
self.config.clear()
|
||||
self.config.update(saved)
|
||||
|
||||
def _on_prompt_changed(self):
|
||||
if self._loading or self._displayed_state is None:
|
||||
return
|
||||
|
||||
+18
-5
@@ -9,7 +9,11 @@ FIXED_CATEGORIES = ("白底图", "场景图", "卖点图")
|
||||
DEFAULT_CATEGORY_COUNTS = OrderedDict(
|
||||
(("白底图", 1), ("场景图", 2), ("卖点图", 2))
|
||||
)
|
||||
PLATFORMS = ("Shopee", "Lazada", "TikTok Shop", "Amazon")
|
||||
COUNTRIES = ("中国台湾", "新加坡", "马来西亚", "菲律宾", "泰国", "越南")
|
||||
LANGUAGES = ("繁体中文", "简体中文", "英文", "泰文", "越南文")
|
||||
RATIOS = ("1:1", "3:4", "4:3", "16:9", "9:16")
|
||||
LAST_SETTING_KEYS = ("platform", "country", "language", "ratio")
|
||||
MAX_CATEGORY_NAME_LENGTH = 10
|
||||
MAX_GENERATION_COUNT_WITHOUT_CONFIRM = 16
|
||||
|
||||
@@ -29,11 +33,10 @@ def default_suite_settings():
|
||||
def normalize_suite_settings(value=None):
|
||||
raw = dict(value or {}) if isinstance(value, dict) else {}
|
||||
normalized = default_suite_settings()
|
||||
normalized["platform"] = str(raw.get("platform") or "Shopee")
|
||||
normalized["country"] = str(raw.get("country") or "中国台湾")
|
||||
normalized["language"] = str(raw.get("language") or "繁体中文")
|
||||
ratio = str(raw.get("ratio") or "1:1")
|
||||
normalized["ratio"] = ratio if ratio in RATIOS else "1:1"
|
||||
normalized["platform"] = _choice(raw.get("platform"), PLATFORMS, "Shopee")
|
||||
normalized["country"] = _choice(raw.get("country"), COUNTRIES, "中国台湾")
|
||||
normalized["language"] = _choice(raw.get("language"), LANGUAGES, "繁体中文")
|
||||
normalized["ratio"] = _choice(raw.get("ratio"), RATIOS, "1:1")
|
||||
normalized["per_image_primary"] = bool(raw.get("per_image_primary", False))
|
||||
|
||||
raw_categories = raw.get("categories") if isinstance(raw.get("categories"), dict) else {}
|
||||
@@ -56,6 +59,11 @@ def normalize_suite_settings(value=None):
|
||||
return normalized
|
||||
|
||||
|
||||
def last_suite_settings(value=None):
|
||||
normalized = normalize_suite_settings(value)
|
||||
return {key: normalized[key] for key in LAST_SETTING_KEYS}
|
||||
|
||||
|
||||
def suite_name_error(name, existing=None, old_name=""):
|
||||
value = str(name or "")
|
||||
if not value.strip():
|
||||
@@ -146,3 +154,8 @@ def _count(value):
|
||||
return max(0, int(value or 0))
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
|
||||
def _choice(value, choices, default):
|
||||
text = str(value or "").strip()
|
||||
return text if text in choices else default
|
||||
|
||||
Reference in New Issue
Block a user