""" PDF转Word工具 - 单文件上传 - 进度条显示转换进度 - 保持原始排版 """ import os import logging from pathlib import Path from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFrame, QFileDialog, QMessageBox, QProgressBar ) from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtGui import QFont, QPixmap from ui.workspace import BaseWorkspace, UploadArea try: from pdf2docx import Converter HAS_PDF2DOCX = True except ImportError: HAS_PDF2DOCX = False logging.warning("pdf2docx未安装, PDF转Word功能不可用") class ConvertWorker(QThread): """转换工作线程""" progress = Signal(int) # 百分比 finished = Signal(str) # 输出路径 error = Signal(str) def __init__(self, pdf_path: str, output_path: str): super().__init__() self.pdf_path = pdf_path self.output_path = output_path def run(self): try: cv = Converter(self.pdf_path) # pdf2docx 没有直接的进度回调,我们模拟进度 self.progress.emit(10) cv.convert(self.output_path) self.progress.emit(90) cv.close() self.progress.emit(100) self.finished.emit(self.output_path) except Exception as e: logging.error(f"PDF转Word失败: {e}") self.error.emit(str(e)) class PDFToWordPage(BaseWorkspace): """PDF转Word页面""" def __init__(self, parent=None): super().__init__(parent) self.pdf_path = None self.setup_convert_ui() def setup_convert_ui(self): """设置转换UI""" self.history_btn.hide() # 上传区域 self.upload_area = UploadArea("PDF文件 (*.pdf)") self.upload_area.files_dropped.connect(self.on_file_added) self.content_layout.addWidget(self.upload_area) # 转换区域 convert_frame = QFrame() convert_frame.setObjectName("card") convert_layout = QVBoxLayout(convert_frame) convert_layout.setContentsMargins(32, 32, 32, 32) convert_layout.setSpacing(24) convert_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) # 图标 icon_layout = QHBoxLayout() icon_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) icon_layout.setSpacing(24) pdf_icon = QLabel("📄") pdf_icon.setFont(QFont("Segoe UI Emoji", 48)) pdf_icon.setStyleSheet("background: rgba(239, 68, 68, 0.1); border-radius: 16px; padding: 16px;") icon_layout.addWidget(pdf_icon) arrow = QLabel("➡️") arrow.setFont(QFont("Segoe UI Emoji", 32)) icon_layout.addWidget(arrow) word_icon = QLabel("📝") word_icon.setFont(QFont("Segoe UI Emoji", 48)) word_icon.setStyleSheet("background: rgba(59, 130, 246, 0.1); border-radius: 16px; padding: 16px;") icon_layout.addWidget(word_icon) convert_layout.addLayout(icon_layout) # 文件信息 self.file_info = QLabel("选择PDF文件开始转换") self.file_info.setStyleSheet("color: #94a3b8; font-size: 14px;") self.file_info.setAlignment(Qt.AlignmentFlag.AlignCenter) convert_layout.addWidget(self.file_info) # 特性说明 features_layout = QHBoxLayout() features_layout.setSpacing(32) features_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) features = [ ("✅", "保持排版"), ("✅", "保留图片"), ("✅", "提取表格") ] for icon, text in features: feature = QLabel(f"{icon} {text}") feature.setStyleSheet("color: #22c55e; font-size: 13px;") features_layout.addWidget(feature) convert_layout.addLayout(features_layout) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) self.progress_bar.setMinimumWidth(400) convert_layout.addWidget(self.progress_bar, 0, Qt.AlignmentFlag.AlignCenter) # 状态标签 self.status_label = QLabel("") self.status_label.setStyleSheet("color: #fbbf24; font-size: 13px;") self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.status_label.setVisible(False) convert_layout.addWidget(self.status_label) # 转换按钮 self.convert_btn = QPushButton("📝 开始转换") self.convert_btn.setObjectName("primary_btn") self.convert_btn.setMinimumSize(200, 50) self.convert_btn.setFont(QFont("Microsoft YaHei", 13, QFont.Weight.Bold)) self.convert_btn.clicked.connect(self.do_convert) self.convert_btn.setEnabled(False) convert_layout.addWidget(self.convert_btn, 0, Qt.AlignmentFlag.AlignCenter) # 提示 hint = QLabel("提示: 转换复杂PDF可能需要较长时间,请耐心等待") hint.setStyleSheet("color: #64748b; font-size: 11px;") hint.setAlignment(Qt.AlignmentFlag.AlignCenter) convert_layout.addWidget(hint) self.content_layout.addWidget(convert_frame) self.content_layout.addStretch() def on_file_added(self, files: list): """文件添加""" if not files: return pdf_file = None for f in files: if f.lower().endswith('.pdf'): pdf_file = f break if not pdf_file: QMessageBox.warning(self, "提示", "请选择PDF文件") return self.pdf_path = pdf_file self.file_info.setText(f"📁 {Path(pdf_file).name}") self.file_info.setStyleSheet("color: white; font-size: 14px; font-weight: 500;") self.convert_btn.setEnabled(True) logging.info(f"已选择PDF文件: {pdf_file}") def do_convert(self): """执行转换""" if not HAS_PDF2DOCX: QMessageBox.critical(self, "错误", "pdf2docx未安装,无法转换PDF") return if not self.pdf_path: QMessageBox.warning(self, "提示", "请先选择PDF文件") return # 选择保存路径 default_name = Path(self.pdf_path).stem + ".docx" save_path, _ = QFileDialog.getSaveFileName( self, "保存Word文档", default_name, "Word文档 (*.docx)" ) if not save_path: return # 开始转换 self.convert_btn.setEnabled(False) self.progress_bar.setVisible(True) self.progress_bar.setValue(0) self.status_label.setVisible(True) self.status_label.setText("正在转换中,请稍候...") self.worker = ConvertWorker(self.pdf_path, save_path) self.worker.progress.connect(self.on_progress) self.worker.finished.connect(self.on_convert_finished) self.worker.error.connect(self.on_convert_error) self.worker.start() logging.info(f"开始转换PDF: {self.pdf_path}") def on_progress(self, value: int): """进度更新""" self.progress_bar.setValue(value) if value < 30: self.status_label.setText("正在解析PDF结构...") elif value < 70: self.status_label.setText("正在转换内容...") else: self.status_label.setText("正在生成Word文档...") def on_convert_finished(self, output_path: str): """转换完成""" self.convert_btn.setEnabled(True) self.progress_bar.setVisible(False) self.status_label.setVisible(False) QMessageBox.information( self, "成功", f"PDF转Word完成!\n\n保存到: {output_path}" ) logging.info(f"PDF转Word完成: {output_path}") def on_convert_error(self, error: str): """转换错误""" self.convert_btn.setEnabled(True) self.progress_bar.setVisible(False) self.status_label.setVisible(False) QMessageBox.critical(self, "错误", f"转换失败:\n{error}") logging.error(f"PDF转Word失败: {error}")