Files
2026-07-30 11:12:31 +08:00

570 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# styles.py
"""界面样式集中管理模块:PySide6 QSS 样式表。
颜色主题:品牌蓝 #0960D1,主背景 #F8FAFC,成功绿 #0F955D。
通过 apply_app_style(app) 应用全局 QSS 样式,并拿到配色字典。
"""
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QPalette, QColor
# ==========================================
# 统一配色架构(与 Tkinter 版本保持一致)
# ==========================================
COLORS = {
"BG_COLOR": "#F8FAFC", # 浅色大背景(微冷超浅灰蓝)
"CARD_BG": "#FFFFFF", # 容器卡片背景
"BORDER_COLOR": "#E2E8F0", # 扁平化细边框
"TEXT_MAIN": "#1A1A1A", # 主文字颜色(高清晰度深灰)
"TEXT_MUTED": "#757575", # 辅助文字颜色(中灰)
"ACCENT_LIGHT": "#0960D1", # 主品牌色/高亮蓝
"ACCENT_DARK": "#0960D1", # 品牌色(统一)
"HOVER_BLUE": "#0856B8", # 悬停过渡色(品牌色加深)
"SUCCESS_GREEN": "#0F955D", # 连接成功绿
"SUCCESS_ACTIVE": "#0D8250", # 成功按钮按下态
"DISABLED": "#CBD5E1", # 禁用态灰
"ERROR_RED": "#FF2424", # 状态警示红
"NAV_BG": "#FFFFFF", # 导航栏背景(白色)
"NAV_BORDER": "#E2E8F0", # 导航栏底部边框
"NAV_TAB_ACTIVE_TEXT": "#0960D1", # Tab激活态文字色
"NAV_TAB_HOVER": "#F1F5F9", # Tab悬停背景
"FORM_LABEL": "#333333", # 表单标签色
"REFRESH_BORDER": "#CCDBF0", # 刷新按钮边框
}
# 科技蓝核心高亮统一为浅色主题色
COLORS["ACCENT_BLUE"] = COLORS["ACCENT_LIGHT"]
# 字体配置
BASE_FONT_FAMILY = "Microsoft YaHei, PingFang SC, SimHei, sans-serif"
BASE_FONT_SIZE = "14px"
BASE_FONT_SIZE_SM = "12px"
BASE_FONT_SIZE_LG = "16px"
BASE_FONT_SIZE_XL = "28px"
BASE_FONT_SIZE_NAV_TITLE = "22px"
QSS_STYLESHEET = f"""
/* ===== 全局默认 ===== */
QMainWindow, QWidget {{
background-color: {COLORS["BG_COLOR"]};
color: {COLORS["TEXT_MAIN"]};
font-family: "{BASE_FONT_FAMILY}";
font-size: {BASE_FONT_SIZE};
}}
/* ===== 标题行(Layer 1:纯白背景) ===== */
QWidget[cssClass="titleRow"] {{
background-color: #FFFFFF;
}}
/* ===== Tab 栏容器(Layer 2:一体化浅灰背景 #F8FAFC ===== */
QWidget[cssClass="tabRow"] {{
background-color: #F8FAFC;
}}
/* ===== QTabBar 导航标签 ===== */
QTabBar[cssClass="mainTab"]::tab {{
background: transparent;
padding: 10px 24px 10px 16px;
font-size: 15px;
color: #64748B;
border: none;
border-bottom: 3px solid transparent;
font-weight: normal;
}}
QTabBar[cssClass="mainTab"]::tab:selected {{
color: #0960D1;
font-weight: bold;
border-bottom: 3px solid #0960D1;
}}
QTabBar[cssClass="mainTab"]::tab:hover:!selected {{
color: #0960D1;
}}
/* ===== QGroupBox ===== */
QGroupBox {{
background-color: {COLORS["CARD_BG"]};
border: 1px solid {COLORS["BORDER_COLOR"]};
border-radius: 12px;
margin-top: 14px;
padding: 16px 12px 12px 12px;
font-weight: bold;
color: {COLORS["TEXT_MAIN"]};
font-size: {BASE_FONT_SIZE_LG};
}}
QGroupBox::title {{
subcontrol-origin: margin;
subcontrol-position: top left;
padding: 0 10px;
color: {COLORS["ACCENT_DARK"]};
font-weight: bold;
font-size: {BASE_FONT_SIZE_LG};
background-color: transparent;
}}
/* ===== Section 卡片(替代 QGroupBox 的轻量方案) ===== */
QFrame[cssClass="sectionCard"] {{
background-color: {COLORS["CARD_BG"]};
border: 1px solid {COLORS["BORDER_COLOR"]};
border-radius: 12px;
}}
/* ===== Section 标题 ===== */
QLabel[cssClass="sectionTitle"] {{
color: {COLORS["TEXT_MAIN"]};
font-weight: bold;
font-size: {BASE_FONT_SIZE_LG};
background-color: transparent;
}}
/* Section 标题左侧蓝色竖线(4px x 16px */
QWidget[cssClass="sectionAccent"] {{
background-color: {COLORS["ACCENT_LIGHT"]};
border-radius: 2px;
}}
/* ===== 表单标签 ===== */
QLabel[cssClass="formLabel"] {{
color: {COLORS["FORM_LABEL"]};
font-size: 14px;
font-weight: bold;
background-color: transparent;
min-width: 140px;
}}
/* ===== QPushButton 基础 ===== */
QPushButton {{
background-color: {COLORS["ACCENT_BLUE"]};
color: white;
border: none;
border-radius: 6px;
padding: 9px 20px;
font-weight: bold;
font-size: {BASE_FONT_SIZE};
}}
QPushButton:hover {{
background-color: {COLORS["HOVER_BLUE"]};
}}
QPushButton:pressed {{
background-color: {COLORS["ACCENT_DARK"]};
}}
QPushButton:disabled {{
background-color: {COLORS["DISABLED"]};
color: #94A3B8;
}}
/* 主操作按钮(绿色 - 连接设备) */
QPushButton[cssClass="action"] {{
background-color: {COLORS["SUCCESS_GREEN"]};
border: none;
color: #FFFFFF;
padding: 8px 24px;
font-size: 14px;
font-weight: bold;
border-radius: 6px;
}}
QPushButton[cssClass="action"]:hover {{
background-color: #0D8250;
}}
QPushButton[cssClass="action"]:pressed {{
background-color: {COLORS["SUCCESS_ACTIVE"]};
}}
/* 次要按钮 / 刷新按钮 */
QPushButton[cssClass="refresh"] {{
background-color: #FFFFFF;
border: 1px solid {COLORS["REFRESH_BORDER"]};
color: {COLORS["ACCENT_LIGHT"]};
padding: 8px 20px;
font-size: 14px;
font-weight: bold;
border-radius: 6px;
}}
QPushButton[cssClass="refresh"]:hover {{
background-color: #F1F5F9;
border-color: {COLORS["ACCENT_LIGHT"]};
}}
QPushButton[cssClass="refresh"]:pressed {{
background-color: #E2E8F0;
}}
/* ===== 底部操作按钮(ID 选择器,精确控制高度与内边距,修复文字截断) ===== */
QPushButton#refresh_btn {{
background-color: #FFFFFF;
border: 1px solid #CCDBF0;
color: #0960D1;
border-radius: 6px;
min-height: 34px;
max-height: 34px;
padding: 0px 20px;
font-weight: bold;
font-size: 14px;
outline: none;
}}
QPushButton#refresh_btn:hover {{
background-color: #F0F4FA;
}}
QPushButton#refresh_btn:focus {{
outline: none;
}}
QPushButton#connect_btn {{
background-color: #0F955D;
border: none;
color: #FFFFFF;
border-radius: 6px;
min-height: 34px;
max-height: 34px;
padding: 0px 24px;
font-weight: bold;
font-size: 14px;
outline: none;
}}
QPushButton#connect_btn:hover {{
background-color: #0D8250;
}}
QPushButton#connect_btn:focus {{
outline: none;
}}
/* 危险按钮(红色,用于停止) */
QPushButton[cssClass="danger"] {{
background-color: #EF4444;
border: none;
color: #FFFFFF;
padding: 8px 24px;
font-size: 14px;
font-weight: bold;
border-radius: 6px;
}}
QPushButton[cssClass="danger"]:hover {{
background-color: #DC2626;
}}
QPushButton[cssClass="danger"]:pressed {{
background-color: #B91C1C;
}}
/* ===== QLineEdit 输入框 ===== */
QLineEdit {{
background-color: #FFFFFF;
border: 1px solid {COLORS["BORDER_COLOR"]};
border-radius: 6px;
padding-left: 12px;
color: #333333;
font-size: 14px;
min-height: 36px;
max-height: 36px;
}}
QLineEdit:focus {{
border: 1px solid {COLORS["ACCENT_LIGHT"]};
}}
QLineEdit:hover:!focus {{
border-color: #94A3B8;
}}
QLineEdit:disabled {{
background-color: #F1F5F9;
color: {COLORS["TEXT_MUTED"]};
border-color: #E2E8F0;
}}
/* ===== QComboBox 下拉框(增强鲁棒性,防止打包后黑底) ===== */
QComboBox {{
background-color: #FFFFFF;
border: 1px solid {COLORS["BORDER_COLOR"]};
border-radius: 6px;
padding: 7px 10px;
color: {COLORS["TEXT_MAIN"]};
font-size: 14px;
min-width: 100px;
min-height: 36px;
max-height: 36px;
outline: none;
}}
QComboBox:hover {{
border-color: #94A3B8;
}}
QComboBox:focus {{
border-color: {COLORS["ACCENT_LIGHT"]};
border-width: 1px;
}}
QComboBox:disabled {{
background-color: #F1F5F9;
color: {COLORS["TEXT_MUTED"]};
border-color: #E2E8F0;
}}
QComboBox::drop-down {{
subcontrol-origin: padding;
subcontrol-position: top right;
width: 28px;
border: none;
border-left: 1px solid {COLORS["BORDER_COLOR"]};
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
background-color: #F8FAFC;
}}
QComboBox::down-arrow {{
width: 12px;
height: 12px;
}}
/* 下拉弹出视图(最关键的修复点——保证白色背景) */
QComboBox QAbstractItemView {{
background-color: #FFFFFF;
border: 1px solid {COLORS["BORDER_COLOR"]};
border-radius: 4px;
selection-background-color: {COLORS["ACCENT_LIGHT"]};
selection-color: #FFFFFF;
color: {COLORS["TEXT_MAIN"]};
font-size: {BASE_FONT_SIZE};
padding: 4px;
outline: none;
}}
QComboBox QAbstractItemView::item {{
padding: 6px 10px;
border-radius: 3px;
color: {COLORS["TEXT_MAIN"]};
background-color: #FFFFFF;
}}
QComboBox QAbstractItemView::item:selected {{
background-color: {COLORS["ACCENT_LIGHT"]};
color: #FFFFFF;
}}
QComboBox QAbstractItemView::item:hover {{
background-color: #EFF6FF;
}}
/* 防止下拉滚动条区域也变黑 */
QComboBox QAbstractScrollArea {{
background-color: #FFFFFF;
color: {COLORS["TEXT_MAIN"]};
}}
/* ===== QRadioButton / QCheckBox ===== */
QRadioButton, QCheckBox {{
background-color: transparent;
color: {COLORS["TEXT_MAIN"]};
font-size: {BASE_FONT_SIZE};
spacing: 8px;
}}
QRadioButton::indicator {{
width: 18px;
height: 18px;
border-radius: 9px;
border: 2px solid {COLORS["BORDER_COLOR"]};
background-color: #FFFFFF;
}}
QRadioButton::indicator:checked {{
background-color: {COLORS["ACCENT_BLUE"]};
border-color: {COLORS["ACCENT_BLUE"]};
}}
QRadioButton::indicator:hover {{
border-color: {COLORS["ACCENT_BLUE"]};
}}
QRadioButton::indicator:checked:hover {{
background-color: {COLORS["ACCENT_DARK"]};
border-color: {COLORS["ACCENT_DARK"]};
}}
QRadioButton:disabled {{
color: {COLORS["DISABLED"]};
}}
QRadioButton::indicator:disabled {{
background-color: #F1F5F9;
border-color: {COLORS["DISABLED"]};
}}
QCheckBox::indicator {{
width: 18px;
height: 18px;
border-radius: 4px;
border: 2px solid {COLORS["BORDER_COLOR"]};
background-color: #FFFFFF;
}}
QCheckBox::indicator:checked {{
background-color: {COLORS["ACCENT_BLUE"]};
border-color: {COLORS["ACCENT_BLUE"]};
}}
QCheckBox::indicator:hover {{
border-color: {COLORS["ACCENT_BLUE"]};
}}
QCheckBox::indicator:checked:hover {{
background-color: {COLORS["ACCENT_DARK"]};
border-color: {COLORS["ACCENT_DARK"]};
}}
QCheckBox:disabled {{
color: {COLORS["DISABLED"]};
}}
QCheckBox::indicator:disabled {{
background-color: #F1F5F9;
border-color: {COLORS["DISABLED"]};
}}
/* ===== 仪表板卡片 ===== */
QFrame[cssClass="dashboardCard"] {{
background-color: {COLORS["CARD_BG"]};
border: 1px solid {COLORS["BORDER_COLOR"]};
border-radius: 12px;
padding: 12px;
}}
QFrame[cssClass="dashboardCard"] QLabel {{
background-color: transparent;
}}
QLabel[cssClass="dashboardLabel"] {{
color: {COLORS["TEXT_MUTED"]};
font-size: {BASE_FONT_SIZE_SM};
font-weight: normal;
}}
QLabel[cssClass="dashboardValue"] {{
font-family: "SF Mono, Menlo, Consolas, monospace";
font-size: {BASE_FONT_SIZE_XL};
font-weight: bold;
}}
/* ===== 底部状态栏 ===== */
QWidget[cssClass="bottomBar"] {{
background-color: #FFFFFF;
border-top: 1px solid {COLORS["BORDER_COLOR"]};
}}
QLabel[cssClass="logLabel"] {{
color: #757575;
font-family: "SF Mono, Consolas, Menlo, monospace";
font-size: 12px;
background-color: transparent;
}}
QLabel[cssClass="statusLabel"] {{
font-weight: bold;
font-size: 14px;
background-color: transparent;
}}
/* ===== 透明背景容器(避免 inline stylesheet 覆盖子控件 QSS ===== */
QWidget[cssClass="transparentBg"] {{
background-color: transparent;
}}
/* ===== QStackedWidget 页面 ===== */
QWidget[cssClass="tabPage"] {{
background-color: #FFFFFF;
}}
/* ===== 分组辅助标签 ===== */
QLabel[cssClass="section"] {{
color: {COLORS["ACCENT_DARK"]};
font-weight: bold;
font-size: {BASE_FONT_SIZE_LG};
background-color: transparent;
}}
/* ===== 导航栏标题 ===== */
QLabel[cssClass="navTitle"] {{
color: {COLORS["TEXT_MAIN"]};
font-size: 22px;
font-weight: bold;
background-color: transparent;
letter-spacing: 0px;
}}
QLabel[cssClass="navSubtitle"] {{
color: {COLORS["ACCENT_DARK"]};
font-size: 11px;
font-weight: normal;
background-color: transparent;
}}
QLabel[cssClass="navDivider"] {{
color: {COLORS["BORDER_COLOR"]};
background-color: transparent;
}}
/* 设置按钮(圆形,右上角) */
QPushButton[cssClass="navSettings"] {{
background-color: transparent;
border: 1.5px solid {COLORS["BORDER_COLOR"]};
border-radius: 18px;
padding: 4px;
min-width: 36px;
max-width: 36px;
min-height: 36px;
max-height: 36px;
}}
QPushButton[cssClass="navSettings"]:hover {{
background-color: {COLORS["BG_COLOR"]};
border-color: #CBD5E1;
}}
QPushButton[cssClass="navSettings"]:pressed {{
background-color: #E2E8F0;
}}
"""
def apply_app_style(app: QApplication):
"""配置全局 QSS 样式表与窗口默认调色板,返回配色字典供布局复用。"""
# ---- 强制使用 Fusion 风格(跨平台一致,避免 Windows 原生风格/深色模式干扰 QSS ----
app.setStyle("Fusion")
# 应用 QSS 样式表
app.setStyleSheet(QSS_STYLESHEET)
# 设置默认字体
font = app.font()
font.setFamily(BASE_FONT_FAMILY.split(",")[0].strip().strip('"'))
font.setPointSize(10)
app.setFont(font)
# 配置默认调色板(仅设置 Window/Base 等基础角色,不污染 Button/ComboBox
palette = QPalette()
palette.setColor(QPalette.Window, QColor(COLORS["BG_COLOR"]))
palette.setColor(QPalette.WindowText, QColor(COLORS["TEXT_MAIN"]))
palette.setColor(QPalette.Base, QColor("#FFFFFF"))
palette.setColor(QPalette.Text, QColor(COLORS["TEXT_MAIN"]))
palette.setColor(QPalette.Button, QColor("#FFFFFF")) # 白色底,避免黑色
palette.setColor(QPalette.ButtonText, QColor(COLORS["TEXT_MAIN"]))
palette.setColor(QPalette.Highlight, QColor(COLORS["ACCENT_LIGHT"]))
palette.setColor(QPalette.HighlightedText, QColor("#FFFFFF"))
app.setPalette(palette)
return COLORS