
欢迎大家B站搜索:丁老师的技术随笔

打工人救星🌟PySide6开发PDF转Word小工具,免费好用! 办公自动化|Python自制PDF转Word工具,一键搞定✅ PySide6+pdf2docx|自制PDF转换器,再也不开会员了💰
文末有打包完成的PDF转化word工具.exe文件,无需安装,双击即可使用
word 批量转化为pdf ?批量转PDF别再花钱了!教你用Python做个桌面小工具

转换后:

import sysimport osfrom pathlib import Pathfrom PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QFileDialog, QProgressBar, QTextEdit, QFrame, QMessageBox, QComboBox, QSpinBox, QCheckBox)from PySide6.QtCore import Qt, QThread, Signal, QTimerfrom PySide6.QtGui import QFont, QIcon, QDragEnterEvent, QDropEvent, QColorfrom PySide6.QtCore import QPropertyAnimation, QEasingCurve, QParallelAnimationGroup# 需要安装: pip install PySide6 pdf2docxtry: from pdf2docx import Converterexcept ImportError: print("请先安装 pdf2docx: pip install pdf2docx") sys.exit(1)class ConversionWorker(QThread): """后台转换线程""" progress = Signal(int) status = Signal(str) finished_success = Signal(str) finished_error = Signal(str) def __init__(self, pdf_path, docx_path, start_page=None, end_page=None): super().__init__() self.pdf_path = pdf_path self.docx_path = docx_path self.start_page = start_page self.end_page = end_page self.is_running = True def run(self): try: self.status.emit("正在初始化转换器...") cv = Converter(self.pdf_path) # 获取总页数用于进度计算 total_pages = len(cv.pages) self.status.emit(f"PDF 共 {total_pages} 页,开始转换...") # 设置页面范围 start = self.start_page if self.start_page else 0 end = self.end_page if self.end_page else total_pages # 模拟进度更新(因为 pdf2docx 没有内置进度回调) def progress_callback(page_no): if not self.is_running: raise Exception("用户取消") progress = int((page_no / total_pages) * 100) self.progress.emit(progress) self.status.emit(f"正在转换第 {page_no}/{total_pages} 页...") # 执行转换 cv.convert( self.docx_path, start=start, end=end, progress_callback=progress_callback ) cv.close() self.progress.emit(100) self.finished_success.emit(self.docx_path) except Exception as e: if self.is_running: self.finished_error.emit(str(e)) def stop(self): self.is_running = False self.wait(1000)class ModernButton(QPushButton): """现代化按钮样式""" def __init__(self, text, parent=None, primary=False): super().__init__(text, parent) self.primary = primary self.setMinimumHeight(40) self.setCursor(Qt.PointingHandCursor) self.update_style() def update_style(self): if self.primary: self.setStyleSheet(""" QPushButton { background-color: #3b82f6; color: white; border: none; border-radius: 8px; padding: 10px 24px; font-weight: 600; font-size: 14px; } QPushButton:hover { background-color: #2563eb; transform: scale(1.02); } QPushButton:pressed { background-color: #1d4ed8; } QPushButton:disabled { background-color: #9ca3af; color: #e5e7eb; } """) else: self.setStyleSheet(""" QPushButton { background-color: #f3f4f6; color: #374151; border: 1px solid #d1d5db; border-radius: 8px; padding: 10px 24px; font-weight: 500; font-size: 14px; } QPushButton:hover { background-color: #e5e7eb; border-color: #9ca3af; } QPushButton:pressed { background-color: #d1d5db; } """)class DropArea(QFrame): """拖拽区域组件""" file_dropped = Signal(str) def __init__(self): super().__init__() self.setAcceptDrops(True) self.setMinimumHeight(120) self.setFrameStyle(QFrame.StyledPanel) self.setStyleSheet(""" QFrame { background-color: #f9fafb; border: 2px dashed #d1d5db; border-radius: 12px; } QFrame:hover { border-color: #3b82f6; background-color: #eff6ff; } """) layout = QVBoxLayout(self) layout.setAlignment(Qt.AlignCenter) self.icon_label = QLabel("📄") self.icon_label.setStyleSheet("font-size: 48px; border: none; background: transparent;") self.icon_label.setAlignment(Qt.AlignCenter) self.text_label = QLabel("拖拽 PDF 文件到此处") self.text_label.setStyleSheet(""" font-size: 16px; color: #6b7280; border: none; background: transparent; font-weight: 500; """) self.text_label.setAlignment(Qt.AlignCenter) self.sub_label = QLabel("或点击选择文件") self.sub_label.setStyleSheet(""" font-size: 13px; color: #9ca3af; border: none; background: transparent; """) self.sub_label.setAlignment(Qt.AlignCenter) layout.addWidget(self.icon_label) layout.addWidget(self.text_label) layout.addWidget(self.sub_label) self.setLayout(layout) def dragEnterEvent(self, event: QDragEnterEvent): if event.mimeData().hasUrls(): urls = event.mimeData().urls() if urls and urls[0].toLocalFile().lower().endswith('.pdf'): event.acceptProposedAction() self.setStyleSheet(""" QFrame { background-color: #eff6ff; border: 2px dashed #3b82f6; border-radius: 12px; } """) def dragLeaveEvent(self, event): self.setStyleSheet(""" QFrame { background-color: #f9fafb; border: 2px dashed #d1d5db; border-radius: 12px; } """) def dropEvent(self, event: QDropEvent): urls = event.mimeData().urls() if urls: file_path = urls[0].toLocalFile() if file_path.lower().endswith('.pdf'): self.file_dropped.emit(file_path) self.setStyleSheet(""" QFrame { background-color: #f9fafb; border: 2px dashed #d1d5db; border-radius: 12px; } """) def mousePressEvent(self, event): file_path, _ = QFileDialog.getOpenFileName( self, "选择 PDF 文件", "", "PDF 文件 (*.pdf)" ) if file_path: self.file_dropped.emit(file_path)class PDFToWordConverter(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("PDF 转 Word 转换器") self.setMinimumSize(700, 800) self.current_pdf = None self.worker = None # 设置应用样式 self.setStyleSheet(""" QMainWindow { background-color: #ffffff; } QLabel { color: #1f2937; } QLineEdit { padding: 10px; border: 1px solid #d1d5db; border-radius: 8px; background-color: #ffffff; font-size: 14px; } QLineEdit:focus { border-color: #3b82f6; outline: none; } QProgressBar { border: none; border-radius: 6px; background-color: #e5e7eb; height: 8px; text-align: center; } QProgressBar::chunk { background-color: #3b82f6; border-radius: 6px; } QTextEdit { border: 1px solid #e5e7eb; border-radius: 8px; padding: 10px; background-color: #f9fafb; font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; } QSpinBox { padding: 8px; border: 1px solid #d1d5db; border-radius: 6px; } QCheckBox { spacing: 8px; font-size: 14px; } QCheckBox::indicator { width: 18px; height: 18px; border-radius: 4px; border: 2px solid #d1d5db; } QCheckBox::indicator:checked { background-color: #3b82f6; border-color: #3b82f6; } """) self.init_ui() def init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(20) main_layout.setContentsMargins(40, 30, 40, 30) # 标题 title = QLabel("PDF 转 Word 转换器") title.setStyleSheet(""" font-size: 28px; font-weight: bold; color: #111827; margin-bottom: 10px; """) title.setAlignment(Qt.AlignCenter) main_layout.addWidget(title) subtitle = QLabel("快速、高质量地将 PDF 文档转换为可编辑的 Word 格式") subtitle.setStyleSheet("font-size: 14px; color: #6b7280; margin-bottom: 20px;") subtitle.setAlignment(Qt.AlignCenter) main_layout.addWidget(subtitle) # 拖拽区域 self.drop_area = DropArea() self.drop_area.file_dropped.connect(self.on_file_selected) main_layout.addWidget(self.drop_area) # 添加伸缩间距组件 main_layout.addStretch() # 文件信息区域 info_frame = QFrame() info_frame.setStyleSheet(""" QFrame { background-color: #f3f4f6; border-radius: 12px; padding: 10px; } """) info_layout = QVBoxLayout(info_frame) info_layout.setSpacing(20) # 文件路径显示 path_layout = QHBoxLayout() path_label = QLabel("文件路径:") path_label.setStyleSheet("font-weight: 600; color: #374151;") path_label.setMinimumHeight(40) # 设置最小高度为30像素 self.path_display = QLineEdit() self.path_display.setReadOnly(True) self.path_display.setMinimumHeight(30) # 设置最小高度为40像素 self.path_display.setStyleSheet(""" background-color: #ffffff; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; padding: 1px; """) self.path_display.setPlaceholderText("请选择或拖拽 PDF 文件...") path_layout.addWidget(path_label) path_layout.addWidget(self.path_display, 1) info_layout.addLayout(path_layout) # 输出路径 output_layout = QHBoxLayout() output_label = QLabel("输出路径:") output_label.setStyleSheet("font-weight: 600; color: #374151;") output_label.setMinimumHeight(40) # 设置最小高度为30像素 self.output_display = QLineEdit() self.output_display.setReadOnly(True) self.output_display.setMinimumHeight(30) # 设置最小高度为40像素 self.output_display.setStyleSheet(""" background-color: #ffffff; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; padding: 1px; """) self.output_display.setPlaceholderText("自动保存到桌面...") output_btn = ModernButton("选择位置") output_btn.clicked.connect(self.select_output_location) output_layout.addWidget(output_label) output_layout.addWidget(self.output_display, 1) output_layout.addWidget(output_btn) info_layout.addLayout(output_layout) # 高级选项 options_layout = QHBoxLayout() # 页面范围选择 self.range_checkbox = QCheckBox("指定页面范围") self.range_checkbox.stateChanged.connect(self.toggle_range_options) self.range_checkbox.setMinimumHeight(20) options_layout.addWidget(self.range_checkbox) self.start_page = QSpinBox() self.start_page.setRange(1, 9999) self.start_page.setEnabled(False) self.start_page.setPrefix("从 ") self.start_page.setSuffix(" 页") self.start_page.setMinimumHeight(20) self.start_page.setStyleSheet(""" background-color: #ffffff; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; padding: 1px; """) options_layout.addWidget(self.start_page) self.end_page = QSpinBox() self.end_page.setRange(1, 9999) self.end_page.setEnabled(False) self.end_page.setPrefix("到 ") self.end_page.setSuffix(" 页") self.end_page.setMinimumHeight(20) self.end_page.setStyleSheet(""" background-color: #ffffff; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; padding: 1px; """) options_layout.addWidget(self.end_page) options_layout.addStretch() info_layout.addLayout(options_layout) main_layout.addWidget(info_frame) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setValue(0) self.progress_bar.setTextVisible(True) self.progress_bar.setFormat("%p%") main_layout.addWidget(self.progress_bar) # 状态标签 self.status_label = QLabel("就绪") self.status_label.setStyleSheet("color: #6b7280; font-size: 13px;") self.status_label.setAlignment(Qt.AlignCenter) main_layout.addWidget(self.status_label) # 按钮区域 btn_layout = QHBoxLayout() btn_layout.setSpacing(12) self.convert_btn = ModernButton("开始转换", primary=True) self.convert_btn.setEnabled(False) self.convert_btn.clicked.connect(self.start_conversion) self.convert_btn.setMinimumWidth(140) self.clear_btn = ModernButton("清空") self.clear_btn.clicked.connect(self.clear_all) self.open_folder_btn = ModernButton("打开输出文件夹") self.open_folder_btn.clicked.connect(self.open_output_folder) self.open_folder_btn.setEnabled(False) btn_layout.addStretch() btn_layout.addWidget(self.clear_btn) btn_layout.addWidget(self.open_folder_btn) btn_layout.addWidget(self.convert_btn) btn_layout.addStretch() main_layout.addLayout(btn_layout) # 日志区域 log_label = QLabel("转换日志") log_label.setStyleSheet("font-weight: 600; color: #374151; margin-transform: translateY( 10px;") main_layout.addWidget(log_label) self.log_text = QTextEdit() self.log_text.setMaximumHeight(120) self.log_text.setReadOnly(True) main_layout.addWidget(self.log_text) # 底部信息 footer = QLabel("支持 .pdf 格式 • 保留原始排版 • 本地转换保护隐私") footer.setStyleSheet("color: #9ca3af; font-size: 12px; margin-top: 10px;") footer.setAlignment(Qt.AlignCenter) main_layout.addWidget(footer) def toggle_range_options(self, state): enabled = state == Qt.Checked self.start_page.setEnabled(enabled) self.end_page.setEnabled(enabled) def on_file_selected(self, file_path): self.current_pdf = file_path self.path_display.setText(file_path) # 自动设置输出路径为桌面 desktop = Path.home() / "Desktop" filename = Path(file_path).stem) output_path = desktop / f"{filename}.docx" self.output_display.setText(str(output_path)) self.convert_btn.setEnabled(True) self.log_message(f"已选择文件: {file_path}") self.log_message(f"预计输出到: {output_path}") # 动画效果 self.animate_success() def select_output_location(self): if not self.current_pdf: QMessageBox.warning(self, "提示", "请先选择 PDF 文件") return file_path, _ = QFileDialog.getSaveFileName( self, "保存 Word 文档", self.output_display.text(), "Word 文档 (*.docx)" ) if file_path: if not file_path.endswith('.docx'): file_path += '.docx' self.output_display.setText(file_path) def start_conversion(self): if not self.current_pdf: return output_path = self.output_display.text() if not output_path: QMessageBox.warning(self, "错误", "请选择输出位置") return # 禁用界面 self.convert_btn.setEnabled(False) self.convert_btn.setText("转换中...") self.drop_area.setEnabled(False) # 获取页面范围 start = None end = None if self.range_checkbox.isChecked(): start = self.start_page.value() - 1 # 转换为0-based end = self.end_page.value() # 启动工作线程 self.worker = ConversionWorker( self.current_pdf, output_path, start_page=start, end_page=end ) self.worker.progress.connect(self.update_progress) self.worker.status.connect(self.update_status) self.worker.finished_success.connect(self.conversion_success) self.worker.finished_error.connect(self.conversion_error) self.worker.start() def update_progress(self, value): self.progress_bar.setValue(value) def update_status(self, message): self.status_label.setText(message) self.log_message(message) def conversion_success(self, output_path): self.progress_bar.setValue(100) self.status_label.setText("转换完成!") self.status_label.setStyleSheet("color: #10b981; font-weight: 600;") self.log_message(f"✅ 转换成功: {output_path}") self.convert_btn.setText("转换完成") self.open_folder_btn.setEnabled(True) QMessageBox.information( self, "转换完成", f"PDF 已成功转换为 Word 文档!\n\n保存位置:{output_path}" ) # 重置状态 QTimer.singleShot(2000, self.reset_ui_state) def conversion_error(self, error_msg): self.status_label.setText("转换失败") self.status_label.setStyleSheet("color: #ef4444; font-weight: 600;") self.log_message(f"❌ 错误: {error_msg}") self.convert_btn.setEnabled(True) self.convert_btn.setText("重试转换") self.drop_area.setEnabled(True) QMessageBox.critical(self, "转换失败", f"转换过程中出现错误:\n{error_msg}") def reset_ui_state(self): self.convert_btn.setEnabled(True) self.convert_btn.setText("开始转换") self.drop_area.setEnabled(True) self.status_label.setStyleSheet("color: #6b7280; font-size: 13px;") def clear_all(self): self.current_pdf = None self.path_display.clear() self.output_display.clear() self.progress_bar.setValue(0) self.status_label.setText("就绪") self.status_label.setStyleSheet("color: #6b7280; font-size: 13px;") self.log_text.clear() self.convert_btn.setEnabled(False) self.open_folder_btn.setEnabled(False) self.range_checkbox.setChecked(False) def open_output_folder(self): output_path = self.output_display.text() if output_path and os.path.exists(output_path): folder = os.path.dirname(output_path) os.startfile(folder) if sys.platform == 'win32' else os.system(f'open "{folder}"') def log_message(self, message): from datetime import datetime timestamp = datetime.now().strftime("%H:%M:%S") self.log_text.append(f"[{timestamp}] {message}") def animate_success(self): # 简单的缩放动画效果 self.drop_area.icon_label.setText("✅") QTimer.singleShot(1000, lambda: self.drop_area.icon_label.setText("📄")) def closeEvent(self, event): if self.worker and self.worker.isRunning(): reply = QMessageBox.question( self, "确认退出", "转换正在进行中,确定要退出吗?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.worker.stop() event.accept() else: event.ignore() else: event.accept()def main(): # 设置高 DPI 支持 QApplication.setHighDpiScaleFactorRoundingPolicy( Qt.HighDpiScaleFactorRoundingPolicy.PassThrough ) app = QApplication(sys.argv) app.setStyle('Fusion') # 使用 Fusion 风格获得更现代的外观 # 设置全局字体 font = QFont("Microsoft YaHei", 10) app.setFont(font) window = PDFToWordConverter() window.show() sys.exit(app.exec())if __name__ == "__main__": main()使用pyinstaller进行 .exe 打包,让系统不安装python 也可以正常使用
pyinstaller --onefile --windowed --name "PDF转Word工具" --clean pdf_converter.py --icon=icon.ico打包效果:
链接: 通过网盘分享的文件:PDF转Word工具.exe 链接: https://pan.baidu.com/s/1Mb2TLSmKH7emhR63UDlF7w 提取码: unzc
通过网盘分享的文件:PDF转Word工具.exe 提取码: unzc
END


● 批量转PDF别再花钱了!教你用Python做个桌面小工具
关注二维码
获取更多精彩内容
