[Python] 纯文本查看 复制代码
import sys, os
import pythoncom
import win32com.client
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout,
QFileDialog, QListWidget, QAbstractItemView
)
from qfluentwidgets import (
PrimaryPushButton, PushButton, LineEdit, RadioButton,
SimpleCardWidget, TitleLabel,
InfoBar, InfoBarPosition, setThemeColor
)
class Worker(QThread):
progress = Signal(int, int)
log = Signal(str)
finished = Signal(str)
error = Signal(str)
def __init__(self, files, folder_path, output_option, output_name):
super().__init__()
self.files = files
self.folder_path = folder_path
self.output_option = output_option
self.output_name = output_name
def _ensure_docx(self, word, file_path):
if not file_path.lower().endswith('.doc'):
return file_path
tmp = os.path.join(os.environ.get('TEMP', os.path.dirname(file_path)),
f'~_convert_{os.path.basename(file_path)}x')
doc = word.Documents.Open(file_path)
doc.SaveAs(tmp, FileFormat=16)
doc.Close()
return tmp
def run(self):
pythoncom.CoInitialize()
folder_name = self.output_name or os.path.basename(os.path.normpath(self.folder_path))
if self.output_option == '桌面':
output_file = os.path.join(os.environ['USERPROFILE'], 'Desktop', folder_name + '.docx')
else:
output_file = os.path.join(self.folder_path, folder_name + '.docx')
word = win32com.client.Dispatch("Word.Application")
word.visible = False
tmp_files = []
try:
out_doc = word.Documents.Add()
for i, f in enumerate(self.files, start=1):
path = os.path.join(self.folder_path, f)
orig_is_doc = path.lower().endswith('.doc')
path = self._ensure_docx(word, path)
if orig_is_doc:
tmp_files.append(path)
if i > 1:
word.Selection.EndKey(6)
out_doc.Repaginate()
if word.Selection.Information(3) % 2 == 1:
word.Selection.InsertBreak(5)
word.Selection.EndKey(6)
word.Selection.InsertFile(path)
self.log.emit(f"已添加: {f}")
self.progress.emit(i, len(self.files))
out_doc.SaveAs(output_file)
out_doc.Close()
except Exception as e:
self.error.emit(f"合并失败: {e}")
finally:
for t in tmp_files:
try:
os.remove(t)
except OSError:
pass
word.Quit()
pythoncom.CoUninitialize()
self.finished.emit(output_file)
class WordCombineInterface(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_StyledBackground)
self.setStyleSheet("background-color: #FFFFFF; SimpleCardWidget { border-radius: 12px; }")
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(40, 30, 40, 30)
card1 = SimpleCardWidget(self)
c1 = QVBoxLayout(card1)
c1.setSpacing(12)
c1.addWidget(TitleLabel("输入文件夹"))
row1 = QHBoxLayout()
self.folder_path = LineEdit()
self.folder_path.setPlaceholderText("请选择要读取文件的目录...")
self.folder_path.setReadOnly(True)
row1.addWidget(self.folder_path)
btn_browse = PushButton("浏览...")
btn_browse.clicked.connect(self.browse_folder)
row1.addWidget(btn_browse)
c1.addLayout(row1)
layout.addWidget(card1)
card_files = SimpleCardWidget(self)
cf = QVBoxLayout(card_files)
cf.setSpacing(8)
cf.addWidget(TitleLabel("文件列表"))
self.file_list = QListWidget()
self.file_list.setDragDropMode(QAbstractItemView.InternalMove)
self.file_list.setDefaultDropAction(Qt.MoveAction)
self.file_list.keyPressEvent = lambda e: (
self.file_list.takeItem(self.file_list.currentRow())
if e.key() == Qt.Key_Delete and self.file_list.currentRow() >= 0
else QListWidget.keyPressEvent(self.file_list, e)
)
cf.addWidget(self.file_list)
layout.addWidget(card_files, 1)
card2 = SimpleCardWidget(self)
c2 = QVBoxLayout(card2)
c2.setSpacing(12)
c2.addWidget(TitleLabel("输出设置"))
self.radio_desktop = RadioButton("桌面")
self.radio_same = RadioButton("与输入文件夹相同")
self.radio_desktop.setChecked(True)
c2.addWidget(self.radio_desktop)
c2.addWidget(self.radio_same)
c2.addSpacing(4)
c2.addWidget(TitleLabel("文件名(留空默认用文件夹名)"))
self.output_name = LineEdit()
self.output_name.setPlaceholderText("留空则使用输入文件夹名称...")
c2.addWidget(self.output_name)
layout.addWidget(card2)
self.btn_start = PrimaryPushButton("开始合并")
self.btn_start.clicked.connect(self.start_merge)
layout.addWidget(self.btn_start)
layout.addStretch()
def browse_folder(self):
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
if folder:
self.folder_path.setText(folder)
self.refresh_file_list(folder)
def refresh_file_list(self, folder):
self.file_list.clear()
files = sorted([f for f in os.listdir(folder) if f.endswith(('.docx', '.doc'))])
if not files:
self.file_list.addItem("(未发现 .docx / .doc 文件)")
return
for f in files:
self.file_list.addItem(f)
def start_merge(self):
folder = self.folder_path.text()
if not folder or not os.path.isdir(folder):
InfoBar.error(title="错误", content="请先选择一个有效的文件夹",
parent=self, position=InfoBarPosition.TOP, duration=3000)
return
items = [self.file_list.item(i).text() for i in range(self.file_list.count())]
items = [f for f in items if f.endswith(('.docx', '.doc'))]
if not items:
InfoBar.error(title="错误", content="没有可合并的 Word 文档",
parent=self, position=InfoBarPosition.TOP, duration=3000)
return
self.btn_start.setEnabled(False)
self.btn_start.setText("处理中 0%")
out_opt = '桌面' if self.radio_desktop.isChecked() else '相同文件夹'
self.worker = Worker(items, folder, out_opt, self.output_name.text().strip())
self.worker.progress.connect(self.update_progress)
self.worker.finished.connect(self.on_finished)
self.worker.error.connect(self.on_error)
self.worker.start()
def update_progress(self, current, total):
self.btn_start.setText(f"处理中 {int(current / total * 100)}%")
def on_finished(self, output_file):
self.btn_start.setText("开始合并")
self.btn_start.setEnabled(True)
InfoBar.success(title="合并完成", content=f"文件已保存到: {output_file}",
parent=self, position=InfoBarPosition.TOP, duration=5000)
def on_error(self, msg):
self.btn_start.setText("开始合并")
self.btn_start.setEnabled(True)
InfoBar.error(title="错误", content=msg,
parent=self, position=InfoBarPosition.TOP, duration=5000)
if __name__ == "__main__":
app = QApplication(sys.argv)
setThemeColor("#0078D4")
w = WordCombineInterface()
w.resize(700, 600)
w.setWindowTitle("Word 文件一键合并工具")
w.show()
sys.exit(app.exec())