feat: 知识管理平台精简版 - PyQt6桌面应用

主要功能:
- 账号管理:添加/编辑/删除账号,测试登录
- 浏览任务:批量浏览应读/选读内容并标记已读
- 截图管理:wkhtmltoimage截图,查看历史
- 金山文档:扫码登录/微信快捷登录,自动上传截图

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-18 22:16:36 +08:00
commit 83fef6dff2
24 changed files with 6133 additions and 0 deletions

228
config.py Normal file
View File

@@ -0,0 +1,228 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
配置管理模块 - 精简版
使用JSON本地存储不搞数据库那些花里胡哨的东西
"""
import os
from dataclasses import dataclass, field
from typing import List, Optional
from pathlib import Path
# 项目根目录
BASE_DIR = Path(__file__).parent.absolute()
DATA_DIR = BASE_DIR / "data"
COOKIES_DIR = DATA_DIR / "cookies"
SCREENSHOTS_DIR = DATA_DIR / "screenshots"
# 确保目录存在
DATA_DIR.mkdir(exist_ok=True)
COOKIES_DIR.mkdir(exist_ok=True)
SCREENSHOTS_DIR.mkdir(exist_ok=True)
# 配置文件路径
CONFIG_FILE = DATA_DIR / "config.json"
ENCRYPTION_KEY_FILE = DATA_DIR / "encryption_key.bin"
ENCRYPTION_SALT_FILE = DATA_DIR / "encryption_salt.bin"
KDOCS_LOGIN_STATE_FILE = DATA_DIR / "kdocs_login_state.json"
@dataclass
class AccountConfig:
"""账号配置"""
username: str
password: str # 加密存储
remark: str = ""
enabled: bool = True
@dataclass
class KDocsConfig:
"""金山文档配置"""
enabled: bool = False
doc_url: str = "https://kdocs.cn/l/cpwEOo5ynKX4"
sheet_name: str = "Sheet1"
sheet_index: int = 0
unit_column: str = "A"
image_column: str = "D"
unit: str = "" # 县区名
name_column: str = "C" # 姓名列
row_start: int = 0 # 有效行起始0表示不限制
row_end: int = 0 # 有效行结束0表示不限制
@dataclass
class ScreenshotConfig:
"""截图配置"""
dir: str = str(SCREENSHOTS_DIR)
quality: int = 95
width: int = 1920
height: int = 1080
js_delay_ms: int = 3000
timeout_seconds: int = 60
wkhtmltoimage_path: str = "" # 自定义路径,空则自动查找
@dataclass
class ProxyConfig:
"""代理配置"""
enabled: bool = False
server: str = "" # http://host:port
@dataclass
class ZSGLConfig:
"""知识管理平台配置"""
base_url: str = "https://postoa.aidunsoft.com"
login_url: str = "https://postoa.aidunsoft.com/admin/login.aspx"
index_url_pattern: str = "index.aspx"
@dataclass
class AppConfig:
"""应用总配置"""
accounts: List[AccountConfig] = field(default_factory=list)
kdocs: KDocsConfig = field(default_factory=KDocsConfig)
screenshot: ScreenshotConfig = field(default_factory=ScreenshotConfig)
proxy: ProxyConfig = field(default_factory=ProxyConfig)
zsgl: ZSGLConfig = field(default_factory=ZSGLConfig)
theme: str = "light" # light/dark
def to_dict(self) -> dict:
"""转换为字典"""
return {
"accounts": [
{
"username": a.username,
"password": a.password,
"remark": a.remark,
"enabled": a.enabled
} for a in self.accounts
],
"kdocs": {
"enabled": self.kdocs.enabled,
"doc_url": self.kdocs.doc_url,
"sheet_name": self.kdocs.sheet_name,
"sheet_index": self.kdocs.sheet_index,
"unit_column": self.kdocs.unit_column,
"image_column": self.kdocs.image_column,
"unit": self.kdocs.unit,
"name_column": self.kdocs.name_column,
"row_start": self.kdocs.row_start,
"row_end": self.kdocs.row_end,
},
"screenshot": {
"dir": self.screenshot.dir,
"quality": self.screenshot.quality,
"width": self.screenshot.width,
"height": self.screenshot.height,
"js_delay_ms": self.screenshot.js_delay_ms,
"timeout_seconds": self.screenshot.timeout_seconds,
"wkhtmltoimage_path": self.screenshot.wkhtmltoimage_path,
},
"proxy": {
"enabled": self.proxy.enabled,
"server": self.proxy.server,
},
"zsgl": {
"base_url": self.zsgl.base_url,
"login_url": self.zsgl.login_url,
"index_url_pattern": self.zsgl.index_url_pattern,
},
"theme": self.theme,
}
@classmethod
def from_dict(cls, data: dict) -> "AppConfig":
"""从字典创建配置"""
config = cls()
# 加载账号
accounts_data = data.get("accounts", [])
config.accounts = [
AccountConfig(
username=a.get("username", ""),
password=a.get("password", ""),
remark=a.get("remark", ""),
enabled=a.get("enabled", True)
) for a in accounts_data
]
# 加载金山文档配置
kdocs_data = data.get("kdocs", {})
config.kdocs = KDocsConfig(
enabled=kdocs_data.get("enabled", False),
doc_url=kdocs_data.get("doc_url", "https://kdocs.cn/l/cpwEOo5ynKX4"),
sheet_name=kdocs_data.get("sheet_name", "Sheet1"),
sheet_index=kdocs_data.get("sheet_index", 0),
unit_column=kdocs_data.get("unit_column", "A"),
image_column=kdocs_data.get("image_column", "D"),
unit=kdocs_data.get("unit", ""),
name_column=kdocs_data.get("name_column", "C"),
row_start=kdocs_data.get("row_start", 0),
row_end=kdocs_data.get("row_end", 0),
)
# 加载截图配置
screenshot_data = data.get("screenshot", {})
config.screenshot = ScreenshotConfig(
dir=screenshot_data.get("dir", str(SCREENSHOTS_DIR)),
quality=screenshot_data.get("quality", 95),
width=screenshot_data.get("width", 1920),
height=screenshot_data.get("height", 1080),
js_delay_ms=screenshot_data.get("js_delay_ms", 3000),
timeout_seconds=screenshot_data.get("timeout_seconds", 60),
wkhtmltoimage_path=screenshot_data.get("wkhtmltoimage_path", ""),
)
# 加载代理配置
proxy_data = data.get("proxy", {})
config.proxy = ProxyConfig(
enabled=proxy_data.get("enabled", False),
server=proxy_data.get("server", ""),
)
# 加载知识管理平台配置
zsgl_data = data.get("zsgl", {})
config.zsgl = ZSGLConfig(
base_url=zsgl_data.get("base_url", "https://postoa.aidunsoft.com"),
login_url=zsgl_data.get("login_url", "https://postoa.aidunsoft.com/admin/login.aspx"),
index_url_pattern=zsgl_data.get("index_url_pattern", "index.aspx"),
)
# 主题
config.theme = data.get("theme", "light")
return config
# 全局配置实例
_config: Optional[AppConfig] = None
def get_config() -> AppConfig:
"""获取配置实例(懒加载)"""
global _config
if _config is None:
from utils.storage import load_config
_config = load_config()
return _config
def save_config(config: AppConfig = None):
"""保存配置"""
global _config
if config:
_config = config
if _config:
from utils.storage import save_config as _save
_save(_config)
def reload_config():
"""重新加载配置"""
global _config
from utils.storage import load_config
_config = load_config()
return _config