PDF拆分、合并

This commit is contained in:
2025-06-08 23:35:27 +08:00
parent c325b5d1bc
commit 0323e92151
8 changed files with 2182 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/v1/build/
/v1/dist/
/v1/test/

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

39
v1/DocConverter.spec Normal file
View File

@@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='DocConverter',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['app.ico'],
)

BIN
v1/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

38
v1/main.spec Normal file
View File

@@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

1188
v1/views/pdf_tools_page.py Normal file

File diff suppressed because it is too large Load Diff

869
v1/views/word_to_pdf-v2.py Normal file
View File

@@ -0,0 +1,869 @@
import os
import shutil
import time
import traceback
import random
import datetime
import win32com
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QListWidget, QListWidgetItem,
QFileDialog, QProgressBar, QMessageBox, QSpacerItem)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize, QTimer
from PyQt5.QtGui import QPixmap, QIcon, QDragEnterEvent, QDropEvent, QDragLeaveEvent
def generate_unique_filename(original_path, output_dir):
"""生成唯一的文件名格式原名称_日期_随机数.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(1000000, 9999999)
# 组合成新文件名
new_filename = f"{name_without_ext}_{current_date}_{random_num}.pdf"
return os.path.join(output_dir, 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, output_dir):
super().__init__()
self.files = files
self.output_dir = output_dir
self.is_running = True
def run(self):
print("转换线程启动")
word = None
try:
# 尝试创建Word应用程序实例
print("正在创建Word应用程序实例...")
try:
word = win32com.client.Dispatch("Word.Application")
print("Word应用程序实例创建成功")
word.Visible = False
word.DisplayAlerts = False
print("Word应用程序配置完成")
except Exception as e:
print(f"创建Word应用程序失败: {str(e)}")
self.conversion_done.emit("", False, f"无法启动Word: {str(e)}")
return
total = len(self.files)
print(f"共有 {total} 个文件需要处理")
# 处理每个文件
for i, file_path in enumerate(self.files):
if not self.is_running:
print("线程被请求中断")
break
print(f"开始处理文件 #{i + 1}: {file_path}")
self.progress_updated.emit(i + 1, total, os.path.basename(file_path))
# 检查文件是否存在
if not os.path.exists(file_path):
print(f"文件不存在: {file_path}")
self.conversion_done.emit(file_path, False, "文件不存在")
continue
try:
# 生成PDF路径
pdf_path = generate_unique_filename(file_path, self.output_dir)
pdf_path = os.path.abspath(pdf_path)
print(f"PDF路径: {pdf_path}")
# 打开文档
print("准备打开Word文档...")
doc = None
try:
# 使用绝对路径
doc = word.Documents.Open(
os.path.abspath(file_path),
False, # ConfirmConversions
True, # ReadOnly
False # AddToRecent
)
print("Word文档打开成功")
# 短暂延迟确保文档加载
time.sleep(1)
# 保存为PDF
print("准备保存为PDF...")
doc.SaveAs(pdf_path, FileFormat=17)
print("PDF保存成功")
self.conversion_done.emit(file_path, True, pdf_path)
except Exception as e:
print(f"文档处理失败: {str(e)}")
error_msg = f"转换失败: {str(e)}"
self.conversion_done.emit(file_path, False, error_msg)
finally:
# 确保文档被安全关闭
if doc:
try:
print("正在关闭文档...")
doc.Close(False) # 不保存更改
doc = None
print("文档关闭成功")
except Exception as e:
print(f"文档关闭失败: {str(e)}")
except Exception as e:
print(f"文件处理失败: {str(e)}")
error_msg = f"处理文件时出错: {str(e)}"
self.conversion_done.emit(file_path, False, error_msg)
print(f"文件 #{i + 1} 处理完成")
print("所有文件处理完成")
self.finished_all.emit()
except Exception as e:
print(f"转换过程中发生严重错误: {str(e)}")
error_msg = f"转换过程中发生严重错误: {str(e)}"
self.conversion_done.emit("", False, error_msg)
self.finished_all.emit()
finally:
# 清理代码不使用pythoncom
try:
if word:
print("正在退出Word应用程序...")
word.Quit()
print("Word应用程序已退出")
word = None
except Exception as e:
print(f"退出Word失败: {str(e)}")
try:
print("尝试强制终止Word进程...")
os.system('taskkill /f /im winword.exe')
print("强制终止命令已执行")
except:
pass
print("清理完成")
print("转换线程结束")
def stop(self):
"""安全停止转换线程"""
self.is_running = False
class WordToPDFPage(QWidget):
def __init__(self):
super().__init__()
self.files = []
self.converted_files = {}
self.conversion_thread = None
self.output_dir = "" # 添加输出目录变量
self.init_ui()
self.setStyleSheet(self.get_page_style())
def init_ui(self):
self.setObjectName("wordToPdfPage")
self.setAcceptDrops(True)
layout = QHBoxLayout()
layout.setContentsMargins(20, 20, 20, 20)
layout.setSpacing(20)
# 左侧面板 - 文件上传
left_panel = QWidget()
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_area.setAcceptDrops(True)
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)
self.upload_text = QLabel("拖拽文件或文件夹到此处\n或点击上传")
self.upload_text.setAlignment(Qt.AlignCenter)
self.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(self.upload_text)
upload_layout.addWidget(upload_btn)
# 文件列表
file_list_frame = QFrame()
file_list_frame.setObjectName("fileListFrame")
file_list_layout = QVBoxLayout(file_list_frame)
file_list_layout.setContentsMargins(0, 0, 0, 0)
file_list_layout.setSpacing(5)
file_title = QLabel("待转换文件")
file_title.setObjectName("sectionTitle")
self.file_list = QListWidget()
self.file_list.setObjectName("fileList")
self.file_list.setSelectionMode(QListWidget.ExtendedSelection)
file_list_layout.addWidget(file_title)
file_list_layout.addWidget(self.file_list)
# 在操作按钮上方添加输出目录选择
output_layout = QHBoxLayout()
output_layout.setSpacing(10)
self.output_label = QLabel("输出目录: 未选择")
self.output_label.setObjectName("outputLabel")
self.output_label.setStyleSheet("color: #cbd5e0; font-size: 13px;")
self.output_label.setFixedWidth(300)
output_btn = QPushButton("选择目录")
output_btn.setObjectName("outputBtn")
output_btn.clicked.connect(self.select_output_directory)
output_layout.addWidget(self.output_label)
output_layout.addWidget(output_btn)
output_layout.addStretch()
# 操作按钮
btn_layout = QHBoxLayout()
btn_layout.setSpacing(10)
self.convert_btn = QPushButton("开始转换")
self.convert_btn.setObjectName("convertBtn")
self.convert_btn.clicked.connect(self.start_conversion)
delete_btn = QPushButton("删除选中")
delete_btn.setObjectName("deleteBtn")
delete_btn.clicked.connect(self.delete_selected)
btn_layout.addWidget(self.convert_btn)
btn_layout.addWidget(delete_btn)
# 组装左侧面板
left_layout.addWidget(upload_area)
left_layout.addLayout(output_layout) # 添加输出目录选择
left_layout.addWidget(file_list_frame)
left_layout.addLayout(btn_layout)
# 右侧面板 - 转换结果
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
right_layout.setContentsMargins(0, 0, 0, 0)
right_layout.setSpacing(15)
# 进度区域
progress_frame = QFrame()
progress_frame.setObjectName("progressFrame")
progress_layout = QVBoxLayout(progress_frame)
progress_layout.setContentsMargins(15, 15, 15, 15)
progress_layout.setSpacing(10)
progress_title = QLabel("转换进度")
progress_title.setObjectName("sectionTitle")
# 总体进度
total_layout = QHBoxLayout()
total_layout.setSpacing(10)
total_label = QLabel("总体进度:")
total_label.setObjectName("progressLabel")
self.total_progress = QProgressBar()
self.total_progress.setObjectName("totalProgress")
self.total_progress.setTextVisible(False)
total_layout.addWidget(total_label)
total_layout.addWidget(self.total_progress)
# 文件进度
file_layout = QHBoxLayout()
file_layout.setSpacing(10)
file_label = QLabel("当前文件:")
file_label.setObjectName("progressLabel")
self.file_progress = QProgressBar()
self.file_progress.setObjectName("fileProgress")
self.file_progress.setTextVisible(False)
file_layout.addWidget(file_label)
file_layout.addWidget(self.file_progress)
# 状态标签
self.status_label = QLabel("就绪")
self.status_label.setObjectName("statusLabel")
self.status_label.setAlignment(Qt.AlignCenter)
progress_layout.addWidget(progress_title)
progress_layout.addLayout(total_layout)
progress_layout.addLayout(file_layout)
progress_layout.addWidget(self.status_label)
# 结果列表
result_frame = QFrame()
result_frame.setObjectName("resultFrame")
result_layout = QVBoxLayout(result_frame)
result_layout.setContentsMargins(0, 0, 0, 0)
result_layout.setSpacing(5)
result_title = QLabel("转换结果")
result_title.setObjectName("sectionTitle")
self.result_list = QListWidget()
self.result_list.setObjectName("resultList")
self.result_list.setSelectionMode(QListWidget.ExtendedSelection)
result_layout.addWidget(result_title)
result_layout.addWidget(self.result_list)
# 下载按钮改为另存为
self.save_as_btn = QPushButton("另存为...")
self.save_as_btn.setObjectName("saveAsBtn")
self.save_as_btn.setEnabled(False)
self.save_as_btn.clicked.connect(self.save_as)
# 日志区域
log_frame = QFrame()
log_frame.setObjectName("logFrame")
log_layout = QVBoxLayout(log_frame)
log_layout.setContentsMargins(0, 0, 0, 0)
log_layout.setSpacing(5)
log_title = QLabel("转换日志")
log_title.setObjectName("sectionTitle")
self.log_content = QListWidget()
self.log_content.setObjectName("logContent")
self.log_content.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
log_layout.addWidget(log_title)
log_layout.addWidget(self.log_content)
# 组装右侧面板
right_layout.addWidget(progress_frame)
right_layout.addWidget(result_frame)
right_layout.addWidget(self.save_as_btn)
right_layout.addWidget(log_frame)
# 主布局
layout.addWidget(left_panel)
layout.addWidget(right_panel)
self.setLayout(layout)
def get_page_style(self):
return """
/* ===== Word转PDF页面样式 ===== */
#wordToPdfPage {
background-color: #1a202c;
}
/* 卡片样式 */
#uploadArea,
#fileListFrame,
#progressFrame,
#resultFrame,
#logFrame {
background-color: #2d3748;
border: 1px solid #4a5568;
border-radius: 8px;
padding: 15px;
}
/* 上传区域 */
#uploadArea {
border: 2px dashed #4a5568;
background-color: rgba(45, 55, 72, 0.5);
transition: all 0.3s ease;
}
#uploadArea:hover {
border-color: #818cf8;
background-color: rgba(129, 140, 248, 0.1);
}
#uploadArea[dragActive="true"] {
border-color: #4ade80;
background-color: rgba(74, 222, 128, 0.1);
}
/* 标题样式 */
#sectionTitle {
font-size: 16px;
font-weight: 600;
color: #e2e8f0;
margin-bottom: 10px;
}
/* 文件列表 */
#fileList, #resultList, #logContent {
background-color: rgba(26, 32, 44, 0.3);
border: 1px solid #4a5568;
border-radius: 6px;
min-height: 200px;
max-height: 250px;
font-size: 13px;
}
#fileList::item, #resultList::item, #logContent::item {
padding: 8px 12px;
border-bottom: 1px solid rgba(74, 85, 104, 0.5);
}
#fileList::item:selected,
#resultList::item:selected,
#logContent::item:selected {
background-color: rgba(129, 140, 248, 0.15);
}
/* 进度条 */
#totalProgress, #fileProgress {
height: 8px;
border-radius: 4px;
background-color: #2d3748;
}
#totalProgress::chunk {
background-color: #818cf8;
border-radius: 4px;
}
#fileProgress::chunk {
background-color: #4ade80;
border-radius: 4px;
}
#progressLabel {
color: #a0aec0;
font-size: 13px;
min-width: 70px;
}
#statusLabel {
font-size: 14px;
color: #e2e8f0;
margin-top: 10px;
padding: 8px;
border-radius: 6px;
background-color: rgba(26, 32, 44, 0.3);
}
/* 按钮样式 */
#outputBtn, #uploadBtn, #convertBtn, #deleteBtn, #saveAsBtn {
min-height: 36px;
font-weight: 500;
border-radius: 6px;
padding: 8px 16px;
}
#uploadBtn {
min-width: 180px;
}
#outputBtn {
background-color: #4a5568;
color: #e2e8f0;
}
#outputBtn:hover {
background-color: #2d3748;
}
#convertBtn {
background-color: #4f46e5;
}
#convertBtn:disabled {
background-color: #4a5568;
}
#saveAsBtn {
background-color: #2563eb;
}
#saveAsBtn:hover {
background-color: #1d4ed8;
}
#saveAsBtn:disabled {
background-color: #4a5568;
}
#deleteBtn {
background-color: #ef4444;
}
#deleteBtn:hover {
background-color: #dc2626;
}
/* 日志样式 */
#logContent::item[type="info"] {
color: #a0aec0;
}
#logContent::item[type="success"] {
color: #34d399;
}
#logContent::item[type="warning"] {
color: #fbbf24;
}
#logContent::item[type="error"] {
color: #f87171;
}
"""
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
event.acceptProposedAction()
# 高亮显示拖拽区域
self.findChild(QFrame, "uploadArea").setProperty("dragActive", True)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
self.upload_text.setText("释放文件以添加")
def dragLeaveEvent(self, event: QDragLeaveEvent):
# 恢复拖拽区域样式
self.findChild(QFrame, "uploadArea").setProperty("dragActive", False)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
self.upload_text.setText("拖拽文件或文件夹到此处\n或点击上传")
def dropEvent(self, event: QDropEvent):
# 恢复拖拽区域样式
self.findChild(QFrame, "uploadArea").setProperty("dragActive", False)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
self.upload_text.setText("拖拽文件或文件夹到此处\n或点击上传")
urls = event.mimeData().urls()
files = []
added_count = 0
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:
added_count = self.add_files(files)
self.add_log(f"添加了 {added_count} 个文件", "success")
else:
self.add_log("未找到有效的Word文件", "warning")
def handle_upload(self):
"""处理文件或文件夹上传"""
options = QFileDialog.Options()
files, _ = QFileDialog.getOpenFileNames(
self, "选择Word文件", "", "Word Files (*.doc *.docx);;All Files (*)", options=options
)
if files:
added_count = self.add_files(files)
self.add_log(f"添加了 {added_count} 个文件", "success")
else:
folder = QFileDialog.getExistingDirectory(self, "选择包含Word文件的文件夹")
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))
if files:
added_count = self.add_files(files)
self.add_log(f"从文件夹添加了 {added_count} 个文件", "success")
else:
self.add_log("文件夹中没有找到Word文件", "warning")
def add_files(self, files):
"""添加文件到列表,避免重复"""
added_count = 0
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)
added_count += 1
return added_count
def delete_selected(self):
"""删除选中的文件"""
selected_items = self.file_list.selectedItems()
if not selected_items:
self.add_log("请先选择要删除的文件", "warning")
return
for item in selected_items:
self.files.remove(item.data(Qt.UserRole))
self.file_list.takeItem(self.file_list.row(item))
self.add_log(f"删除了 {len(selected_items)} 个文件", "info")
def select_output_directory(self):
"""选择输出目录"""
folder = QFileDialog.getExistingDirectory(self, "选择PDF保存位置")
if folder:
self.output_dir = folder
self.output_label.setText(f"输出目录: {folder}")
self.output_label.setToolTip(folder)
self.add_log(f"设置输出目录: {folder}", "info")
return True
return False
def start_conversion(self):
"""开始转换文件"""
if not self.files:
self.add_log("请先添加要转换的文件!", "warning")
return
# 检查输出目录
if not self.output_dir:
if not self.select_output_directory():
self.add_log("未选择输出目录,转换取消", "warning")
return
# 确保目录存在
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
self.add_log(f"创建输出目录: {self.output_dir}", "info")
if not self.files:
self.add_log("请先添加要转换的文件!", "warning")
return
# 重置状态
self.converted_files = {}
self.result_list.clear()
self.log_content.clear()
self.download_btn.setEnabled(False)
self.convert_btn.setEnabled(False)
# 重置进度条
self.total_progress.setValue(0)
self.file_progress.setValue(0)
self.status_label.setText("正在准备转换...")
# 添加开始日志
self.add_log("开始转换任务", "info")
self.add_log(f"{len(self.files)} 个文件待处理", "info")
# 创建并启动转换线程
self.conversion_thread = ConversionThread(self.files.copy(), self.output_dir)
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)
# 重置文件进度为0
self.file_progress.setValue(0)
self.file_progress.setMaximum(100)
# 更新状态
self.status_label.setText(f"正在转换: {os.path.basename(filename)}")
self.add_log(f"开始转换: {os.path.basename(filename)} ({current}/{total})", "info")
# 模拟文件进度动画
self.animate_file_progress()
def animate_file_progress(self):
"""模拟文件转换进度动画(实际应用中应由实际进度驱动)"""
self.file_progress_value = 0
def update_progress():
if self.file_progress_value < 100:
self.file_progress_value += 2
self.file_progress.setValue(self.file_progress_value)
QTimer.singleShot(50, update_progress)
update_progress()
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.add_log(f"✅ 转换成功: {basename} -> {os.path.basename(pdf_path)}", "success")
else:
# 失败时显示错误信息
self.add_log(f"❌❌ 转换失败: {basename} - {message}", "error")
def handle_finished_all(self):
"""所有文件转换完成"""
success_count = len(self.converted_files)
fail_count = len(self.files) - success_count
# 更新状态
self.status_label.setText(f"转换完成! 成功: {success_count}, 失败: {fail_count}")
self.total_progress.setValue(self.total_progress.maximum())
self.file_progress.setValue(100)
self.convert_btn.setEnabled(True)
self.save_as_btn.setEnabled(bool(self.converted_files))
# 添加完成日志
if success_count:
self.add_log(f"🎉🎉 全部转换完成! 成功转换 {success_count} 个文件", "success")
self.add_log(f"文件已保存到: {self.output_dir}", "info")
if fail_count:
self.add_log(f"⚠️ 有 {fail_count} 个文件转换失败", "warning")
def save_as(self):
"""另存为单个或选中的文件"""
if not self.converted_files:
self.add_log("没有可保存的文件", "warning")
return
selected_items = self.result_list.selectedItems()
files_to_save = []
if selected_items:
# 保存选中的文件
for item in selected_items:
file_path = item.data(Qt.UserRole)
files_to_save.append(file_path)
else:
# 保存所有文件
files_to_save = list(self.converted_files.values())
if not files_to_save:
self.add_log("没有选择要保存的文件", "warning")
return
# 如果只保存一个文件,使用另存为对话框
if len(files_to_save) == 1:
default_name = os.path.basename(files_to_save[0])
file_path, _ = QFileDialog.getSaveFileName(
self, "保存PDF文件", default_name, "PDF Files (*.pdf)"
)
if file_path:
try:
shutil.copy2(files_to_save[0], file_path)
self.add_log(f"已保存: {os.path.basename(file_path)}", "success")
except Exception as e:
self.add_log(f"保存失败: {os.path.basename(file_path)} - {str(e)}", "error")
else:
# 保存多个文件,选择目录
folder = QFileDialog.getExistingDirectory(self, "选择保存位置")
if folder:
success = 0
errors = []
for src_path in files_to_save:
try:
file_name = os.path.basename(src_path)
dest_path = os.path.join(folder, file_name)
shutil.copy2(src_path, dest_path)
success += 1
except Exception as e:
errors.append(f"{file_name}: {str(e)}")
if success:
self.add_log(f"已保存 {success} 个文件到 {folder}", "success")
if errors:
self.add_log(f"保存失败 {len(errors)} 个文件", "error")
for error in errors:
self.add_log(f" {error}", "error")
def download_all(self):
"""下载所有转换后的文件"""
if not self.converted_files:
self.add_log("没有可下载的文件", "warning")
return
folder = QFileDialog.getExistingDirectory(self, "选择保存位置")
if not folder:
self.add_log("下载已取消", "info")
return
success = 0
errors = []
# 开始下载
self.status_label.setText("正在下载文件...")
self.add_log(f"开始下载 {len(self.converted_files)} 个文件到 {folder}", "info")
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
self.add_log(f"已下载: {file_name}", "success")
except Exception as e:
errors.append(f"{os.path.basename(pdf_path)}: {str(e)}")
self.add_log(f"下载失败: {os.path.basename(pdf_path)} - {str(e)}", "error")
# 更新状态
self.status_label.setText(f"下载完成! 成功: {success}, 失败: {len(errors)}")
# 显示结果
if errors:
self.add_log(f"⚠️ 部分文件下载失败 ({len(errors)} 个)", "warning")
else:
self.add_log(f"✅ 全部文件下载成功!", "success")
self.add_log(f"文件已保存到: {folder}", "info")
def add_log(self, message, log_type="info"):
"""添加日志项"""
item = QListWidgetItem(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] {message}")
item.setData(Qt.UserRole + 1, log_type) # 存储日志类型用于样式
self.log_content.addItem(item)
self.log_content.scrollToBottom()
def paintEvent(self, event):
"""为拖拽区域添加悬停效果"""
if self.underMouse():
self.findChild(QFrame, "uploadArea").setProperty("hover", True)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
else:
self.findChild(QFrame, "uploadArea").setProperty("hover", False)
self.findChild(QFrame, "uploadArea").style().polish(self.findChild(QFrame, "uploadArea"))
super().paintEvent(event)

View File

@@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='奶酪云工具箱',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['icon.ico'],
)