#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Browse task panel - select accounts, browsing type, execute task with progress """ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton, QProgressBar, QGroupBox, QCheckBox, QListWidget, QListWidgetItem, QMessageBox, QScrollArea, QFrame, QSizePolicy ) from PyQt6.QtCore import Qt, pyqtSignal, QSize from config import get_config, AccountConfig from utils.crypto import decrypt_password, is_encrypted from .constants import ( PAGE_PADDING, SECTION_SPACING, GROUP_PADDING_TOP, GROUP_PADDING_SIDE, GROUP_PADDING_BOTTOM, GROUP_SPACING, FORM_LABEL_WIDTH, FORM_H_SPACING, INPUT_HEIGHT, INPUT_MIN_WIDTH, BUTTON_HEIGHT, BUTTON_HEIGHT_NORMAL, BUTTON_MIN_WIDTH, BUTTON_MIN_WIDTH_NORMAL, BUTTON_SPACING, LIST_MIN_HEIGHT, LIST_ITEM_HEIGHT, PROGRESS_HEIGHT, get_title_style, get_help_text_style ) class BrowseWidget(QWidget): """Browse task panel""" log_signal = pyqtSignal(str) def __init__(self, parent=None): super().__init__(parent) self._worker = None self._is_running = False self._setup_ui() self._refresh_accounts() 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(16, 12, 16, 12) layout.setSpacing(10) # ==================== Title + Control buttons ==================== header_layout = QHBoxLayout() header_layout.setSpacing(SECTION_SPACING) title = QLabel("🔄 浏览任务") title.setStyleSheet(get_title_style()) header_layout.addWidget(title) header_layout.addStretch() self.stop_btn = QPushButton("⏹ 停止") self.stop_btn.setMinimumWidth(BUTTON_MIN_WIDTH_NORMAL) self.stop_btn.setMinimumHeight(BUTTON_HEIGHT) self.stop_btn.setEnabled(False) self.stop_btn.clicked.connect(self._stop_task) header_layout.addWidget(self.stop_btn) self.start_btn = QPushButton("▶ 开始任务") self.start_btn.setObjectName("primary") self.start_btn.setMinimumWidth(BUTTON_MIN_WIDTH) self.start_btn.setMinimumHeight(BUTTON_HEIGHT) self.start_btn.clicked.connect(self._start_task) header_layout.addWidget(self.start_btn) layout.addLayout(header_layout) # ==================== Progress area (compact) ==================== progress_group = QGroupBox("任务进度") progress_layout = QVBoxLayout(progress_group) progress_layout.setContentsMargins(12, 8, 12, 8) progress_layout.setSpacing(6) # Current task + stats in one row task_row = QHBoxLayout() self.current_task_label = QLabel("当前任务: 无") task_row.addWidget(self.current_task_label) task_row.addStretch() self.stats_label = QLabel("已处理: 0 条内容,0 个附件") self.stats_label.setStyleSheet(get_help_text_style()) task_row.addWidget(self.stats_label) progress_layout.addLayout(task_row) # Progress bars in one row prog_row = QHBoxLayout() prog_row.setSpacing(12) prog_row.addWidget(QLabel("总体:")) self.total_progress = QProgressBar() self.total_progress.setValue(0) self.total_progress.setFixedHeight(18) prog_row.addWidget(self.total_progress, 1) prog_row.addWidget(QLabel("当前:")) self.account_progress = QProgressBar() self.account_progress.setValue(0) self.account_progress.setFixedHeight(18) prog_row.addWidget(self.account_progress, 1) progress_layout.addLayout(prog_row) layout.addWidget(progress_group) # ==================== Browse options (compact, one row) ==================== options_group = QGroupBox("浏览选项") options_layout = QHBoxLayout(options_group) options_layout.setContentsMargins(12, 8, 12, 8) options_layout.setSpacing(16) options_layout.addWidget(QLabel("浏览类型:")) self.browse_type_combo = QComboBox() self.browse_type_combo.addItems(["应读", "注册前未读"]) self.browse_type_combo.setMinimumWidth(140) self.browse_type_combo.setFixedHeight(32) self.browse_type_combo.setStyleSheet("QComboBox { padding-left: 10px; }") options_layout.addWidget(self.browse_type_combo) options_layout.addSpacing(20) self.auto_screenshot_check = QCheckBox("浏览后自动截图") self.auto_screenshot_check.setChecked(True) options_layout.addWidget(self.auto_screenshot_check) self.auto_upload_check = QCheckBox("截图后自动上传") self.auto_upload_check.setChecked(False) options_layout.addWidget(self.auto_upload_check) options_layout.addStretch() layout.addWidget(options_group) # ==================== Account selection (compact) ==================== account_group = QGroupBox("选择账号") account_group.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) account_layout = QVBoxLayout(account_group) account_layout.setContentsMargins(12, 8, 12, 8) account_layout.setSpacing(6) # Toolbar - compact buttons with proper width toolbar = QHBoxLayout() toolbar.setSpacing(8) self.select_all_btn = QPushButton("全选") self.select_all_btn.setFixedSize(70, 28) self.select_all_btn.setStyleSheet("QPushButton { padding: 2px 8px; }") self.select_all_btn.clicked.connect(self._select_all) toolbar.addWidget(self.select_all_btn) self.deselect_all_btn = QPushButton("取消全选") self.deselect_all_btn.setFixedSize(90, 28) self.deselect_all_btn.setStyleSheet("QPushButton { padding: 2px 8px; }") self.deselect_all_btn.clicked.connect(self._deselect_all) toolbar.addWidget(self.deselect_all_btn) toolbar.addStretch() self.refresh_btn = QPushButton("刷新") self.refresh_btn.setFixedSize(70, 28) self.refresh_btn.setStyleSheet("QPushButton { padding: 2px 8px; }") self.refresh_btn.clicked.connect(self._refresh_accounts) toolbar.addWidget(self.refresh_btn) account_layout.addLayout(toolbar) # Account list - compact self.account_list = QListWidget() self.account_list.setMinimumHeight(120) self.account_list.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.account_list.setSpacing(1) account_layout.addWidget(self.account_list, 1) layout.addWidget(account_group, 1) scroll.setWidget(content) main_layout.addWidget(scroll) def _refresh_accounts(self): """Refresh account list""" self.account_list.clear() config = get_config() for account in config.accounts: if account.enabled: item = QListWidgetItem() item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable) item.setCheckState(Qt.CheckState.Checked) display_name = account.remark if account.remark else account.username item.setText(f"{display_name} ({account.username})") item.setData(Qt.ItemDataRole.UserRole, account) item.setSizeHint(item.sizeHint().expandedTo(QSize(0, LIST_ITEM_HEIGHT))) self.account_list.addItem(item) def _select_all(self): """Select all""" for i in range(self.account_list.count()): self.account_list.item(i).setCheckState(Qt.CheckState.Checked) def _deselect_all(self): """Deselect all""" for i in range(self.account_list.count()): self.account_list.item(i).setCheckState(Qt.CheckState.Unchecked) def _get_selected_accounts(self) -> list: """Get selected accounts""" accounts = [] for i in range(self.account_list.count()): item = self.account_list.item(i) if item.checkState() == Qt.CheckState.Checked: account = item.data(Qt.ItemDataRole.UserRole) accounts.append(account) return accounts def _start_task(self): """Start task""" accounts = self._get_selected_accounts() if not accounts: QMessageBox.information(self, "提示", "请选择至少一个账号") return self._is_running = True self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) self._stop_requested = False browse_type = self.browse_type_combo.currentText() auto_screenshot = self.auto_screenshot_check.isChecked() auto_upload = self.auto_upload_check.isChecked() self.total_progress.setMaximum(len(accounts)) self.total_progress.setValue(0) self.log_signal.emit(f"开始任务: {len(accounts)} 个账号, 类型: {browse_type}") from utils.worker import Worker def run_tasks(_signals=None, _should_stop=None): results = [] for i, account in enumerate(accounts): if _should_stop and _should_stop(): break _signals.log.emit(f"[{i+1}/{len(accounts)}] 处理账号: {account.username}") _signals.progress.emit(i, f"正在处理: {account.username}") password = decrypt_password(account.password) if is_encrypted(account.password) else account.password from core.api_browser import APIBrowser from config import get_config as _get_config proxy_config = None cfg = _get_config() if cfg.proxy.enabled and cfg.proxy.server: proxy_config = {"server": cfg.proxy.server} browse_result = None with APIBrowser(log_callback=lambda msg: _signals.log.emit(msg), proxy_config=proxy_config) as browser: if browser.login(account.username, password): browser.save_cookies_for_screenshot(account.username) result = browser.browse_content( browse_type, should_stop_callback=_should_stop, ) browse_result = { "success": result.success, "total_items": result.total_items, "total_attachments": result.total_attachments, } screenshot_path = None if browse_result and browse_result.get("success") and auto_screenshot: from core.screenshot import take_screenshot _signals.log.emit("正在截图...") ss_result = take_screenshot( account.username, password, browse_type, remark=account.remark, log_callback=lambda msg: _signals.log.emit(msg), proxy_config=proxy_config ) if ss_result.success: screenshot_path = ss_result.filepath if screenshot_path and auto_upload: from core.kdocs_uploader import get_kdocs_uploader from config import get_config as _get_config2 cfg2 = _get_config2() if cfg2.kdocs.enabled: _signals.log.emit("正在上传到金山文档...") uploader = get_kdocs_uploader() uploader._log_callback = lambda msg: _signals.log.emit(msg) upload_result = uploader.upload_image( screenshot_path, cfg2.kdocs.unit, account.remark or account.username ) if upload_result.get("success"): _signals.log.emit("[OK] 上传成功") else: _signals.log.emit(f"上传失败: {upload_result.get('error', '未知错误')}") results.append({ "account": account.username, "browse": browse_result, "screenshot": screenshot_path, }) _signals.progress.emit(i + 1, f"完成: {account.username}") return results def on_progress(current, message): self.total_progress.setValue(current) self.current_task_label.setText(f"当前任务: {message}") def on_finished(success, message): self._is_running = False self.start_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.current_task_label.setText("当前任务: 完成") self.log_signal.emit(f"任务完成: {message}") def on_result(results): if results: total_items = sum(r.get("browse", {}).get("total_items", 0) for r in results if r.get("browse")) total_attachments = sum(r.get("browse", {}).get("total_attachments", 0) for r in results if r.get("browse")) self.stats_label.setText(f"已处理: {total_items} 条内容,{total_attachments} 个附件") self._worker = Worker(run_tasks) self._worker.signals.log.connect(self.log_signal.emit) self._worker.signals.progress.connect(on_progress) self._worker.signals.finished.connect(on_finished) self._worker.signals.result.connect(on_result) self._worker.start() def _stop_task(self): """Stop task""" if self._worker: self._worker.stop() self.log_signal.emit("正在停止任务...") self.stop_btn.setEnabled(False)