feat: 知识管理平台精简版 - PyQt6桌面应用
主要功能: - 账号管理:添加/编辑/删除账号,测试登录 - 浏览任务:批量浏览应读/选读内容并标记已读 - 截图管理:wkhtmltoimage截图,查看历史 - 金山文档:扫码登录/微信快捷登录,自动上传截图 技术栈: - PyQt6 GUI框架 - Playwright 浏览器自动化 - SQLite 本地数据存储 - wkhtmltoimage 网页截图 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
635
ui/styles.py
Normal file
635
ui/styles.py
Normal file
@@ -0,0 +1,635 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
QSS Modern Stylesheet - 现代化UI样式
|
||||
老王说:样式写好了界面才漂亮,这些都是精心调教过的!
|
||||
"""
|
||||
|
||||
# 浅色主题
|
||||
LIGHT_THEME = {
|
||||
"bg_primary": "#ffffff",
|
||||
"bg_secondary": "#fafafa",
|
||||
"bg_tertiary": "#f0f0f0",
|
||||
"bg_card": "#ffffff",
|
||||
"text_primary": "#1a1a1a",
|
||||
"text_secondary": "#595959",
|
||||
"text_muted": "#8c8c8c",
|
||||
"text_placeholder": "#bfbfbf",
|
||||
"border": "#e8e8e8",
|
||||
"border_light": "#f0f0f0",
|
||||
"accent": "#1677ff",
|
||||
"accent_hover": "#4096ff",
|
||||
"accent_pressed": "#0958d9",
|
||||
"accent_bg": "#e6f4ff",
|
||||
"success": "#52c41a",
|
||||
"success_bg": "#f6ffed",
|
||||
"warning": "#faad14",
|
||||
"warning_bg": "#fffbe6",
|
||||
"error": "#ff4d4f",
|
||||
"error_bg": "#fff2f0",
|
||||
"sidebar_bg": "#001529",
|
||||
"sidebar_text": "#ffffffd9",
|
||||
"sidebar_text_muted": "#ffffff73",
|
||||
"sidebar_hover": "#1677ff",
|
||||
"sidebar_active_bg": "#1677ff",
|
||||
"shadow": "rgba(0, 0, 0, 0.08)",
|
||||
}
|
||||
|
||||
# 深色主题
|
||||
DARK_THEME = {
|
||||
"bg_primary": "#141414",
|
||||
"bg_secondary": "#1f1f1f",
|
||||
"bg_tertiary": "#262626",
|
||||
"bg_card": "#1f1f1f",
|
||||
"text_primary": "#ffffffd9",
|
||||
"text_secondary": "#ffffffa6",
|
||||
"text_muted": "#ffffff73",
|
||||
"text_placeholder": "#ffffff40",
|
||||
"border": "#424242",
|
||||
"border_light": "#303030",
|
||||
"accent": "#1668dc",
|
||||
"accent_hover": "#3c89e8",
|
||||
"accent_pressed": "#1554ad",
|
||||
"accent_bg": "#111a2c",
|
||||
"success": "#49aa19",
|
||||
"success_bg": "#162312",
|
||||
"warning": "#d89614",
|
||||
"warning_bg": "#2b2111",
|
||||
"error": "#dc4446",
|
||||
"error_bg": "#2c1618",
|
||||
"sidebar_bg": "#000000",
|
||||
"sidebar_text": "#ffffffd9",
|
||||
"sidebar_text_muted": "#ffffff73",
|
||||
"sidebar_hover": "#1668dc",
|
||||
"sidebar_active_bg": "#1668dc",
|
||||
"shadow": "rgba(0, 0, 0, 0.45)",
|
||||
}
|
||||
|
||||
|
||||
def get_stylesheet(theme: dict) -> str:
|
||||
"""Generate QSS stylesheet based on theme"""
|
||||
return f"""
|
||||
/* ==================== Global Reset ==================== */
|
||||
* {{
|
||||
outline: none;
|
||||
}}
|
||||
|
||||
QWidget {{
|
||||
font-family: "Microsoft YaHei UI", "Segoe UI", "PingFang SC", sans-serif;
|
||||
font-size: 14px;
|
||||
color: {theme["text_primary"]};
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
QMainWindow {{
|
||||
background-color: {theme["bg_secondary"]};
|
||||
}}
|
||||
|
||||
/* ==================== Scroll Area ==================== */
|
||||
QScrollArea {{
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QScrollArea > QWidget > QWidget {{
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
/* ==================== Sidebar ==================== */
|
||||
#sidebar {{
|
||||
background-color: {theme["sidebar_bg"]};
|
||||
border: none;
|
||||
}}
|
||||
|
||||
#sidebar QPushButton {{
|
||||
color: {theme["sidebar_text"]};
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 14px 20px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
|
||||
#sidebar QPushButton:hover {{
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}}
|
||||
|
||||
#sidebar QPushButton:checked {{
|
||||
background-color: {theme["sidebar_active_bg"]};
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}}
|
||||
|
||||
#sidebar_title {{
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
padding: 20px;
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
/* ==================== Buttons ==================== */
|
||||
QPushButton {{
|
||||
background-color: {theme["bg_card"]};
|
||||
color: {theme["text_primary"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
|
||||
QPushButton:hover {{
|
||||
border-color: {theme["accent"]};
|
||||
color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QPushButton:pressed {{
|
||||
background-color: {theme["bg_tertiary"]};
|
||||
}}
|
||||
|
||||
QPushButton:disabled {{
|
||||
background-color: {theme["bg_tertiary"]};
|
||||
color: {theme["text_muted"]};
|
||||
border-color: {theme["border"]};
|
||||
}}
|
||||
|
||||
/* Primary Button */
|
||||
QPushButton#primary {{
|
||||
background-color: {theme["accent"]};
|
||||
color: white;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
}}
|
||||
|
||||
QPushButton#primary:hover {{
|
||||
background-color: {theme["accent_hover"]};
|
||||
}}
|
||||
|
||||
QPushButton#primary:pressed {{
|
||||
background-color: {theme["accent_pressed"]};
|
||||
}}
|
||||
|
||||
QPushButton#primary:disabled {{
|
||||
background-color: {theme["accent"]};
|
||||
opacity: 0.6;
|
||||
}}
|
||||
|
||||
/* Success Button */
|
||||
QPushButton#success {{
|
||||
background-color: {theme["success"]};
|
||||
color: white;
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QPushButton#success:hover {{
|
||||
background-color: #73d13d;
|
||||
}}
|
||||
|
||||
/* Danger Button */
|
||||
QPushButton#danger {{
|
||||
background-color: {theme["error"]};
|
||||
color: white;
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QPushButton#danger:hover {{
|
||||
background-color: #ff7875;
|
||||
}}
|
||||
|
||||
/* ==================== Input Fields ==================== */
|
||||
QLineEdit {{
|
||||
background-color: {theme["bg_card"]};
|
||||
color: {theme["text_primary"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
selection-background-color: {theme["accent"]};
|
||||
selection-color: white;
|
||||
}}
|
||||
|
||||
QLineEdit:hover {{
|
||||
border-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QLineEdit:focus {{
|
||||
border-color: {theme["accent"]};
|
||||
border-width: 2px;
|
||||
padding: 5px 9px;
|
||||
}}
|
||||
|
||||
QLineEdit:disabled {{
|
||||
background-color: {theme["bg_tertiary"]};
|
||||
color: {theme["text_muted"]};
|
||||
}}
|
||||
|
||||
QLineEdit[readOnly="true"] {{
|
||||
background-color: {theme["bg_secondary"]};
|
||||
}}
|
||||
|
||||
/* ==================== TextEdit ==================== */
|
||||
QTextEdit, QPlainTextEdit {{
|
||||
background-color: {theme["bg_card"]};
|
||||
color: {theme["text_primary"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
selection-background-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QTextEdit:focus, QPlainTextEdit:focus {{
|
||||
border-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
/* ==================== ComboBox ==================== */
|
||||
QComboBox {{
|
||||
background-color: {theme["bg_card"]};
|
||||
color: {theme["text_primary"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
padding-right: 30px;
|
||||
}}
|
||||
|
||||
QComboBox:hover {{
|
||||
border-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QComboBox:focus {{
|
||||
border-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QComboBox::drop-down {{
|
||||
border: none;
|
||||
width: 30px;
|
||||
subcontrol-position: right center;
|
||||
}}
|
||||
|
||||
QComboBox::down-arrow {{
|
||||
image: none;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 6px solid {theme["text_secondary"]};
|
||||
margin-right: 10px;
|
||||
}}
|
||||
|
||||
QComboBox QAbstractItemView {{
|
||||
background-color: {theme["bg_card"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 6px;
|
||||
padding: 4px;
|
||||
selection-background-color: {theme["accent_bg"]};
|
||||
selection-color: {theme["accent"]};
|
||||
outline: none;
|
||||
}}
|
||||
|
||||
QComboBox QAbstractItemView::item {{
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
QComboBox QAbstractItemView::item:hover {{
|
||||
background-color: {theme["bg_tertiary"]};
|
||||
}}
|
||||
|
||||
/* ==================== SpinBox ==================== */
|
||||
QSpinBox {{
|
||||
background-color: {theme["bg_card"]};
|
||||
color: {theme["text_primary"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
padding-right: 30px;
|
||||
}}
|
||||
|
||||
QSpinBox:hover {{
|
||||
border-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QSpinBox:focus {{
|
||||
border-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QSpinBox::up-button, QSpinBox::down-button {{
|
||||
width: 20px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}}
|
||||
|
||||
QSpinBox::up-arrow, QSpinBox::down-arrow {{
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}}
|
||||
|
||||
/* ==================== CheckBox ==================== */
|
||||
QCheckBox {{
|
||||
spacing: 10px;
|
||||
color: {theme["text_primary"]};
|
||||
}}
|
||||
|
||||
QCheckBox::indicator {{
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 4px;
|
||||
background-color: {theme["bg_card"]};
|
||||
}}
|
||||
|
||||
QCheckBox::indicator:hover {{
|
||||
border-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QCheckBox::indicator:checked {{
|
||||
background-color: {theme["accent"]};
|
||||
border-color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
/* ==================== GroupBox - Card Style ==================== */
|
||||
QGroupBox {{
|
||||
background-color: {theme["bg_card"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 10px;
|
||||
margin-top: 14px;
|
||||
padding: 24px 0 0 0;
|
||||
}}
|
||||
|
||||
QGroupBox::title {{
|
||||
subcontrol-origin: margin;
|
||||
subcontrol-position: top left;
|
||||
left: 16px;
|
||||
top: 0px;
|
||||
padding: 4px 12px;
|
||||
background-color: {theme["accent"]};
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}}
|
||||
|
||||
/* ==================== Table ==================== */
|
||||
QTableWidget {{
|
||||
background-color: {theme["bg_card"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 8px;
|
||||
gridline-color: {theme["border_light"]};
|
||||
selection-background-color: {theme["accent_bg"]};
|
||||
selection-color: {theme["text_primary"]};
|
||||
}}
|
||||
|
||||
QTableWidget::item {{
|
||||
padding: 8px 4px;
|
||||
border: none;
|
||||
}}
|
||||
|
||||
QTableWidget::item:selected {{
|
||||
background-color: {theme["accent_bg"]};
|
||||
color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QTableWidget::item:hover {{
|
||||
background-color: {theme["bg_secondary"]};
|
||||
}}
|
||||
|
||||
QHeaderView::section {{
|
||||
background-color: {theme["bg_secondary"]};
|
||||
color: {theme["text_secondary"]};
|
||||
padding: 12px 8px;
|
||||
border: none;
|
||||
border-bottom: 1px solid {theme["border"]};
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}}
|
||||
|
||||
QHeaderView::section:first {{
|
||||
border-top-left-radius: 8px;
|
||||
}}
|
||||
|
||||
QHeaderView::section:last {{
|
||||
border-top-right-radius: 8px;
|
||||
}}
|
||||
|
||||
QTableCornerButton::section {{
|
||||
background-color: {theme["bg_secondary"]};
|
||||
border: none;
|
||||
}}
|
||||
|
||||
/* ==================== List ==================== */
|
||||
QListWidget {{
|
||||
background-color: {theme["bg_card"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
outline: none;
|
||||
}}
|
||||
|
||||
QListWidget::item {{
|
||||
padding: 12px 14px;
|
||||
border-radius: 6px;
|
||||
margin: 2px 0;
|
||||
}}
|
||||
|
||||
QListWidget::item:selected {{
|
||||
background-color: {theme["accent_bg"]};
|
||||
color: {theme["accent"]};
|
||||
}}
|
||||
|
||||
QListWidget::item:hover:!selected {{
|
||||
background-color: {theme["bg_secondary"]};
|
||||
}}
|
||||
|
||||
/* ==================== Progress Bar ==================== */
|
||||
QProgressBar {{
|
||||
background-color: {theme["bg_tertiary"]};
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 8px;
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
QProgressBar::chunk {{
|
||||
background-color: {theme["accent"]};
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
/* ==================== ScrollBar ==================== */
|
||||
QScrollBar:vertical {{
|
||||
background-color: transparent;
|
||||
width: 8px;
|
||||
margin: 4px 2px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:vertical {{
|
||||
background-color: {theme["border"]};
|
||||
border-radius: 4px;
|
||||
min-height: 40px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:vertical:hover {{
|
||||
background-color: {theme["text_muted"]};
|
||||
}}
|
||||
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
|
||||
height: 0;
|
||||
background: none;
|
||||
}}
|
||||
|
||||
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {{
|
||||
background: none;
|
||||
}}
|
||||
|
||||
QScrollBar:horizontal {{
|
||||
background-color: transparent;
|
||||
height: 8px;
|
||||
margin: 2px 4px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:horizontal {{
|
||||
background-color: {theme["border"]};
|
||||
border-radius: 4px;
|
||||
min-width: 40px;
|
||||
}}
|
||||
|
||||
QScrollBar::handle:horizontal:hover {{
|
||||
background-color: {theme["text_muted"]};
|
||||
}}
|
||||
|
||||
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{
|
||||
width: 0;
|
||||
background: none;
|
||||
}}
|
||||
|
||||
/* ==================== Tab Widget ==================== */
|
||||
QTabWidget::pane {{
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 8px;
|
||||
background-color: {theme["bg_card"]};
|
||||
padding: 8px;
|
||||
}}
|
||||
|
||||
QTabBar::tab {{
|
||||
background-color: transparent;
|
||||
color: {theme["text_secondary"]};
|
||||
padding: 10px 20px;
|
||||
margin-right: 4px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}}
|
||||
|
||||
QTabBar::tab:selected {{
|
||||
color: {theme["accent"]};
|
||||
border-bottom: 2px solid {theme["accent"]};
|
||||
}}
|
||||
|
||||
QTabBar::tab:hover:!selected {{
|
||||
color: {theme["text_primary"]};
|
||||
}}
|
||||
|
||||
/* ==================== ToolTip ==================== */
|
||||
QToolTip {{
|
||||
background-color: {theme["bg_tertiary"]};
|
||||
color: {theme["text_primary"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
}}
|
||||
|
||||
/* ==================== Message Box ==================== */
|
||||
QMessageBox {{
|
||||
background-color: {theme["bg_card"]};
|
||||
}}
|
||||
|
||||
QMessageBox QLabel {{
|
||||
color: {theme["text_primary"]};
|
||||
font-size: 14px;
|
||||
}}
|
||||
|
||||
QMessageBox QPushButton {{
|
||||
min-width: 80px;
|
||||
min-height: 32px;
|
||||
}}
|
||||
|
||||
/* ==================== Dialog ==================== */
|
||||
QDialog {{
|
||||
background-color: {theme["bg_card"]};
|
||||
}}
|
||||
|
||||
/* ==================== Label ==================== */
|
||||
QLabel {{
|
||||
color: {theme["text_primary"]};
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
QLabel#title {{
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: {theme["text_primary"]};
|
||||
}}
|
||||
|
||||
QLabel#subtitle {{
|
||||
font-size: 14px;
|
||||
color: {theme["text_secondary"]};
|
||||
}}
|
||||
|
||||
QLabel#help {{
|
||||
font-size: 13px;
|
||||
color: {theme["text_muted"]};
|
||||
}}
|
||||
|
||||
/* ==================== Frame ==================== */
|
||||
QFrame {{
|
||||
background-color: transparent;
|
||||
}}
|
||||
|
||||
QFrame#card {{
|
||||
background-color: {theme["bg_card"]};
|
||||
border: 1px solid {theme["border"]};
|
||||
border-radius: 10px;
|
||||
}}
|
||||
|
||||
QFrame#separator {{
|
||||
background-color: {theme["border"]};
|
||||
max-height: 1px;
|
||||
}}
|
||||
|
||||
/* ==================== Log Widget ==================== */
|
||||
#log_widget {{
|
||||
background-color: {theme["bg_secondary"]};
|
||||
border-top: 1px solid {theme["border"]};
|
||||
}}
|
||||
|
||||
#log_widget QPlainTextEdit {{
|
||||
background-color: {theme["bg_secondary"]};
|
||||
border: none;
|
||||
color: {theme["text_secondary"]};
|
||||
font-family: "Cascadia Code", "Consolas", "Monaco", monospace;
|
||||
font-size: 12px;
|
||||
padding: 8px;
|
||||
}}
|
||||
|
||||
/* ==================== Status Colors ==================== */
|
||||
.success {{
|
||||
color: {theme["success"]};
|
||||
}}
|
||||
|
||||
.warning {{
|
||||
color: {theme["warning"]};
|
||||
}}
|
||||
|
||||
.error {{
|
||||
color: {theme["error"]};
|
||||
}}
|
||||
|
||||
/* ==================== Form Label ==================== */
|
||||
QLabel#formLabel {{
|
||||
color: {theme["text_secondary"]};
|
||||
font-weight: 500;
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def apply_theme(app, theme_name: str = "light"):
|
||||
"""Apply theme to application"""
|
||||
theme = LIGHT_THEME if theme_name == "light" else DARK_THEME
|
||||
app.setStyleSheet(get_stylesheet(theme))
|
||||
Reference in New Issue
Block a user