80a4cdfc by 柴进

feat(ui): 迁移 StyleDesignerTab + TaskQueueWidget 样式

- StyleDesignerTab 删 8 处 inline setStyleSheet:
  - 主按钮"生成珠宝图片" variant=primary(统一两个 tab 的视觉权重)
  - 随机/恢复按钮去硬编码颜色,恢复按钮 variant=ghost
  - 各 caption label 用 role=caption(统一字号)
  - prompt_group 的 font-size:16px inline 删除
  - result_label 用 #previewImage objectName + has_image property 切换
  - 类内补 _set_status helper(之前批量替换时漏了)

- task_queue.py:
  - TaskQueueWidget 用 #taskQueueSidebar objectName,
    顶部新增 #sidebarHeader 40px header bar,
    与主区 TabBar 视觉同高 — 修复"任务队列"标题飘到 tab 旁边的视觉断层
  - 删除 list/widget 内嵌 stylesheet,全部走全局主题
  - QListWidgetItem 状态色(橙/蓝/绿/红/灰)改用 theme.get_color() 读当前
    主题 token,浅深模式都对

- theme.py 新增 get_color(token, fallback) 公共函数:
  用于 widget 内嵌颜色调用(如 setForeground 等 QSS 命中不到的渲染层 API)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent de306ffe
