一、背景故事
我有一个搞财会的朋友,每个季度末都得给不同的公司发通知函,告诉它们目前为止暂估了多少金额,或者缺了多少成本金额。
所以她要花6~8小时把几百家公司的名字和财务数据复制粘贴到通知函模板里再集中打印。
以下是她发给我的数据EXCLE表格和通知函WORD模板(稍作处理),如下图所示。
二、思考
我们为什么不能使用百度文心快码Comate Zulu,让它来帮我们写一个自动化的小应用来给我们打工呢?
三、行动
1、数据格式标准化
根据多年的倒腾经验,当前的Excel文件数据和Word模板格式不利于自动化处理。(也许可以,但是AI可能要花很长时间理解)所以,我们得把Excel文件数据和Word模板进行一些微调。
将Excel文件中有暂估金额和成本金额的数据放到Sheet2,而只有暂估金额金额的数据放到Sheet1.
并且用标头+数据的格式替代原来文本+数据的格式。如下:
这样Excel里的标题和Word的客户名称、暂估金额和成本金额就成了一一对应的关系。
2、需求提炼
我们通过txt文本来初步提炼下我们的应用需求:
(1)从暂估函数据Excel文件的Sheet1和Sheet2中读取数据。
(2)Sheet1中的客户名称和暂估金额,填到暂估函数模板A.docx的客户名称和暂估金额位置。
(3)将Sheet2中的客户名称、暂估金额和成本金额,填到暂估函数模板B.docx的客户名称、暂估金额和成本金额位置。
(4)不同的客户名称输出不同的文件,为方便打印,多个填充后的文档合并成一个输出文件。最后生成两种输出文档(暂估函A_合并.docx和暂估函B_合并.docx)。
3、提交需求
为了方便Zulu读取文档数据,新建一个文件夹存放暂估函数据、暂估函模板A、暂估函模板B。
打开VsCode,打开存档数据的文件夹,点击百度文心快码插件,切换到Zulu模式,将需求提交给它。
很快,Zulu经过思考理解了你的需求并给我写了一个python脚本。
Zulu为了运行这个脚本,指导我安装必要的Python库来运行。
安装了必要的库,Zulu开始检查Excel文件的内容,以便了解数据结构。
Zulu要求我运行命令来检查,检查的结果是没有输出任何内容。它认为这可能是因为Excel文件的格式问题或者文件路径不正确。
因此它开始为我们排查错误,尝试列出当前目录中的文件,以确保我们在正确的位置。
最终它发现是因为我的列名中存在空格!
最后运行终端命令。成功地生成了两个合并的文档:暂估函A_合并.docx 和 暂估函B_合并.docx。
Zulu每次完成后都会有个总结:
我们创建了一个Python脚本 process_docs.py,用于处理Excel数据和Word模板。
我们从 暂估函数据.xlsx 文件中读取了Sheet1和Sheet2的数据。
我们使用 暂估函模板A.docx 处理了Sheet1的数据,生成了 暂估函A_合并.docx。
我们使用 暂估函模板B.docx 处理了Sheet2的数据,生成了 暂估函B_合并.docx。
每个生成的文档都包含了多个客户的信息,每个客户的信息都在单独的页面上。
现在,您可以打开 暂估函A_合并.docx 和 暂估函B_合并.docx 来查看结果。这两个文件应该包含了所有客户的暂估函,每个客户的信息都在单独的页面上,便于打印。
四、深化
Zulu用了5分钟时间实现了基本应用需求,后面我又花了15分钟深化了自己的需求:
1、增加了一个选择文件的窗口界面
2、要求它保留原始文档的格式和样式
其中保留Word模版的格式花了最长的时间,因为涉及到不同的Pyhton库。下图是我最终实现的应用界面:
其实这个应用需求未来还可以进一步深化。我们现在解决的固定场景的固定数据,如果我要处理不同的Excel模板,不同的Word模板,有多余3个以上需要替换的呢?
五、使用感受
从"人工肝"到"AI外挂":效率革命的暴击
全程没写一行代码,只用百度文心快码(Baidu Comate)里的Zulu工具,20分钟就搞定了她6小时的重复劳动!这感觉就像从"人工肝"模式瞬间切换成"AI外挂",爽到飞起!
Zulu的的"神仙操作
- 需求提炼:和AI的"灵魂对话"
用自然语言描述需求:"把Sheet1的数据填到模板A,Sheet2填到模板B,最后合并成两个文件"。Zulu居然秒懂!这体验比跟甲方沟通顺畅100倍!生成的Python脚本逻辑清晰,连我这个代码盲都能看懂注释。 - 智能纠错:AI比你还细心!
运行脚本时突然报错?别急!Zulu主动排查发现列名里的空格是罪魁祸首!这波操作让我跪服——以前手动处理时,这种细节错误至少要检查半小时,AI却瞬间定位问题,连Debug都省了!
价值爆点:省下的不止是时间
💡 时间自由:6小时→20分钟
省下的5小时40分钟够追半部剧、撸个铁,甚至学门新技能!对财会朋友来说,季度末再也不用加班到深夜,生活质量直线上升。
💡 错误率归零:AI比人更靠谱
手动复制时难免看错行、漏数据,但Zulu严格按照逻辑执行,生成的文件0错误!这对财务数据来说简直是"保命符"。
💡 技能平权:不会编程?不存在的!
整个流程最颠覆的是——全程没碰代码!Zulu把Python封装成"魔法对话",连需求描述都用自然语言。这让我想起《三体》里的"智子",只不过这次是帮人类打工的AI。
总结:AI工具不是替代人,而是解放人
用Zulu的过程让我深刻感受到:重复性工作正在被重新定义。它不是要抢谁的饭碗,而是让人类从机械劳动中抽身,去做更有创造力的事。就像财务朋友说的:"省下的时间,我终于能分析数据而不是做表格了!"
点击我的邀请码,你也来试试吧:
https://comate.baidu.com/?inviteCode=6h2mrwpb
六、附Python脚本
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from openpyxl import load_workbook
from docx import Document
import os
import threading
import shutil
import sys
import subprocess
import tempfile
def count_data_rows(sheet):
"""统计sheet中除标题外的数据行数"""
row_count = 0
for row in sheet.iter_rows(min_row=2): # 从第2行开始,跳过标题
if any(cell.value for cell in row): # 如果行中有任何非空值
row_count += 1
return row_count
def replace_placeholders(doc, data):
"""在文档中替换占位符,保留原始格式"""
# 打印调试信息
print("正在替换以下占位符:", data)
def process_paragraph(paragraph, data):
"""处理单个段落中的占位符"""
original_text = paragraph.text
new_text = original_text
# 收集所有运行的文本和格式
runs_with_format = []
for run in paragraph.runs:
runs_with_format.append({
'text': run.text,
'font': run.font,
'bold': run.bold,
'italic': run.italic,
'underline': run.underline,
'style': run.style
})
# 替换所有占位符
for key, value in data.items():
replace_value = str(value) if value is not None else ""
if key in new_text:
new_text = new_text.replace(key, replace_value)
# 如果文本有变化,重新构建段落
if new_text != original_text:
# 清除原有内容
for run in paragraph.runs:
run._element.getparent().remove(run._element)
# 添加新的运行,保持原有格式
new_run = paragraph.add_run(new_text)
if runs_with_format:
# 使用第一个运行的格式
format_info = runs_with_format[0]
new_run.font.name = format_info['font'].name
new_run.font.size = format_info['font'].size
new_run.font.bold = format_info['bold']
new_run.font.italic = format_info['italic']
new_run.font.underline = format_info['underline']
if format_info['style']:
new_run.style = format_info['style']
# 处理所有段落
for paragraph in doc.paragraphs:
if paragraph.text.strip(): # 只处理非空段落
process_paragraph(paragraph, data)
# 处理表格中的段落
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
if paragraph.text.strip(): # 只处理非空段落
process_paragraph(paragraph, data)
def append_document(target_doc_path, source_doc_path):
"""将源文档附加到目标文档,保留所有格式"""
# 使用docxcompose合并文档
from docxcompose.composer import Composer
# 打开目标文档作为主文档
master = Document(target_doc_path)
composer = Composer(master)
# 添加分页符
master.add_page_break()
# 附加源文档
doc2 = Document(source_doc_path)
composer.append(doc2)
# 保存合并后的文档
composer.save(target_doc_path)
def install_required_packages(status_callback=None):
"""安装所需的依赖包"""
if status_callback:
status_callback("检查并安装必要的依赖包...")
try:
import docxcompose
if status_callback:
status_callback("依赖包已安装")
return True
except ImportError:
if status_callback:
status_callback("正在安装 docxcompose 包...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "docxcompose"])
if status_callback:
status_callback("依赖包安装成功")
return True
except Exception as e:
error_msg = f"无法安装必要的依赖包 'docxcompose': {str(e)}"
if status_callback:
status_callback(f"错误: {error_msg}")
raise Exception(error_msg)
def replace_placeholders(doc, data):
"""在文档中替换占位符"""
# 替换段落中的占位符
for paragraph in doc.paragraphs:
for key, value in data.items():
if key in paragraph.text:
# 保留原始格式,只替换文本
for run in paragraph.runs:
if key in run.text:
run.text = run.text.replace(key, str(value) if value is not None else "")
# 替换表格中的占位符
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
for key, value in data.items():
if key in paragraph.text:
for run in paragraph.runs:
if key in run.text:
run.text = run.text.replace(key, str(value) if value is not None else "")
def append_document(target_doc_path, source_doc_path):
"""将源文档附加到目标文档,保留所有格式"""
# 使用docxcompose合并文档
from docxcompose.composer import Composer
# 打开目标文档作为主文档
master = Document(target_doc_path)
composer = Composer(master)
# 添加分页符
master.add_page_break()
# 附加源文档
doc2 = Document(source_doc_path)
composer.append(doc2)
# 保存合并后的文档
composer.save(target_doc_path)
def copy_and_replace_template(template_doc, target_doc, data):
"""复制模板内容并替换占位符,保留所有格式"""
# 复制所有段落
for paragraph in template_doc.paragraphs:
# 复制段落样式和对齐方式
new_paragraph = target_doc.add_paragraph()
new_paragraph._p.set_attribute('style', paragraph._p.get_attribute('style'))
new_paragraph.paragraph_format.alignment = paragraph.paragraph_format.alignment
new_paragraph.paragraph_format.left_indent = paragraph.paragraph_format.left_indent
new_paragraph.paragraph_format.right_indent = paragraph.paragraph_format.right_indent
new_paragraph.paragraph_format.space_before = paragraph.paragraph_format.space_before
new_paragraph.paragraph_format.space_after = paragraph.paragraph_format.space_after
new_paragraph.paragraph_format.line_spacing = paragraph.paragraph_format.line_spacing
# 复制段落中的所有文本和格式
for run in paragraph.runs:
new_run = new_paragraph.add_run(run.text)
# 复制字体格式
new_run.bold = run.bold
new_run.italic = run.italic
new_run.underline = run.underline
new_run.font.name = run.font.name
new_run.font.color.rgb = run.font.color.rgb
if run.font.size:
new_run.font.size = run.font.size
# 复制其他格式属性
new_run.style = run.style
if hasattr(run, 'math') and run.math is not None:
new_run._r.append(run.math._element)
# 复制所有表格
for table in template_doc.tables:
# 创建新表格并复制样式
new_table = target_doc.add_table(rows=len(table.rows), cols=len(table.columns))
new_table.style = table.style
new_table.alignment = table.alignment
# 复制表格宽度
if hasattr(table, 'width'):
new_table._tbl.tblPr.tblW.w = table._tbl.tblPr.tblW.w
new_table._tbl.tblPr.tblW.type = table._tbl.tblPr.tblW.type
# 复制单元格内容和格式
for i, row in enumerate(table.rows):
# 复制行高
if hasattr(row, 'height'):
new_table.rows[i].height = row.height
for j, cell in enumerate(row.cells):
new_cell = new_table.cell(i, j)
# 复制单元格属性
if cell.width:
new_cell.width = cell.width
# 处理单元格合并
if cell.vertical_merge:
new_cell._tc.tcPr.vMerge = cell._tc.tcPr.vMerge
if hasattr(cell._tc.tcPr, 'hMerge') and cell._tc.tcPr.hMerge is not None:
new_cell._tc.tcPr.hMerge = cell._tc.tcPr.hMerge
# 复制单元格内容
# 先清空新单元格中的段落
for p in new_cell.paragraphs:
p._p.getparent().remove(p._p)
new_cell._paragraphs = []
# 复制单元格中的所有段落
for paragraph in cell.paragraphs:
new_paragraph = new_cell.add_paragraph()
new_paragraph._p.set_attribute('style', paragraph._p.get_attribute('style'))
new_paragraph.paragraph_format.alignment = paragraph.paragraph_format.alignment
# 复制段落中的所有文本和格式
for run in paragraph.runs:
new_run = new_paragraph.add_run(run.text)
new_run.bold = run.bold
new_run.italic = run.italic
new_run.underline = run.underline
new_run.font.name = run.font.name
new_run.font.color.rgb = run.font.color.rgb
if run.font.size:
new_run.font.size = run.font.size
new_run.style = run.style
# 替换占位符
replace_placeholders(target_doc, data)
# 添加分页符
target_doc.add_page_break()
def process_documents(excel_path, template_a_path, template_b_path, output_a_path, output_b_path,
status_callback=None, progress_callback=None):
"""处理文档并提供状态和进度回调"""
try:
if status_callback:
status_callback("开始处理文档...")
# 加载Excel文件
if status_callback:
status_callback("正在读取Excel数据...")
wb = load_workbook(excel_path)
sheet1 = wb['Sheet1']
sheet2 = wb['Sheet2']
# 统计数据行数
row_count_sheet1 = count_data_rows(sheet1)
row_count_sheet2 = count_data_rows(sheet2)
if status_callback:
status_callback(f"Sheet1中有{row_count_sheet1}行数据")
status_callback(f"Sheet2中有{row_count_sheet2}行数据")
# 更新进度
if progress_callback:
progress_callback(10)
# 处理A类文档
if status_callback:
status_callback("正在处理模板A的数据...")
# 直接使用模板文件作为基础
shutil.copy(template_a_path, output_a_path)
doc_a = Document(output_a_path)
# 处理第一个公司数据
first_row = next(sheet1.iter_rows(min_row=2, max_row=2), None)
if first_row:
company_data = {
"客户名称": first_row[0].value if first_row[0].value else "",
"暂估金额": first_row[1].value if first_row[1].value else ""
}
# 替换第一个文档中的占位符
replace_placeholders(doc_a, company_data)
doc_a.save(output_a_path)
# 处理后续公司数据
for i, row in enumerate(sheet1.iter_rows(min_row=3, max_row=row_count_sheet1+1)):
try:
company_data = {
"客户名称": row[0].value if row[0].value else "",
"暂估金额": row[1].value if row[1].value else ""
}
# 创建临时文件
temp_file = f"temp_a_{i}.docx"
shutil.copy(template_a_path, temp_file)
# 替换临时文件中的占位符
temp_doc = Document(temp_file)
replace_placeholders(temp_doc, company_data)
temp_doc.save(temp_file)
# 将临时文件附加到主文档
append_document(output_a_path, temp_file)
# 删除临时文件
try:
os.remove(temp_file)
except Exception as e:
print(f"删除临时文件错误: {e}")
except Exception as e:
print(f"处理A类文档第{i+1}行数据时出错: {e}")
if status_callback:
status_callback(f"处理A类文档第{i+1}行数据时出错: {e}")
# 更新进度
if progress_callback:
progress = 10 + (i + 1) / row_count_sheet1 * 40
progress_callback(progress)
# 更新进度
if progress_callback:
progress_callback(50)
# 处理B类文档
if status_callback:
status_callback("正在处理模板B的数据...")
# 直接使用模板文件作为基础
shutil.copy(template_b_path, output_b_path)
doc_b = Document(output_b_path)
# 处理第一个公司数据
first_row = next(sheet2.iter_rows(min_row=2, max_row=2), None)
if first_row:
company_data = {
"客户名称": first_row[0].value if first_row[0].value else "",
"暂估金额": first_row[1].value if first_row[1].value else "",
"成本金额": first_row[2].value if first_row[2].value else ""
}
# 替换第一个文档中的占位符
replace_placeholders(doc_b, company_data)
doc_b.save(output_b_path)
# 处理后续公司数据
for i, row in enumerate(sheet2.iter_rows(min_row=3, max_row=row_count_sheet2+1)):
try:
company_data = {
"客户名称": row[0].value if row[0].value else "",
"暂估金额": row[1].value if row[1].value else "",
"成本金额": row[2].value if row[2].value else ""
}
# 打印调试信息,确保成本金额被正确读取
print(f"处理第{i+3}行数据 - 客户名称: {company_data['客户名称']}, 暂估金额: {company_data['暂估金额']}, 成本金额: {company_data['成本金额']}")
# 创建临时文件
temp_file = f"temp_b_{i}.docx"
shutil.copy(template_b_path, temp_file)
# 替换临时文件中的占位符
temp_doc = Document(temp_file)
replace_placeholders(temp_doc, company_data)
temp_doc.save(temp_file)
# 将临时文件附加到主文档
append_document(output_b_path, temp_file)
# 删除临时文件
try:
os.remove(temp_file)
except Exception as e:
print(f"删除临时文件错误: {e}")
except Exception as e:
print(f"处理B类文档第{i+1}行数据时出错: {e}")
if status_callback:
status_callback(f"处理B类文档第{i+1}行数据时出错: {e}")
# 更新进度
if progress_callback:
progress = 50 + (i + 1) / row_count_sheet2 * 40
progress_callback(progress)
# 更新最终进度
if progress_callback:
progress_callback(100)
if status_callback:
status_callback("所有文档处理完成!")
return row_count_sheet1, row_count_sheet2
except Exception as e:
error_message = f"处理文档时出错: {str(e)}"
print(error_message)
if status_callback:
status_callback(f"错误: {error_message}")
raise
class EstimateDocApp:
def __init__(self, root):
self.root = root
self.root.title("暂估函自动填写工具")
self.root.geometry("700x700")
# 创建主框架
main_frame = ttk.Frame(root, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建文件路径选择区域
file_frame = ttk.LabelFrame(main_frame, text="文件设置", padding="10")
file_frame.pack(fill=tk.X, pady=10)
# Excel文件路径
ttk.Label(file_frame, text="Excel数据文件:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.excel_path_var = tk.StringVar(value="暂估函数据.xlsx")
ttk.Entry(file_frame, textvariable=self.excel_path_var, width=50).grid(row=0, column=1, sticky=tk.W)
ttk.Button(file_frame, text="浏览...", command=lambda: self.browse_file(self.excel_path_var, [("Excel文件", "*.xlsx")])).grid(row=0, column=2, padx=5)
# 模板A文件路径
ttk.Label(file_frame, text="模板A文件:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.template_a_path_var = tk.StringVar(value="暂估函模板A.docx")
ttk.Entry(file_frame, textvariable=self.template_a_path_var, width=50).grid(row=1, column=1, sticky=tk.W)
ttk.Button(file_frame, text="浏览...", command=lambda: self.browse_file(self.template_a_path_var, [("Word文件", "*.docx")])).grid(row=1, column=2, padx=5)
# 模板B文件路径
ttk.Label(file_frame, text="模板B文件:").grid(row=2, column=0, sticky=tk.W, pady=5)
self.template_b_path_var = tk.StringVar(value="暂估函模板B.docx")
ttk.Entry(file_frame, textvariable=self.template_b_path_var, width=50).grid(row=2, column=1, sticky=tk.W)
ttk.Button(file_frame, text="浏览...", command=lambda: self.browse_file(self.template_b_path_var, [("Word文件", "*.docx")])).grid(row=2, column=2, padx=5)
# 输出文件设置
output_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="10")
output_frame.pack(fill=tk.X, pady=10)
# 输出文件A路径
ttk.Label(output_frame, text="输出文件A:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.output_a_path_var = tk.StringVar(value="暂估函A_合并.docx")
ttk.Entry(output_frame, textvariable=self.output_a_path_var, width=50).grid(row=0, column=1, sticky=tk.W)
ttk.Button(output_frame, text="浏览...", command=lambda: self.save_file(self.output_a_path_var, [("Word文件", "*.docx")])).grid(row=0, column=2, padx=5)
# 输出文件B路径
ttk.Label(output_frame, text="输出文件B:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.output_b_path_var = tk.StringVar(value="暂估函B_合并.docx")
ttk.Entry(output_frame, textvariable=self.output_b_path_var, width=50).grid(row=1, column=1, sticky=tk.W)
ttk.Button(output_frame, text="浏览...", command=lambda: self.save_file(self.output_b_path_var, [("Word文件", "*.docx")])).grid(row=1, column=2, padx=5)
# 进度和状态区域
status_frame = ttk.Frame(main_frame, padding="10")
status_frame.pack(fill=tk.X, pady=10)
# 进度条
ttk.Label(status_frame, text="处理进度:").pack(anchor=tk.W)
self.progress = ttk.Progressbar(status_frame, length=650, mode='determinate')
self.progress.pack(fill=tk.X, pady=5)
# 状态文本框
ttk.Label(status_frame, text="处理状态:").pack(anchor=tk.W)
self.status_text = tk.Text(status_frame, height=10, width=80, wrap=tk.WORD)
self.status_text.pack(fill=tk.BOTH, expand=True, pady=5)
# 滚动条
scrollbar = ttk.Scrollbar(self.status_text, orient="vertical", command=self.status_text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.status_text.configure(yscrollcommand=scrollbar.set)
# 操作按钮区域
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=10)
# 开始处理按钮
self.process_button = ttk.Button(button_frame, text="开始处理", command=self.start_processing)
self.process_button.pack(side=tk.RIGHT, padx=5)
# 打开输出文件夹按钮
self.open_button = ttk.Button(button_frame, text="打开输出文件", command=self.open_output_files)
self.open_button.pack(side=tk.RIGHT, padx=5)
# 初始状态
self.log("程序已启动,请设置文件路径并点击'开始处理'按钮。")
def browse_file(self, path_var, file_types):
"""打开文件选择对话框"""
filename = filedialog.askopenfilename(filetypes=file_types)
if filename:
path_var.set(filename)
def save_file(self, path_var, file_types):
"""打开文件保存对话框"""
filename = filedialog.asksaveasfilename(filetypes=file_types, defaultextension=file_types[0][1])
if filename:
path_var.set(filename)
def log(self, message):
"""添加日志到状态文本框"""
self.status_text.insert(tk.END, message + "\n")
self.status_text.see(tk.END) # 自动滚动到底部
self.root.update_idletasks()
def update_progress(self, value):
"""更新进度条"""
self.progress['value'] = value
self.root.update_idletasks()
def start_processing(self):
"""开始处理文档"""
# 禁用按钮,防止重复点击
self.process_button.configure(state=tk.DISABLED)
# 获取文件路径
excel_path = self.excel_path_var.get()
template_a_path = self.template_a_path_var.get()
template_b_path = self.template_b_path_var.get()
output_a_path = self.output_a_path_var.get()
output_b_path = self.output_b_path_var.get()
# 验证文件路径
if not all([os.path.exists(excel_path), os.path.exists(template_a_path), os.path.exists(template_b_path)]):
messagebox.showerror("错误", "请确保所有输入文件路径有效!")
self.process_button.configure(state=tk.NORMAL)
return
# 安装必要的依赖包
try:
install_required_packages(status_callback=self.log)
except Exception as e:
messagebox.showerror("错误", f"安装依赖包失败: {str(e)}\n请手动运行: pip install docxcompose")
self.process_button.configure(state=tk.NORMAL)
return
# 清空状态文本框
self.status_text.delete(1.0, tk.END)
self.progress['value'] = 0
# 在新线程中处理文档,避免界面卡死
thread = threading.Thread(target=self.process_in_thread, args=(
excel_path, template_a_path, template_b_path, output_a_path, output_b_path
))
thread.daemon = True
thread.start()
def process_in_thread(self, excel_path, template_a_path, template_b_path, output_a_path, output_b_path):
"""在线程中处理文档"""
try:
row_count_sheet1, row_count_sheet2 = process_documents(
excel_path, template_a_path, template_b_path, output_a_path, output_b_path,
status_callback=self.log,
progress_callback=self.update_progress
)
# 处理完成后显示消息框
self.root.after(0, lambda: messagebox.showinfo("完成",
f"文档处理完成!\n"
f"- 已处理Sheet1中的{row_count_sheet1}行数据\n"
f"- 已处理Sheet2中的{row_count_sheet2}行数据\n"
f"输出文件已保存为:\n"
f"- {os.path.basename(output_a_path)}\n"
f"- {os.path.basename(output_b_path)}"
))
except Exception as e:
self.root.after(0, lambda: messagebox.showerror("错误", f"处理过程中出现错误:\n{str(e)}"))
self.log(f"错误: {str(e)}")
finally:
# 恢复按钮状态
self.root.after(0, lambda: self.process_button.configure(state=tk.NORMAL))
def open_output_files(self):
"""打开输出文件"""
output_a_path = self.output_a_path_var.get()
output_b_path = self.output_b_path_var.get()
if os.path.exists(output_a_path):
os.startfile(output_a_path)
else:
self.log(f"文件不存在: {output_a_path}")
if os.path.exists(output_b_path):
os.startfile(output_b_path)
else:
self.log(f"文件不存在: {output_b_path}")
if __name__ == "__main__":
try:
# 检查并安装必要的依赖包
try:
install_required_packages(status_callback=print)
except Exception as e:
print(f"警告: 无法自动安装依赖包: {str(e)}")
print("请手动运行: pip install docxcompose")
root = tk.Tk()
app = EstimateDocApp(root)
root.mainloop()
except Exception as e:
print(f"程序运行出错:{str(e)}")