feat: 知识管理平台精简版 - PyQt6桌面应用
主要功能: - 账号管理:添加/编辑/删除账号,测试登录 - 浏览任务:批量浏览应读/选读内容并标记已读 - 截图管理:wkhtmltoimage截图,查看历史 - 金山文档:扫码登录/微信快捷登录,自动上传截图 技术栈: - PyQt6 GUI框架 - Playwright 浏览器自动化 - SQLite 本地数据存储 - wkhtmltoimage 网页截图 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
265
ui/settings_widget.py
Normal file
265
ui/settings_widget.py
Normal file
@@ -0,0 +1,265 @@
|
||||
#!/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("已恢复默认设置")
|
||||
Reference in New Issue
Block a user