PDF拆分、合并
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/v1/build/
|
||||
/v1/dist/
|
||||
/v1/test/
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal 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
39
v1/DocConverter.spec
Normal 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
BIN
v1/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
38
v1/main.spec
Normal file
38
v1/main.spec
Normal 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
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
869
v1/views/word_to_pdf-v2.py
Normal 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)
|
||||
39
v1/奶酪云工具箱.spec
Normal file
39
v1/奶酪云工具箱.spec
Normal 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'],
|
||||
)
|
||||
Reference in New Issue
Block a user