......@@ -4007,6 +4007,7 @@ class StyleDesignerTab(QWidget):
self.logger = logging.getLogger(__name__)
self.library_manager = library_manager
self.parent_window = parent
self.setObjectName("styleDesignerTab")
# 字段顺序
self.categories = ["主石形状", "主石材质", "金属", "花头形式", "戒臂结构", "戒臂处理", "特殊元素", "辅石镶嵌"]
......@@ -4051,38 +4052,15 @@ class StyleDesignerTab(QWidget):
# 全局按钮区域
buttons_layout = QHBoxLayout()
# 随机生成按钮
# 随机生成按钮(次要操作,无 variant 走默认)
random_btn = QPushButton("🎲 随机生成参数")
random_btn.clicked.connect(self.randomize_parameters)
random_btn.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
padding: 8px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #45a049;
}
""")
buttons_layout.addWidget(random_btn)
# 全局恢复按钮
# 全局恢复按钮(轻量警告 - 用 ghost)
reset_all_btn = QPushButton("🔄 恢复所有默认词库")
reset_all_btn.setProperty("variant", "ghost")
reset_all_btn.clicked.connect(self.reset_all_library)
reset_all_btn.setStyleSheet("""
QPushButton {
background-color: #ff9800;
color: white;
padding: 8px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #f57c00;
}
""")
buttons_layout.addWidget(reset_all_btn)
buttons_layout.addStretch()
......@@ -4101,7 +4079,6 @@ class StyleDesignerTab(QWidget):
self.prompt_preview.setReadOnly(True)
prompt_layout.addWidget(self.prompt_preview)
prompt_group.setLayout(prompt_layout)
prompt_group.setStyleSheet("font-size: 16px;")
content_row.addWidget(prompt_group, 2)
......@@ -4111,7 +4088,7 @@ class StyleDesignerTab(QWidget):
# 生成模式(放在最前面)
mode_label = QLabel("生成模式")
mode_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
mode_label.setProperty("role", "caption")
settings_layout.addWidget(mode_label)
self.generation_mode = QComboBox()
self.generation_mode.addItems(["极速模式", "慢速模式"])
......@@ -4123,7 +4100,7 @@ class StyleDesignerTab(QWidget):
# 宽高比
aspect_label = QLabel("宽高比")
aspect_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
aspect_label.setProperty("role", "caption")
settings_layout.addWidget(aspect_label)
self.aspect_ratio = QComboBox()
self.aspect_ratio.addItems([
......@@ -4140,7 +4117,7 @@ class StyleDesignerTab(QWidget):
# 图片尺寸
size_label = QLabel("图片尺寸")
size_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
size_label.setProperty("role", "caption")
settings_layout.addWidget(size_label)
self.image_size = QComboBox()
self.image_size.addItems(["1K", "2K", "4K"])
......@@ -4159,20 +4136,8 @@ class StyleDesignerTab(QWidget):
# Action buttons - 与图片生成页面保持一致
action_layout = QHBoxLayout()
self.generate_btn = QPushButton("🎨 生成珠宝图片")
self.generate_btn.setProperty("variant", "primary")
self.generate_btn.clicked.connect(self.generate_image)
self.generate_btn.setStyleSheet("""
QPushButton {
background-color: #007AFF;
color: white;
padding: 12px;
border-radius: 4px;
font-size: 14px;
font-weight: bold;
}
QPushButton:hover {
background-color: #0051D5;
}
""")
action_layout.addWidget(self.generate_btn)
self.download_btn = QPushButton("💾 下载图片")
......@@ -4181,7 +4146,7 @@ class StyleDesignerTab(QWidget):
action_layout.addWidget(self.download_btn)
self.status_label = QLabel("● 就绪")
self.status_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
self.status_label.setProperty("status", "muted")
action_layout.addWidget(self.status_label)
action_layout.addStretch()
......@@ -4192,19 +4157,11 @@ class StyleDesignerTab(QWidget):
preview_layout = QVBoxLayout()
self.result_label = QLabel("生成的图片将在这里显示\n双击用系统查看器打开")
self.result_label.setObjectName("previewImage")
self.result_label.setProperty("has_image", "false")
self.result_label.setAlignment(Qt.AlignCenter)
self.result_label.setMinimumHeight(300)
self.result_label.setMinimumWidth(400)
self.result_label.setStyleSheet("""
QLabel {
color: #999999;
font-size: 14px;
line-height: 18px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
}
""")
self.result_label.setScaledContents(False)
# 保存原始的双击处理方法引用
self.original_mouseDoubleClickEvent = self.result_label.mouseDoubleClickEvent
......@@ -4221,6 +4178,14 @@ class StyleDesignerTab(QWidget):
# 初始化 prompt 预览
self.update_prompt_preview()
def _set_status(self, status: str, text: str | None = None) -> None:
"""统一状态标签视觉。status: success|warning|danger|info|muted"""
if text is not None:
self.status_label.setText(text)
self.status_label.setProperty("status", status)
self.status_label.style().unpolish(self.status_label)
self.status_label.style().polish(self.status_label)
def randomize_parameters(self):
"""随机生成一套参数(跳过锁定的字段)"""
for category, combo in self.combo_boxes.items():
......@@ -4239,7 +4204,7 @@ class StyleDesignerTab(QWidget):
# 标签
label = QLabel(f"{category}:")
label.setMinimumWidth(70) # 减小标签宽度以适应两列布局
label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
label.setProperty("role", "caption")
layout.addWidget(label)
# 下拉框
......@@ -4671,12 +4636,9 @@ class StyleDesignerTab(QWidget):
Qt.SmoothTransformation
)
self.result_label.setPixmap(scaled_pixmap)
self.result_label.setStyleSheet("""
QLabel {
border: 1px solid #ddd;
background-color: white;
}
""")
self.result_label.setProperty("has_image", "true")
self.result_label.style().unpolish(self.result_label)
self.result_label.style().polish(self.result_label)
# 启用下载按钮
self.download_btn.setEnabled(True)
except Exception as e:
......
......@@ -447,66 +447,49 @@ class TaskQueueWidget(QWidget):
self._update_summary()
def _setup_ui(self):
"""构建右侧任务列表 UI"""
"""构建右侧任务列表 UI(视觉走全局主题)"""
self.setObjectName("taskQueueSidebar")
layout = QVBoxLayout()
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(2)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# Header bar — 与主区 TabBar 同高(40px),消除"任务队列"飘上去的视觉
header = QWidget()
header.setObjectName("sidebarHeader")
header_layout = QHBoxLayout()
header_layout.setContentsMargins(0, 0, 0, 0)
header_layout.setSpacing(0)
# 标题
title = QLabel("任务队列")
title.setStyleSheet("QLabel { font-weight: bold; font-size: 10px; color: #666; }")
title.setAlignment(Qt.AlignCenter)
title.setAlignment(Qt.AlignVCenter)
title.setToolTip("鼠标悬停查看详情\n右键等待中或运行中的任务可取消")
layout.addWidget(title)
# 分隔线
line = QLabel()
line.setFrameStyle(QFrame.HLine | QFrame.Sunken)
layout.addWidget(line)
header_layout.addWidget(title)
header_layout.addStretch()
header.setLayout(header_layout)
layout.addWidget(header)
# 任务状态列表 - 可点击的列表项
# 任务状态列表 - 可点击的列表项(样式由全局 QSS 提供)
self.task_list = QListWidget()
self.task_list.setStyleSheet("""
QListWidget {
border: none;
font-size: 11px;
padding: 2px;
}
QListWidget::item {
padding: 4px 2px;
border-bottom: 1px solid #eee;
min-height: 20px;
}
QListWidget::item:hover {
background-color: #e3f2fd;
cursor: pointer;
}
QListWidget::item:selected {
background-color: #bbdefb;
}
""")
self.task_list.setObjectName("taskList")
self.task_list.setFrameShape(QFrame.NoFrame)
self.task_list.itemClicked.connect(self._on_task_item_clicked)
# 启用右键菜单
self.task_list.setContextMenuPolicy(Qt.CustomContextMenu)
self.task_list.customContextMenuRequested.connect(self._show_context_menu)
layout.addWidget(self.task_list)
# 加 padding 让列表脱离边缘
list_wrap = QVBoxLayout()
list_wrap.setContentsMargins(8, 8, 8, 8)
list_wrap.addWidget(self.task_list)
layout.addLayout(list_wrap)
layout.addStretch()
self.setLayout(layout)
# 设置极窄宽度
self.setMaximumWidth(120)
self.setMinimumWidth(80)
# 样式
self.setStyleSheet("""
TaskQueueWidget {
background-color: #f5f5f5;
border-left: 1px solid #ddd;
}
""")
self.setMaximumWidth(140)
self.setMinimumWidth(100)
def _connect_signals(self):
"""绑定信号"""
......@@ -519,30 +502,30 @@ class TaskQueueWidget(QWidget):
def _update_summary(self):
"""更新任务列表 - 显示可点击的状态项"""
from theme import get_color
self.task_list.clear()
tasks = self.manager.get_all_tasks()
# 按状态分类并转换为文字
# 按状态分类并转换为文字(颜色读自当前主题,浅深都对)
for task in tasks:
# 状态文字和颜色
if task.status == TaskStatus.RUNNING:
status_text = "执行中"
color = "#FF9500" # 橙色
color = get_color("warning", "#FF9500")
elif task.status == TaskStatus.PENDING:
status_text = "等待中"
color = "#007AFF" # 蓝色
color = get_color("accent", "#007AFF")
elif task.status == TaskStatus.COMPLETED:
status_text = "已完成"
color = "#34C759" # 绿色
color = get_color("success", "#34C759")
elif task.status == TaskStatus.FAILED:
status_text = "失败"
color = "#FF3B30" # 红色
color = get_color("danger", "#FF3B30")
elif task.status == TaskStatus.CANCELLED:
status_text = "已取消"
color = "#8E8E93" # 中性灰
color = get_color("text_tertiary", "#8E8E93")
else:
status_text = "未知"
color = "#666666" # 灰色
color = get_color("text_secondary", "#666666")
# 创建列表项
item = QListWidgetItem(status_text)
......@@ -717,7 +700,7 @@ class TaskQueueWidget(QWidget):
# 标题
title = QLabel("📋 任务队列")
title.setStyleSheet("QLabel { font-weight: bold; font-size: 14px; }")
title.setProperty("role", "title")
layout.addWidget(title)
# 任务列表
......@@ -729,13 +712,14 @@ class TaskQueueWidget(QWidget):
item = QListWidgetItem()
item.setData(Qt.UserRole, task.id)
from theme import get_color
status_map = {
TaskStatus.RUNNING: ("●", "#FF9500"),
TaskStatus.PENDING: ("○", "#8E8E93"),
TaskStatus.COMPLETED: ("✓", "#34C759"),
TaskStatus.FAILED: ("✗", "#FF3B30"),
TaskStatus.RUNNING: ("●", get_color("warning", "#FF9500")),
TaskStatus.PENDING: ("○", get_color("text_tertiary", "#8E8E93")),
TaskStatus.COMPLETED: ("✓", get_color("success", "#34C759")),
TaskStatus.FAILED: ("✗", get_color("danger", "#FF3B30")),
}
icon, color = status_map.get(task.status, ("?", "#000"))
icon, color = status_map.get(task.status, ("?", get_color("text_primary", "#000")))
prompt_preview = task.prompt[:30] + "..." if len(task.prompt) > 30 else task.prompt
display_text = f"{icon} {prompt_preview}"
......
......@@ -748,4 +748,24 @@ def apply_theme(app: QApplication) -> ThemeManager:
返回 ThemeManager 实例,调用方应持有引用以保持信号连接(否则会被
Python GC 当作未引用的 QObject 回收,导致系统切换失效)。
"""
return ThemeManager(app)
mgr = ThemeManager(app)
# 全局可访问点,方便 get_color() 等无 widget 引用的场景
app.theme_manager = mgr
return mgr
def get_color(token: str, fallback: str = "#000000") -> str:
"""读取当前主题下的颜色 token。
用于 widget 内嵌颜色调用(如 QListWidgetItem.setForeground 等),
QSS 命中不到的渲染层 API。注意:返回的是当前快照,主题切换后需要
在下一次重绘时重新读取。
"""
try:
app = QApplication.instance()
mgr = getattr(app, "theme_manager", None)
mode = mgr.current_mode() if mgr else "light"
except Exception:
mode = "light"
table = TOKENS_DARK if mode == "dark" else TOKENS_LIGHT
return table.get(token, fallback)
......