Files
nl-utils/v1/views/word_to_pdf.py
2025-06-07 15:54:17 +08:00

338 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import comtypes.client
import random
import datetime
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QListWidget, QListWidgetItem,
QFileDialog, QProgressBar, QMessageBox, QSpacerItem)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize
from PyQt5.QtGui import QPixmap, QIcon, QDragEnterEvent, QDropEvent
def generate_unique_filename(original_path):
"""生成唯一的文件名格式原名称_日期_随机数.pdf"""
base_name = os.path.basename(original_path)
name_without_ext = os.path.splitext(base_name)[0]
# 获取当前日期
current_date = datetime.datetime.now().strftime("%Y%m%d")
# 生成4位随机数
random_num = random.randint(1000, 9999)
# 组合成新文件名
new_filename = f"{name_without_ext}_{current_date}_{random_num}.pdf"
return os.path.join(os.path.dirname(original_path), new_filename)
class ConversionThread(QThread):
progress_updated = pyqtSignal(int, int, str) # current, total, filename
conversion_done = pyqtSignal(str, bool, str) # filename, success, message
finished_all = pyqtSignal()
def __init__(self, files):
super().__init__()
self.files = files
def run(self):
word = None
try:
word = comtypes.client.CreateObject('Word.Application')
word.Visible = False
total = len(self.files)
for i, file_path in enumerate(self.files):
self.progress_updated.emit(i + 1, total, os.path.basename(file_path))
try:
# 使用新的文件名生成逻辑
pdf_path = generate_unique_filename(file_path)
doc = word.Documents.Open(os.path.abspath(file_path))
doc.SaveAs(pdf_path, FileFormat=17) # 17 = PDF
doc.Close()
self.conversion_done.emit(file_path, True, pdf_path) # 传递新文件路径
except Exception as e:
self.conversion_done.emit(file_path, False, str(e))
self.finished_all.emit()
finally:
if word:
word.Quit()
class WordToPDFPage(QWidget):
def __init__(self):
super().__init__()
self.files = []
self.converted_files = {}
self.conversion_thread = None
self.init_ui()
def init_ui(self):
self.setObjectName("wordToPdfPage")
self.setAcceptDrops(True)
layout = QHBoxLayout()
layout.setContentsMargins(20, 20, 20, 20)
layout.setSpacing(20)
# 左侧面板 - 文件上传
left_panel = QFrame()
left_panel.setObjectName("leftPanel")
left_panel.setMinimumWidth(300)
left_layout = QVBoxLayout(left_panel)
left_layout.setContentsMargins(0, 0, 0, 0)
left_layout.setSpacing(15)
# 上传区域
upload_area = QFrame()
upload_area.setObjectName("uploadArea")
upload_area.setFixedHeight(200)
upload_layout = QVBoxLayout(upload_area)
upload_layout.setAlignment(Qt.AlignCenter)
upload_icon = QLabel()
# 确保你有正确的图标路径
# upload_icon.setPixmap(QPixmap(":/icons/upload.png").scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation))
upload_icon.setAlignment(Qt.AlignCenter)
upload_text = QLabel("拖拽文件或文件夹到此处\n或点击上传")
upload_text.setAlignment(Qt.AlignCenter)
upload_text.setWordWrap(True)
upload_btn = QPushButton("选择文件或文件夹")
upload_btn.setObjectName("uploadBtn")
upload_btn.clicked.connect(self.handle_upload)
upload_layout.addWidget(upload_icon)
upload_layout.addWidget(upload_text)
upload_layout.addWidget(upload_btn)
# 文件列表
self.file_list = QListWidget()
self.file_list.setObjectName("fileList")
self.file_list.setSelectionMode(QListWidget.ExtendedSelection)
# 操作按钮
btn_layout = QHBoxLayout()
convert_btn = QPushButton("开始转换")
convert_btn.setObjectName("convertBtn")
convert_btn.clicked.connect(self.start_conversion)
delete_btn = QPushButton("删除选中")
delete_btn.setObjectName("deleteBtn")
delete_btn.clicked.connect(self.delete_selected)
btn_layout.addWidget(convert_btn)
btn_layout.addWidget(delete_btn)
# 组装左侧面板
left_layout.addWidget(upload_area)
left_layout.addWidget(self.file_list)
left_layout.addLayout(btn_layout)
# 右侧面板 - 转换结果
right_panel = QFrame()
right_panel.setObjectName("rightPanel")
right_layout = QVBoxLayout(right_panel)
right_layout.setContentsMargins(0, 0, 0, 0)
right_layout.setSpacing(15)
# 进度条
self.total_progress = QProgressBar()
self.total_progress.setTextVisible(False)
self.file_progress = QProgressBar()
self.file_progress.setTextVisible(False)
# 结果列表
self.result_list = QListWidget()
self.result_list.setObjectName("resultList")
# 下载按钮
self.download_btn = QPushButton("全部下载")
self.download_btn.setObjectName("downloadBtn")
self.download_btn.setEnabled(False)
self.download_btn.clicked.connect(self.download_all)
# 日志
log_area = QFrame()
log_area.setObjectName("logArea")
log_layout = QVBoxLayout(log_area)
log_title = QLabel("转换日志")
log_title.setObjectName("logTitle")
self.log_content = QListWidget()
self.log_content.setObjectName("logContent")
log_layout.addWidget(log_title)
log_layout.addWidget(self.log_content)
# 组装右侧面板
right_layout.addWidget(self.total_progress)
right_layout.addWidget(self.file_progress)
right_layout.addWidget(self.result_list)
right_layout.addWidget(self.download_btn)
right_layout.addWidget(log_area)
# 主布局
layout.addWidget(left_panel)
layout.addWidget(right_panel)
self.setLayout(layout)
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event: QDropEvent):
urls = event.mimeData().urls()
files = []
for url in urls:
path = url.toLocalFile()
if os.path.isdir(path):
# 处理文件夹
for root, _, filenames in os.walk(path):
for f in filenames:
if f.lower().endswith(('.doc', '.docx')):
files.append(os.path.join(root, f))
elif os.path.isfile(path) and path.lower().endswith(('.doc', '.docx')):
files.append(path)
if files:
self.add_files(files)
def handle_upload(self):
"""处理文件或文件夹上传"""
# 使用getExistingDirectory获取文件夹
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
if folder:
files = []
for root, _, filenames in os.walk(folder):
for f in filenames:
if f.lower().endswith(('.doc', '.docx')):
files.append(os.path.join(root, f))
self.add_files(files)
else:
# 如果用户没有选择文件夹,则选择文件
files, _ = QFileDialog.getOpenFileNames(
self, "选择Word文件", "", "Word Files (*.doc *.docx);;All Files (*)"
)
if files:
self.add_files(files)
def add_files(self, files):
"""添加文件到列表,避免重复"""
for file in files:
if file not in self.files and os.path.exists(file):
self.files.append(file)
item = QListWidgetItem(os.path.basename(file))
item.setData(Qt.UserRole, file)
self.file_list.addItem(item)
def delete_selected(self):
"""删除选中的文件"""
for item in self.file_list.selectedItems():
self.files.remove(item.data(Qt.UserRole))
self.file_list.takeItem(self.file_list.row(item))
def start_conversion(self):
"""开始转换文件"""
if not self.files:
QMessageBox.warning(self, "警告", "请先添加要转换的文件!")
return
# 重置状态
self.converted_files = {}
self.result_list.clear()
self.log_content.clear()
self.download_btn.setEnabled(False)
self.conversion_thread = ConversionThread(self.files.copy())
self.conversion_thread.progress_updated.connect(self.update_progress)
self.conversion_thread.conversion_done.connect(self.handle_conversion_done)
self.conversion_thread.finished_all.connect(self.handle_finished_all)
self.conversion_thread.start()
def update_progress(self, current, total, filename):
"""更新进度条"""
self.total_progress.setMaximum(total)
self.total_progress.setValue(current)
self.file_progress.setMaximum(100)
self.file_progress.setValue(0)
self.log_content.addItem(f"正在转换: {filename} ({current}/{total})")
def handle_conversion_done(self, file_path, success, message):
"""处理单个文件转换完成"""
basename = os.path.basename(file_path)
if success:
# 成功时message包含新的PDF路径
pdf_path = message
self.converted_files[file_path] = pdf_path
# 从待转换列表移除
for i in range(self.file_list.count()):
item = self.file_list.item(i)
if item.data(Qt.UserRole) == file_path:
self.file_list.takeItem(i)
break
# 添加到结果列表
item = QListWidgetItem(f"{basename}{os.path.basename(pdf_path)}")
item.setData(Qt.UserRole, pdf_path)
self.result_list.addItem(item)
self.log_content.addItem(f"✅ 转换成功: {basename} -> {os.path.basename(pdf_path)}")
else:
# 失败时显示错误信息
self.log_content.addItem(f"❌ 转换失败: {basename} - {message}")
def handle_finished_all(self):
"""所有文件转换完成"""
self.log_content.addItem(f"🎉 全部转换完成! 共转换 {len(self.converted_files)} 个文件")
self.download_btn.setEnabled(True)
def download_all(self):
"""下载所有转换后的文件"""
if not self.converted_files:
return
folder = QFileDialog.getExistingDirectory(self, "选择保存位置")
if not folder:
return
success = 0
errors = []
for pdf_path in self.converted_files.values():
try:
file_name = os.path.basename(pdf_path)
destination = os.path.join(folder, file_name)
# 复制文件
with open(pdf_path, 'rb') as src_file:
with open(destination, 'wb') as dest_file:
dest_file.write(src_file.read())
success += 1
except Exception as e:
errors.append(f"{os.path.basename(pdf_path)}: {str(e)}")
# 显示结果
if errors:
error_msg = "\n".join(errors[:10]) # 最多显示10个错误
if len(errors) > 10:
error_msg += f"\n...等共 {len(errors)} 个错误"
QMessageBox.warning(self, "部分文件下载失败", f"成功下载 {success} 个文件\n失败文件:\n{error_msg}")
else:
QMessageBox.information(self, "下载完成", f"已成功下载 {success} 个文件到 {folder}")
self.log_content.addItem(f"已下载 {success} 个文件到 {folder}")