主要功能: - 账号管理:添加/编辑/删除账号,测试登录 - 浏览任务:批量浏览应读/选读内容并标记已读 - 截图管理:wkhtmltoimage截图,查看历史 - 金山文档:扫码登录/微信快捷登录,自动上传截图 技术栈: - PyQt6 GUI框架 - Playwright 浏览器自动化 - SQLite 本地数据存储 - wkhtmltoimage 网页截图 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
229 lines
7.1 KiB
Python
229 lines
7.1 KiB
Python
#!/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
|