#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ KDocs panel - configure doc URL, columns, QR login, manual upload """ import base64 from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QGroupBox, QSpinBox, QCheckBox, QMessageBox, QScrollArea, QFrame ) from PyQt6.QtCore import Qt, pyqtSignal, QTimer from PyQt6.QtGui import QPixmap from config import get_config, save_config from .constants import ( PAGE_PADDING, SECTION_SPACING, GROUP_PADDING_TOP, GROUP_PADDING_SIDE, GROUP_PADDING_BOTTOM, GROUP_SPACING, FORM_ROW_SPACING, FORM_LABEL_WIDTH, FORM_H_SPACING, INPUT_HEIGHT, INPUT_MIN_WIDTH, INPUT_WIDTH_SHORT, BUTTON_HEIGHT, BUTTON_HEIGHT_NORMAL, BUTTON_MIN_WIDTH, BUTTON_SPACING, QR_CODE_SIZE, get_title_style, get_status_style ) class KDocsWidget(QWidget): """KDocs panel""" log_signal = pyqtSignal(str) def __init__(self, parent=None): super().__init__(parent) self._worker = None self._login_check_timer = None self._setup_ui() self._load_config() def _setup_ui(self): main_layout = QVBoxLayout(self) main_layout.setContentsMargins(16, 16, 16, 16) main_layout.setSpacing(16) # ==================== Title + Save button ==================== header_layout = QHBoxLayout() title = QLabel("📤 金山文档上传") title.setStyleSheet(get_title_style()) header_layout.addWidget(title) header_layout.addStretch() self.enable_check = QCheckBox("启用上传") self.enable_check.stateChanged.connect(self._on_enable_changed) header_layout.addWidget(self.enable_check) self.save_btn = QPushButton("保存配置") self.save_btn.setObjectName("primary") self.save_btn.setFixedHeight(36) self.save_btn.setMinimumWidth(100) self.save_btn.clicked.connect(self._save_config) header_layout.addWidget(self.save_btn) main_layout.addLayout(header_layout) # ==================== Document config ==================== config_group = QGroupBox("文档配置") config_layout = QVBoxLayout(config_group) config_layout.setContentsMargins(16, 24, 16, 16) config_layout.setSpacing(12) # Row 1: Doc URL row1 = QHBoxLayout() row1.setSpacing(10) lbl1 = QLabel("文档链接:") lbl1.setFixedWidth(70) row1.addWidget(lbl1) self.doc_url_edit = QLineEdit() self.doc_url_edit.setPlaceholderText("https://kdocs.cn/l/xxxxxx") self.doc_url_edit.setMinimumHeight(36) row1.addWidget(self.doc_url_edit, 1) config_layout.addLayout(row1) # Row 2: Sheet name + Unit row2 = QHBoxLayout() row2.setSpacing(10) lbl2 = QLabel("工作表:") lbl2.setFixedWidth(70) row2.addWidget(lbl2) self.sheet_name_edit = QLineEdit() self.sheet_name_edit.setPlaceholderText("Sheet1") self.sheet_name_edit.setMinimumHeight(36) self.sheet_name_edit.setFixedWidth(150) row2.addWidget(self.sheet_name_edit) row2.addSpacing(30) lbl2b = QLabel("所属县区:") lbl2b.setFixedWidth(70) row2.addWidget(lbl2b) self.unit_edit = QLineEdit() self.unit_edit.setPlaceholderText("如:XX县") self.unit_edit.setMinimumHeight(36) row2.addWidget(self.unit_edit, 1) config_layout.addLayout(row2) # Row 3: Columns row3 = QHBoxLayout() row3.setSpacing(10) lbl3 = QLabel("县区列:") lbl3.setFixedWidth(70) row3.addWidget(lbl3) self.unit_col_edit = QLineEdit() self.unit_col_edit.setFixedWidth(80) self.unit_col_edit.setMinimumHeight(36) self.unit_col_edit.setPlaceholderText("A") row3.addWidget(self.unit_col_edit) row3.addSpacing(20) lbl3b = QLabel("姓名列:") lbl3b.setFixedWidth(60) row3.addWidget(lbl3b) self.name_col_edit = QLineEdit() self.name_col_edit.setFixedWidth(80) self.name_col_edit.setMinimumHeight(36) self.name_col_edit.setPlaceholderText("C") row3.addWidget(self.name_col_edit) row3.addSpacing(20) lbl3c = QLabel("图片列:") lbl3c.setFixedWidth(60) row3.addWidget(lbl3c) self.image_col_edit = QLineEdit() self.image_col_edit.setFixedWidth(80) self.image_col_edit.setMinimumHeight(36) self.image_col_edit.setPlaceholderText("D") row3.addWidget(self.image_col_edit) row3.addStretch() config_layout.addLayout(row3) # Row 4: Row range - SpinBox加宽确保"不限制"显示完整 row4 = QHBoxLayout() row4.setSpacing(10) lbl4 = QLabel("起始行:") lbl4.setFixedWidth(70) row4.addWidget(lbl4) self.row_start_spin = QSpinBox() self.row_start_spin.setRange(0, 10000) self.row_start_spin.setSpecialValueText("不限制") self.row_start_spin.setFixedWidth(120) self.row_start_spin.setMinimumHeight(36) row4.addWidget(self.row_start_spin) row4.addSpacing(20) lbl4b = QLabel("结束行:") lbl4b.setFixedWidth(60) row4.addWidget(lbl4b) self.row_end_spin = QSpinBox() self.row_end_spin.setRange(0, 10000) self.row_end_spin.setSpecialValueText("不限制") self.row_end_spin.setFixedWidth(120) self.row_end_spin.setMinimumHeight(36) row4.addWidget(self.row_end_spin) row4.addStretch() config_layout.addLayout(row4) main_layout.addWidget(config_group) # ==================== Login status ==================== login_group = QGroupBox("登录状态") login_layout = QHBoxLayout(login_group) login_layout.setContentsMargins(16, 24, 16, 16) login_layout.setSpacing(20) # QR code self.qr_label = QLabel() self.qr_label.setFixedSize(150, 150) self.qr_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.qr_label.setStyleSheet("border: 1px solid #d0d0d0; border-radius: 6px; background: #fafafa;") self.qr_label.setText("点击获取二维码") login_layout.addWidget(self.qr_label) # Status and buttons status_btn_layout = QVBoxLayout() status_btn_layout.setSpacing(10) self.status_label = QLabel("未登录") self.status_label.setStyleSheet(get_status_style(False)) status_btn_layout.addWidget(self.status_label) status_btn_layout.addStretch() self.get_qr_btn = QPushButton("获取二维码") self.get_qr_btn.setObjectName("primary") self.get_qr_btn.setFixedHeight(36) self.get_qr_btn.setMinimumWidth(120) self.get_qr_btn.clicked.connect(self._get_qr_code) status_btn_layout.addWidget(self.get_qr_btn) self.clear_login_btn = QPushButton("清除登录") self.clear_login_btn.setFixedHeight(36) self.clear_login_btn.setMinimumWidth(120) self.clear_login_btn.clicked.connect(self._clear_login) status_btn_layout.addWidget(self.clear_login_btn) status_btn_layout.addStretch() login_layout.addLayout(status_btn_layout) login_layout.addStretch() main_layout.addWidget(login_group) main_layout.addStretch() def _load_config(self): """Load config""" config = get_config() kdocs = config.kdocs self.enable_check.setChecked(kdocs.enabled) self.doc_url_edit.setText(kdocs.doc_url) self.sheet_name_edit.setText(kdocs.sheet_name) self.unit_col_edit.setText(kdocs.unit_column) self.name_col_edit.setText(kdocs.name_column) self.image_col_edit.setText(kdocs.image_column) self.row_start_spin.setValue(kdocs.row_start) self.row_end_spin.setValue(kdocs.row_end) self.unit_edit.setText(kdocs.unit) self._on_enable_changed(Qt.CheckState.Checked.value if kdocs.enabled else Qt.CheckState.Unchecked.value) def _save_config(self): """Save config""" config = get_config() config.kdocs.enabled = self.enable_check.isChecked() config.kdocs.doc_url = self.doc_url_edit.text().strip() config.kdocs.sheet_name = self.sheet_name_edit.text().strip() config.kdocs.unit_column = self.unit_col_edit.text().strip().upper() or "A" config.kdocs.name_column = self.name_col_edit.text().strip().upper() or "C" config.kdocs.image_column = self.image_col_edit.text().strip().upper() or "D" config.kdocs.row_start = self.row_start_spin.value() config.kdocs.row_end = self.row_end_spin.value() config.kdocs.unit = self.unit_edit.text().strip() save_config(config) self.log_signal.emit("金山文档配置已保存") QMessageBox.information(self, "提示", "配置已保存") def _on_enable_changed(self, state): """Enable state changed""" pass # Can disable/enable other controls if needed def _get_qr_code(self): """Get login QR code and poll for login status""" self.get_qr_btn.setEnabled(False) self.qr_label.setText("获取中...") self.log_signal.emit("🔄 正在获取登录二维码...") # 停止之前的检查 if self._login_check_timer: self._login_check_timer.stop() self._login_check_timer = None from utils.worker import Worker def get_qr_and_wait_login(_signals=None, _should_stop=None): """获取二维码并轮询等待登录""" from core.kdocs_uploader import get_kdocs_uploader import time uploader = get_kdocs_uploader() uploader._log_callback = None # 静默模式 # 1. 获取二维码 result = uploader.request_qr(force=True) if not result.get("success"): return result if result.get("logged_in"): return result # 2. 发送二维码图片(通过信号) qr_image = result.get("qr_image", "") if qr_image: _signals.screenshot_ready.emit(qr_image) # 复用这个信号传递二维码 # 3. 轮询检查登录状态 max_wait = 120 check_interval = 3 waited = 0 while waited < max_wait: if _should_stop and _should_stop(): return {"success": False, "error": "用户取消"} time.sleep(check_interval) waited += check_interval check_result = uploader.check_login_status() if check_result.get("logged_in"): return {"success": True, "logged_in": True} return {"success": False, "error": "登录超时,请重新获取二维码"} def on_qr_ready(qr_base64): """二维码准备好了,显示出来""" if qr_base64: qr_bytes = base64.b64decode(qr_base64) pixmap = QPixmap() pixmap.loadFromData(qr_bytes) scaled = pixmap.scaled(140, 140, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) self.qr_label.setPixmap(scaled) self.log_signal.emit("📱 请使用微信扫描二维码登录") def on_result(result): self.get_qr_btn.setEnabled(True) if result and result.get("success"): if result.get("logged_in"): self.status_label.setText("✅ 已登录") self.status_label.setStyleSheet(get_status_style(True)) self.qr_label.setText("登录成功!") self.log_signal.emit("✅ 金山文档登录成功") else: error = result.get("error", "未知错误") if result else "未知错误" self.qr_label.setText(f"失败") self.status_label.setText("❌ 未登录") self.status_label.setStyleSheet(get_status_style(False)) self.log_signal.emit(f"❌ 登录失败: {error}") def on_error(error): self.get_qr_btn.setEnabled(True) self.qr_label.setText("获取失败") self.log_signal.emit(f"❌ 二维码获取失败: {error}") self._worker = Worker(get_qr_and_wait_login) self._worker.signals.log.connect(self.log_signal.emit) self._worker.signals.screenshot_ready.connect(on_qr_ready) # 用于接收二维码 self._worker.signals.result.connect(on_result) self._worker.signals.error.connect(on_error) self._worker.start() def _clear_login(self): """Clear login status""" from core.kdocs_uploader import get_kdocs_uploader uploader = get_kdocs_uploader() uploader.clear_login() self.status_label.setText("❌ 未登录") self.status_label.setStyleSheet(get_status_style(False)) self.qr_label.setText("点击获取二维码") self.log_signal.emit("🔓 登录状态已清除") if self._login_check_timer: self._login_check_timer.stop() self._login_check_timer = None