#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 依赖安装对话框 检测到缺失依赖时显示,让用户选择是否下载安装 """ from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QProgressBar, QCheckBox, QGroupBox, QMessageBox ) from PyQt6.QtCore import Qt, QThread, pyqtSignal class DownloadThread(QThread): """下载线程""" progress = pyqtSignal(int, int) # downloaded, total log = pyqtSignal(str) finished = pyqtSignal(bool, str) # success, message def __init__(self, dep_name: str): super().__init__() self.dep_name = dep_name def run(self): from utils.dependency_installer import download_and_install_dependency def on_progress(downloaded, total): self.progress.emit(downloaded, total) def on_log(msg): self.log.emit(msg) success, message = download_and_install_dependency( self.dep_name, progress_callback=on_progress, log_callback=on_log ) self.finished.emit(success, message) class DependencyDialog(QDialog): """依赖安装对话框""" def __init__(self, missing_deps: dict, parent=None): super().__init__(parent) self.missing_deps = missing_deps self.download_threads = [] self.setWindowTitle("环境检测") self.setMinimumWidth(450) self.setModal(True) self._setup_ui() def _setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(16) layout.setContentsMargins(20, 20, 20, 20) # 标题 title = QLabel("⚠️ 检测到缺少必要的运行环境") title.setStyleSheet("font-size: 16px; font-weight: bold; color: #fa8c16;") layout.addWidget(title) # 说明 desc = QLabel("以下组件缺失,部分功能可能无法正常使用:") desc.setStyleSheet("color: #666;") layout.addWidget(desc) # 缺失组件列表 deps_group = QGroupBox("缺失组件") deps_layout = QVBoxLayout(deps_group) self.checkboxes = {} if self.missing_deps.get("wkhtmltoimage"): cb = QCheckBox("wkhtmltoimage(截图功能需要)") cb.setChecked(True) self.checkboxes["wkhtmltoimage"] = cb deps_layout.addWidget(cb) if self.missing_deps.get("chromium"): cb = QCheckBox("Playwright Chromium(金山文档上传需要)") cb.setChecked(True) self.checkboxes["chromium"] = cb deps_layout.addWidget(cb) layout.addWidget(deps_group) # 进度区域 self.progress_group = QGroupBox("安装进度") progress_layout = QVBoxLayout(self.progress_group) self.status_label = QLabel("等待开始...") self.status_label.setStyleSheet("color: #666;") progress_layout.addWidget(self.status_label) self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.progress_bar.setValue(0) progress_layout.addWidget(self.progress_bar) self.progress_group.setVisible(False) layout.addWidget(self.progress_group) # 按钮 btn_layout = QHBoxLayout() btn_layout.addStretch() self.skip_btn = QPushButton("跳过") self.skip_btn.setMinimumWidth(80) self.skip_btn.clicked.connect(self.reject) btn_layout.addWidget(self.skip_btn) self.install_btn = QPushButton("立即安装") self.install_btn.setObjectName("primary") self.install_btn.setMinimumWidth(100) self.install_btn.clicked.connect(self._start_install) btn_layout.addWidget(self.install_btn) layout.addLayout(btn_layout) def _start_install(self): """开始安装""" # 获取选中的组件 selected = [name for name, cb in self.checkboxes.items() if cb.isChecked()] if not selected: QMessageBox.information(self, "提示", "请选择要安装的组件") return # 禁用按钮和复选框 self.install_btn.setEnabled(False) self.skip_btn.setText("取消") for cb in self.checkboxes.values(): cb.setEnabled(False) # 显示进度 self.progress_group.setVisible(True) # 开始安装 self._install_queue = selected.copy() self._install_next() def _install_next(self): """安装下一个组件""" if not self._install_queue: # 全部完成 self.status_label.setText("✅ 安装完成!") self.status_label.setStyleSheet("color: #52c41a; font-weight: bold;") self.skip_btn.setText("完成") self.skip_btn.clicked.disconnect() self.skip_btn.clicked.connect(self.accept) return dep_name = self._install_queue.pop(0) display_name = "wkhtmltoimage" if dep_name == "wkhtmltoimage" else "Chromium 浏览器" self.status_label.setText(f"正在下载 {display_name}...") self.progress_bar.setValue(0) # 启动下载线程 thread = DownloadThread(dep_name) thread.progress.connect(self._on_progress) thread.log.connect(self._on_log) thread.finished.connect(self._on_finished) thread.start() self.download_threads.append(thread) def _on_progress(self, downloaded: int, total: int): """下载进度更新""" if total > 0: percent = int(downloaded * 100 / total) self.progress_bar.setValue(percent) mb_downloaded = downloaded / 1024 / 1024 mb_total = total / 1024 / 1024 self.status_label.setText(f"下载中... {mb_downloaded:.1f}MB / {mb_total:.1f}MB") def _on_log(self, msg: str): """日志更新""" self.status_label.setText(msg) def _on_finished(self, success: bool, message: str): """安装完成""" if success: if message: # wkhtmltoimage 需要手动完成安装向导 QMessageBox.information(self, "提示", message) # 继续安装下一个 self._install_next() else: self.status_label.setText(f"❌ 安装失败: {message}") self.status_label.setStyleSheet("color: #ff4d4f;") self.install_btn.setEnabled(True) self.skip_btn.setText("跳过") def closeEvent(self, event): """关闭时停止所有线程""" for thread in self.download_threads: if thread.isRunning(): thread.terminate() thread.wait() event.accept()