feat(product-suite): add independent category rows

This commit is contained in:
chengma
2026-07-16 09:22:33 +08:00
parent dbbf3ce8f7
commit c80c6e67e9
3 changed files with 303 additions and 86 deletions
+141 -84
View File
@@ -57,6 +57,11 @@ ORIGINAL_DOWNLOAD_CONCURRENCY = 2
ORIGINAL_CHECK_STATE_ROLE = Qt.UserRole + 1
_PRODUCT_SUITE_THREAD_REFS = {}
_URL_RE = re.compile(r"https?://[^\s,,;;))\]]+", re.IGNORECASE)
FIXED_CATEGORY_HELPERS = {
"白底图": "白底主图,多角度呈现商品细节",
"场景图": "生活化场景展示商品使用方式",
"卖点图": "突出核心卖点和差异化优势",
}
def _asset_usable(asset):
@@ -392,12 +397,116 @@ class ProductOriginalList(QListWidget):
super().leaveEvent(event)
class CategoryChipButton(QPushButton):
doubleClicked = Signal()
class SuiteCategoryRow(QFrame):
countChangeRequested = Signal(str, int)
renameRequested = Signal(str)
deleteRequested = Signal(str)
def mouseDoubleClickEvent(self, event):
self.doubleClicked.emit()
event.accept()
def __init__(self, name, count, *, helper="", custom=False, parent=None):
super().__init__(parent)
self.category_name = str(name)
self._controls_enabled = True
self.setObjectName("suiteCategoryRow")
self.setProperty("categoryName", self.category_name)
self.setStyleSheet(
"QFrame#suiteCategoryRow { border: none; border-bottom: 1px solid #eaeef2; }"
)
layout = QGridLayout(self)
layout.setContentsMargins(6, 5, 4, 6)
layout.setHorizontalSpacing(8)
layout.setVerticalSpacing(2)
layout.setColumnStretch(0, 1)
self.name_label = QLabel(self.category_name)
self.name_label.setObjectName("suiteCategoryNameLabel")
self.name_label.setStyleSheet("font-weight: 600; color: #24292f;")
layout.addWidget(self.name_label, 0, 0)
controls_widget = QWidget()
controls = QHBoxLayout(controls_widget)
controls.setContentsMargins(0, 0, 0, 0)
controls.setSpacing(4)
self.minus_button = QToolButton()
self.minus_button.setObjectName("suiteCategoryMinusButton")
self.minus_button.setText("−")
self.minus_button.setToolTip("减少%s数量" % self.category_name)
self.minus_button.setAccessibleName("减少%s数量" % self.category_name)
self.minus_button.setMinimumSize(28, 28)
self.minus_button.clicked.connect(
lambda checked=False: self.countChangeRequested.emit(self.category_name, -1)
)
controls.addWidget(self.minus_button)
self.count_label = QLabel()
self.count_label.setObjectName("suiteCategoryCountLabel")
self.count_label.setAlignment(Qt.AlignCenter)
self.count_label.setMinimumWidth(28)
controls.addWidget(self.count_label)
self.plus_button = QToolButton()
self.plus_button.setObjectName("suiteCategoryPlusButton")
self.plus_button.setText("+")
self.plus_button.setToolTip("增加%s数量" % self.category_name)
self.plus_button.setAccessibleName("增加%s数量" % self.category_name)
self.plus_button.setMinimumSize(28, 28)
self.plus_button.clicked.connect(
lambda checked=False: self.countChangeRequested.emit(self.category_name, 1)
)
controls.addWidget(self.plus_button)
self.rename_button = None
self.delete_button = None
if custom:
self.rename_button = QToolButton()
self.rename_button.setObjectName("suiteCategoryRenameButton")
self.rename_button.setText("改名")
self.rename_button.setToolTip("重命名%s分类" % self.category_name)
self.rename_button.setAccessibleName("重命名%s分类" % self.category_name)
self.rename_button.setMinimumSize(48, 28)
self.rename_button.clicked.connect(
lambda checked=False: self.renameRequested.emit(self.category_name)
)
controls.addWidget(self.rename_button)
self.delete_button = QToolButton()
self.delete_button.setObjectName("suiteCategoryDeleteButton")
self.delete_button.setText("×")
self.delete_button.setToolTip("删除%s分类" % self.category_name)
self.delete_button.setAccessibleName("删除%s分类" % self.category_name)
self.delete_button.setMinimumSize(28, 28)
self.delete_button.clicked.connect(
lambda checked=False: self.deleteRequested.emit(self.category_name)
)
controls.addWidget(self.delete_button)
layout.addWidget(controls_widget, 0, 1, 2 if helper else 1, 1, Qt.AlignTop)
self.helper_label = None
if helper:
self.helper_label = QLabel(str(helper))
self.helper_label.setObjectName("suiteCategoryHelperLabel")
self.helper_label.setStyleSheet("color: #6b7280;")
self.helper_label.setWordWrap(True)
layout.addWidget(self.helper_label, 1, 0)
self.set_count(count)
def set_count(self, count):
normalized = max(0, int(count or 0))
self.count_label.setText(str(normalized))
self.minus_button.setEnabled(self._controls_enabled and normalized > 0)
def count(self):
return int(self.count_label.text() or 0)
def set_controls_enabled(self, enabled):
self._controls_enabled = bool(enabled)
self.minus_button.setEnabled(self._controls_enabled and self.count() > 0)
self.plus_button.setEnabled(self._controls_enabled)
if self.rename_button is not None:
self.rename_button.setEnabled(self._controls_enabled)
if self.delete_button is not None:
self.delete_button.setEnabled(self._controls_enabled)
class SuiteResultCard(QFrame):
@@ -511,8 +620,6 @@ class SuiteTaskState:
ai_prompt_snapshot: str = ""
pending_ai_text: str = ""
undo_records: list = field(default_factory=list)
active_category: str = "白底图"
def generation_running(self):
return self.worker is not None
@@ -848,8 +955,10 @@ class ProductSuiteTab(QWidget):
layout.addLayout(title_row)
self.category_grid = QGridLayout()
self.category_grid.setContentsMargins(0, 0, 0, 0)
self.category_grid.setHorizontalSpacing(4)
self.category_grid.setVerticalSpacing(5)
self.category_grid.setHorizontalSpacing(0)
self.category_grid.setVerticalSpacing(3)
self.category_grid.setColumnStretch(0, 1)
self.category_rows = {}
layout.addLayout(self.category_grid)
self.add_category_button = QPushButton("添加自定义分类")
self.add_category_button.setObjectName("suiteAddCategoryButton")
@@ -1650,81 +1759,24 @@ class ProductSuiteTab(QWidget):
state.settings = settings
categories = settings["categories"]
ordered = product_suite.category_order(settings)
if state.active_category not in ordered:
state.active_category = ordered[0]
self.category_rows = {}
for index, name in enumerate(ordered):
name_button = CategoryChipButton("%s %s" % (name, categories.get(name, 0)))
name_button.setCheckable(True)
name_button.setChecked(name == state.active_category)
name_button.setMinimumHeight(30)
name_button.setStyleSheet(
"QPushButton { text-align: center; padding: 3px 7px; }"
"QPushButton:checked { background: #eaf2ff; border-color: #2f6fed; color: #2459c4; }"
custom = name not in product_suite.FIXED_CATEGORIES
row = SuiteCategoryRow(
name,
categories.get(name, 0),
helper=FIXED_CATEGORY_HELPERS.get(name, ""),
custom=custom,
)
name_button.clicked.connect(
lambda checked=False, category=name: self.select_category(category)
)
if name not in product_suite.FIXED_CATEGORIES:
name_button.setToolTip("单击展开数量;双击重命名")
name_button.doubleClicked.connect(
lambda old_name=name: self.rename_custom_category(old_name)
)
else:
name_button.setToolTip("单击展开数量")
self.category_grid.addWidget(name_button, index // 3, index % 3)
detail_row = (len(ordered) + 2) // 3
active_name = state.active_category
detail = QFrame()
detail.setObjectName("suiteCategoryCounter")
detail_layout = QHBoxLayout(detail)
detail_layout.setContentsMargins(6, 3, 6, 3)
active_label = QLabel(active_name)
active_label.setStyleSheet("font-weight: 600;")
detail_layout.addWidget(active_label)
detail_layout.addStretch(1)
minus = QToolButton()
minus.setText("−")
minus.setToolTip("减少%s数量" % active_name)
minus.setAccessibleName("减少%s数量" % active_name)
minus.setMinimumSize(28, 28)
minus.clicked.connect(
lambda checked=False, category=active_name: self.change_category_count(category, -1)
)
detail_layout.addWidget(minus)
count_label = QLabel(str(categories.get(active_name, 0)))
count_label.setAlignment(Qt.AlignCenter)
count_label.setMinimumWidth(28)
detail_layout.addWidget(count_label)
plus = QToolButton()
plus.setText("+")
plus.setToolTip("增加%s数量" % active_name)
plus.setAccessibleName("增加%s数量" % active_name)
plus.setMinimumSize(28, 28)
plus.clicked.connect(
lambda checked=False, category=active_name: self.change_category_count(category, 1)
)
detail_layout.addWidget(plus)
if active_name not in product_suite.FIXED_CATEGORIES:
remove = QToolButton()
remove.setText("×")
remove.setToolTip("删除%s分类" % active_name)
remove.setAccessibleName("删除%s分类" % active_name)
remove.setMinimumSize(28, 28)
remove.clicked.connect(
lambda checked=False, category=active_name: self.delete_custom_category(category)
)
detail_layout.addWidget(remove)
self.category_grid.addWidget(detail, detail_row, 0, 1, 3)
row.countChangeRequested.connect(self.change_category_count)
if custom:
row.renameRequested.connect(self.rename_custom_category)
row.deleteRequested.connect(self.delete_custom_category)
row.set_controls_enabled(not state.generation_running())
self.category_rows[name] = row
self.category_grid.addWidget(row, index, 0)
self._refresh_totals(state)
def select_category(self, name):
state = self._displayed_state
if state is None:
return
state.active_category = str(name)
self._rebuild_categories(state)
def change_category_count(self, name, delta):
state = self._displayed_state
if state is None:
@@ -1735,7 +1787,12 @@ class ProductSuiteTab(QWidget):
settings["categories"] = categories
state.settings = settings
self._persist_state(state)
self._rebuild_categories(state)
row = self.category_rows.get(name) if state is self._displayed_state else None
if row is None:
self._rebuild_categories(state)
else:
row.set_count(categories[name])
self._refresh_totals(state)
def add_custom_category(self, checked=False):
if self._displayed_state is None:
@@ -1761,7 +1818,6 @@ class ProductSuiteTab(QWidget):
settings["categories"] = categories
settings["custom_category_order"] = list(settings["custom_category_order"]) + [name]
state.settings = product_suite.normalize_suite_settings(settings)
state.active_category = name
self._persist_state(state)
self.custom_category_edit.hide()
self.add_category_button.show()
@@ -1798,7 +1854,6 @@ class ProductSuiteTab(QWidget):
for value in settings["custom_category_order"]
]
state.settings = product_suite.normalize_suite_settings(settings)
state.active_category = name
self._persist_state(state)
self._rebuild_categories(state)
@@ -1814,7 +1869,6 @@ class ProductSuiteTab(QWidget):
value for value in settings["custom_category_order"] if value != name
]
state.settings = product_suite.normalize_suite_settings(settings)
state.active_category = product_suite.FIXED_CATEGORIES[0]
self._persist_state(state)
self._rebuild_categories(state)
@@ -2064,8 +2118,11 @@ class ProductSuiteTab(QWidget):
self.per_image_checkbox,
self.prompt_edit,
self.add_category_button,
self.custom_category_edit,
):
widget.setEnabled(not generation_running)
for row in self.category_rows.values():
row.set_controls_enabled(not generation_running)
self.generate_button.setEnabled(True)
if generation_running:
self.generate_button.setText("停止生成")