Files
zsglpt-pc/ui/settings_widget.py
237899745 83fef6dff2 feat: 知识管理平台精简版 - PyQt6桌面应用
主要功能:
- 账号管理:添加/编辑/删除账号,测试登录
- 浏览任务:批量浏览应读/选读内容并标记已读
- 截图管理:wkhtmltoimage截图,查看历史
- 金山文档:扫码登录/微信快捷登录,自动上传截图

技术栈:
- PyQt6 GUI框架
- Playwright 浏览器自动化
- SQLite 本地数据存储
- wkhtmltoimage 网页截图

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 22:16:36 +08:00

266 lines
10 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Settings panel - wkhtmltoimage path, screenshot quality, theme
"""
import os
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton,
QGroupBox, QFormLayout, QSpinBox, QComboBox, QMessageBox,
QFileDialog, QScrollArea, QFrame
)
from PyQt6.QtCore import Qt, pyqtSignal
from config import get_config, save_config
from .constants import (
PAGE_PADDING, SECTION_SPACING, GROUP_PADDING_TOP, GROUP_PADDING_SIDE,
GROUP_PADDING_BOTTOM, FORM_ROW_SPACING, FORM_H_SPACING,
INPUT_HEIGHT, INPUT_MIN_WIDTH, BUTTON_HEIGHT, BUTTON_HEIGHT_NORMAL,
BUTTON_MIN_WIDTH, BUTTON_MIN_WIDTH_NORMAL, BUTTON_SPACING,
get_title_style
)
class SettingsWidget(QWidget):
"""Settings panel"""
log_signal = pyqtSignal(str)
theme_changed = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
self._setup_ui()
self._load_settings()
def _setup_ui(self):
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# Scroll area
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.Shape.NoFrame)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
content = QWidget()
layout = QVBoxLayout(content)
layout.setContentsMargins(PAGE_PADDING, PAGE_PADDING, PAGE_PADDING, PAGE_PADDING)
layout.setSpacing(SECTION_SPACING)
# Title
title = QLabel("⚙️ 系统设置")
title.setStyleSheet(get_title_style())
layout.addWidget(title)
# ==================== Screenshot settings ====================
screenshot_group = QGroupBox("截图设置")
screenshot_layout = QFormLayout(screenshot_group)
screenshot_layout.setContentsMargins(GROUP_PADDING_SIDE, GROUP_PADDING_TOP, GROUP_PADDING_SIDE, GROUP_PADDING_BOTTOM)
screenshot_layout.setSpacing(FORM_ROW_SPACING)
screenshot_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
# wkhtmltoimage path
wkhtml_layout = QHBoxLayout()
wkhtml_layout.setSpacing(FORM_H_SPACING)
self.wkhtml_edit = QLineEdit()
self.wkhtml_edit.setPlaceholderText("留空则自动检测")
self.wkhtml_edit.setMinimumHeight(INPUT_HEIGHT)
wkhtml_layout.addWidget(self.wkhtml_edit)
browse_btn = QPushButton("浏览...")
browse_btn.setMinimumHeight(BUTTON_HEIGHT_NORMAL)
browse_btn.clicked.connect(self._browse_wkhtml)
wkhtml_layout.addWidget(browse_btn)
detect_btn = QPushButton("检测")
detect_btn.setMinimumHeight(BUTTON_HEIGHT_NORMAL)
detect_btn.clicked.connect(self._detect_wkhtml)
wkhtml_layout.addWidget(detect_btn)
screenshot_layout.addRow("wkhtmltoimage:", wkhtml_layout)
# Screenshot width
self.width_spin = QSpinBox()
self.width_spin.setRange(800, 3840)
self.width_spin.setSingleStep(100)
self.width_spin.setMinimumHeight(INPUT_HEIGHT)
self.width_spin.setMinimumWidth(130)
screenshot_layout.addRow("截图宽度:", self.width_spin)
# Screenshot height
self.height_spin = QSpinBox()
self.height_spin.setRange(600, 2160)
self.height_spin.setSingleStep(100)
self.height_spin.setMinimumHeight(INPUT_HEIGHT)
self.height_spin.setMinimumWidth(130)
screenshot_layout.addRow("截图高度:", self.height_spin)
# Quality
self.quality_spin = QSpinBox()
self.quality_spin.setRange(50, 100)
self.quality_spin.setMinimumHeight(INPUT_HEIGHT)
self.quality_spin.setMinimumWidth(130)
self.quality_spin.setSuffix(" %")
screenshot_layout.addRow("JPEG质量:", self.quality_spin)
# JS delay
self.js_delay_spin = QSpinBox()
self.js_delay_spin.setRange(1000, 10000)
self.js_delay_spin.setSingleStep(500)
self.js_delay_spin.setSuffix(" ms")
self.js_delay_spin.setMinimumHeight(INPUT_HEIGHT)
self.js_delay_spin.setMinimumWidth(130)
screenshot_layout.addRow("JS等待时间:", self.js_delay_spin)
layout.addWidget(screenshot_group)
# ==================== Theme settings ====================
theme_group = QGroupBox("主题设置")
theme_layout = QFormLayout(theme_group)
theme_layout.setContentsMargins(GROUP_PADDING_SIDE, GROUP_PADDING_TOP, GROUP_PADDING_SIDE, GROUP_PADDING_BOTTOM)
theme_layout.setSpacing(FORM_ROW_SPACING)
theme_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self.theme_combo = QComboBox()
self.theme_combo.addItem("浅色主题", "light")
self.theme_combo.addItem("深色主题", "dark")
self.theme_combo.setMinimumHeight(INPUT_HEIGHT)
self.theme_combo.setMinimumWidth(INPUT_MIN_WIDTH)
self.theme_combo.currentIndexChanged.connect(self._on_theme_change)
theme_layout.addRow("界面主题:", self.theme_combo)
layout.addWidget(theme_group)
# ==================== Platform API settings ====================
api_group = QGroupBox("平台设置")
api_layout = QFormLayout(api_group)
api_layout.setContentsMargins(GROUP_PADDING_SIDE, GROUP_PADDING_TOP, GROUP_PADDING_SIDE, GROUP_PADDING_BOTTOM)
api_layout.setSpacing(FORM_ROW_SPACING)
api_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self.base_url_edit = QLineEdit()
self.base_url_edit.setMinimumHeight(INPUT_HEIGHT)
api_layout.addRow("平台地址:", self.base_url_edit)
self.login_url_edit = QLineEdit()
self.login_url_edit.setMinimumHeight(INPUT_HEIGHT)
api_layout.addRow("登录地址:", self.login_url_edit)
layout.addWidget(api_group)
# ==================== Action buttons ====================
btn_layout = QHBoxLayout()
btn_layout.setSpacing(BUTTON_SPACING)
btn_layout.addStretch()
reset_btn = QPushButton("恢复默认")
reset_btn.setMinimumWidth(BUTTON_MIN_WIDTH_NORMAL)
reset_btn.setMinimumHeight(BUTTON_HEIGHT)
reset_btn.clicked.connect(self._reset_defaults)
btn_layout.addWidget(reset_btn)
save_btn = QPushButton("💾 保存设置")
save_btn.setObjectName("primary")
save_btn.setMinimumWidth(BUTTON_MIN_WIDTH)
save_btn.setMinimumHeight(BUTTON_HEIGHT)
save_btn.clicked.connect(self._save_settings)
btn_layout.addWidget(save_btn)
layout.addLayout(btn_layout)
layout.addStretch()
scroll.setWidget(content)
main_layout.addWidget(scroll)
def _load_settings(self):
"""Load settings from config"""
config = get_config()
# Screenshot settings
self.wkhtml_edit.setText(config.screenshot.wkhtmltoimage_path)
self.width_spin.setValue(config.screenshot.width)
self.height_spin.setValue(config.screenshot.height)
self.quality_spin.setValue(config.screenshot.quality)
self.js_delay_spin.setValue(config.screenshot.js_delay_ms)
# Theme
idx = self.theme_combo.findData(config.theme)
if idx >= 0:
self.theme_combo.setCurrentIndex(idx)
# API settings
self.base_url_edit.setText(config.zsgl.base_url)
self.login_url_edit.setText(config.zsgl.login_url)
def _save_settings(self):
"""Save settings to config"""
config = get_config()
# Screenshot settings
config.screenshot.wkhtmltoimage_path = self.wkhtml_edit.text().strip()
config.screenshot.width = self.width_spin.value()
config.screenshot.height = self.height_spin.value()
config.screenshot.quality = self.quality_spin.value()
config.screenshot.js_delay_ms = self.js_delay_spin.value()
# Theme
config.theme = self.theme_combo.currentData()
# API settings
config.zsgl.base_url = self.base_url_edit.text().strip()
config.zsgl.login_url = self.login_url_edit.text().strip()
save_config(config)
self.log_signal.emit("设置已保存")
QMessageBox.information(self, "提示", "设置已保存")
def _browse_wkhtml(self):
"""Browse for wkhtmltoimage executable"""
filepath, _ = QFileDialog.getOpenFileName(
self, "选择 wkhtmltoimage",
"",
"可执行文件 (*.exe);;所有文件 (*)"
)
if filepath:
self.wkhtml_edit.setText(filepath)
def _detect_wkhtml(self):
"""Detect wkhtmltoimage path"""
from core.screenshot import _resolve_wkhtmltoimage_path
path = _resolve_wkhtmltoimage_path()
if path:
self.wkhtml_edit.setText(path)
self.log_signal.emit(f"检测到 wkhtmltoimage: {path}")
QMessageBox.information(self, "检测成功", f"找到 wkhtmltoimage:\n{path}")
else:
self.log_signal.emit("未检测到 wkhtmltoimage")
QMessageBox.warning(self, "检测失败", "未找到 wkhtmltoimage请手动指定路径或安装该工具。")
def _on_theme_change(self, index):
"""Handle theme change"""
theme = self.theme_combo.currentData()
self.theme_changed.emit(theme)
def _reset_defaults(self):
"""Reset to default settings"""
reply = QMessageBox.question(
self, "确认",
"确定要恢复默认设置吗?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self.wkhtml_edit.clear()
self.width_spin.setValue(1920)
self.height_spin.setValue(1080)
self.quality_spin.setValue(95)
self.js_delay_spin.setValue(3000)
self.theme_combo.setCurrentIndex(0) # 默认浅色主题
self.base_url_edit.setText("https://postoa.aidunsoft.com")
self.login_url_edit.setText("https://postoa.aidunsoft.com/admin/login.aspx")
self.log_signal.emit("已恢复默认设置")