import os
import subprocess
import shutil
import time
import tkinter as tk
from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage
import pandas as pd
import win32com.client as win32
from bs4 import BeautifulSoup
import threading
import tempfile
import queue
import traceback
class DiffProcessorApp:
def __init__(self, root):
self.root = root
root.title("高?文件?比?工具")
root.geometry("1000x700")
root.configure(bg="#f5f5f5")
# ?建?代?格主?
self.style = ttk.Style()
self.style.theme_use('clam')
# 自定?主??色
self.style.configure('TButton',
font=('Segoe UI', 10, 'bold'),
borderwidth=1,
foreground="#333",
background="#4CAF50",
bordercolor="#388E3C",
relief="flat",
padding=8,
anchor="center")
self.style.map('TButton',
background=[('active', '#388E3C'), ('disabled', '#BDBDBD')],
foreground=[('disabled', '#9E9E9E')])
self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5")
self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'),
background="#f5f5f5", relief="flat", borderwidth=2)
self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'),
background="#f5f5f5", foreground="#2E7D32")
self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25)
self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold'))
# ?建主框架
main_frame = ttk.Frame(root, padding="15")
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# ??区域
header_frame = ttk.Frame(main_frame)
header_frame.pack(fill=tk.X, pady=(0, 15))
# 添加????
try:
icon = PhotoImage(file="folder_icon.png")
self.icon_label = ttk.Label(header_frame, image=icon)
self.icon_label.image = icon
self.icon_label.pack(side=tk.LEFT, padx=(0, 10))
except:
self.icon_label = ttk.Label(header_frame, text="?", font=("Arial", 24))
self.icon_label.pack(side=tk.LEFT, padx=(0, 10))
title_label = ttk.Label(header_frame, text="高?文件?比?工具",
font=("Segoe UI", 18, "bold"), foreground="#2E7D32")
title_label.pack(side=tk.LEFT)
# 文件??区域
file_frame = ttk.LabelFrame(main_frame, text="文件???", padding="12")
file_frame.pack(fill=tk.X, pady=5)
# 文件???
self.old_folder_entry, self.new_folder_entry = self.create_folder_selector(file_frame, "原始文件?:")
self.new_folder_entry = self.create_folder_selector(file_frame, "修改后文件?:")[0]
# 比???区域
options_frame = ttk.LabelFrame(main_frame, text="比???", padding="12")
options_frame.pack(fill=tk.X, pady=5)
# ??比???
self.recursive_var = tk.BooleanVar(value=True)
recursive_check = ttk.Checkbutton(options_frame, text="??比?子文件?",
variable=self.recursive_var)
recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
# 文件??
filter_frame = ttk.Frame(options_frame)
filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(filter_frame, text="文件??:").pack(side=tk.LEFT, padx=(0, 5))
self.filter_var = tk.StringVar(value="*.*")
filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15)
filter_entry.pack(side=tk.LEFT)
# 目?Excel??
excel_frame = ttk.LabelFrame(main_frame, text="?出?置", padding="12")
excel_frame.pack(fill=tk.X, pady=5)
ttk.Label(excel_frame, text="目?Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
self.excel_file_entry = ttk.Entry(excel_frame, width=60)
self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5)
ttk.Button(excel_frame, text="??...",
command=lambda: self.select_file(self.excel_file_entry,
[("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5)
# ?行按?区域
button_frame = ttk.Frame(main_frame)
button_frame.pack(fill=tk.X, pady=10)
self.run_button = ttk.Button(button_frame, text="?行比?",
command=self.start_processing, width=20, style='TButton')
self.run_button.pack(side=tk.LEFT)
# 停止按?
self.stop_button = ttk.Button(button_frame, text="停止",
command=self.stop_processing, width=10, state=tk.DISABLED)
self.stop_button.pack(side=tk.LEFT, padx=10)
# ?度条
self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate')
self.progress.pack(fill=tk.X, pady=5)
# 状?信息
status_frame = ttk.Frame(main_frame)
status_frame.pack(fill=tk.X, pady=5)
self.status_var = tk.StringVar(value="准?就?")
status_label = ttk.Label(status_frame, textvariable=self.status_var,
font=("Segoe UI", 9), foreground="#2E7D32")
status_label.pack(side=tk.LEFT)
# 日志和??区域
notebook = ttk.Notebook(main_frame)
notebook.pack(fill=tk.BOTH, expand=True, pady=5)
# 文件?????
tree_frame = ttk.Frame(notebook, padding="5")
notebook.add(tree_frame, text="文件???")
# ?建?形??
self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree")
self.tree.heading("#0", text="文件???", anchor=tk.W)
self.tree.heading("Status", text="状?", anchor=tk.W)
self.tree.column("#0", width=400)
self.tree.column("Status", width=100)
vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.tree.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
# 日志??
log_frame = ttk.Frame(notebook, padding="5")
notebook.add(log_frame, text="?行日志")
self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD,
font=("Consolas", 9))
self.log_text.pack(fill=tk.BOTH, expand=True)
self.log_text.config(state=tk.DISABLED)
# ?置网格?重
tree_frame.grid_rowconfigure(0, weight=1)
tree_frame.grid_columnconfigure(0, weight=1)
# ?程控制
self.processing = False
self.queue = queue.Queue()
# ???列?理
self.root.after(100, self.process_queue)
def create_folder_selector(self, parent, label_text):
"""?建文件???器?件"""
frame = ttk.Frame(parent)
frame.pack(fill=tk.X, pady=5)
ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
entry = ttk.Entry(frame, width=70)
entry.grid(row=0, column=1, padx=5, pady=5)
button = ttk.Button(frame, text="??文件?...",
command=lambda: self.select_folder(entry))
button.grid(row=0, column=2, padx=5, pady=5)
return entry, button
def select_folder(self, entry):
"""??文件?"""
foldername = filedialog.askdirectory()
if foldername:
entry.delete(0, tk.END)
entry.insert(0, foldername)
# 自?填充文件???
self.populate_folder_tree(foldername)
def select_file(self, entry, filetypes=None):
"""??文件"""
if filetypes is None:
filetypes = [("所有文件", "*.*")]
filename = filedialog.askopenfilename(filetypes=filetypes)
if filename:
entry.delete(0, tk.END)
entry.insert(0, filename)
def populate_folder_tree(self, path):
"""填充文件????"""
self.tree.delete(*self.tree.get_children())
if not os.path.isdir(path):
return
# 添加根?点
root_node = self.tree.insert("", "end", text=os.path.basename(path),
values=("文件?",), open=True)
self.add_tree_nodes(root_node, path)
def add_tree_nodes(self, parent, path):
"""??添加??点"""
try:
for item in os.listdir(path):
item_path = os.path.join(path, item)
if os.path.isdir(item_path):
node = self.tree.insert(parent, "end", text=item, values=("文件?",))
self.add_tree_nodes(node, item_path)
else:
self.tree.insert(parent, "end", text=item, values=("文件",))
except PermissionError:
self.log_message(f"?限??: 无法?? {path}")
def log_message(self, message):
"""??日志消息"""
self.queue.put(("log", message))
def update_progress(self, value):
"""更新?度条"""
self.queue.put(("progress", value))
def update_status(self, message):
"""更新状?信息"""
self.queue.put(("status", message))
def process_queue(self):
"""?理?程?列中的消息"""
try:
while not self.queue.empty():
msg_type, data = self.queue.get_nowait()
if msg_type == "log":
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, data + "\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
elif msg_type == "progress":
self.progress['value'] = data
elif msg_type == "status":
self.status_var.set(data)
except queue.Empty:
pass
self.root.after(100, self.process_queue)
def parse_html_diff(self, html_path):
"""解析HTML差?文件"""
self.log_message("正在解析HTML差?文件...")
try:
with open(html_path, 'r', encoding='utf-8') as f:
content = f.read()
soup = BeautifulSoup(content, 'html.parser')
diff_table = soup.find('table', {'class': 'diff'})
if not diff_table:
self.log_message("??: 未找到差?表格")
return None
# 提取表格数据
diff_data = []
for row in diff_table.find_all('tr')[1:]: # 跳?表?
cols = row.find_all('td')
if len(cols) >= 3:
# 修?TypeError: ?保所有?都是字符串
diff_type = str(cols[0].get_text(strip=True))
content_left = str(cols[1].get_text(strip=True))
content_right = str(cols[2].get_text(strip=True))
diff_data.append([diff_type, content_left, content_right])
# 修?TypeError: 使用f-string???果
self.log_message(f"成功解析 {len(diff_data)} 行差?数据")
return diff_data
except Exception as e:
# 修?TypeError: 使用f-string???常
error_msg = f"解析HTML失?: {str(e)}\n{traceback.format_exc()}"
self.log_message(error_msg)
return None
def write_to_excel(self, excel_path, diff_data):
"""将差?数据写入Excel"""
self.log_message("正在写入Excel文件...")
try:
# 使用win32com打?Excel
excel = win32.gencache.EnsureDispatch('Excel.Application')
excel.Visible = True
workbook = excel.Workbooks.Open(os.path.abspath(excel_path))
sheet = workbook.Sheets("一覧")
# 从第6行?始写入数据
start_row = 6
for i, row_data in enumerate(diff_data):
for j, value in enumerate(row_data[:6]):
# ?保?是字符串?型
sheet.Cells(start_row + i, j + 1).Value = str(value)
# 保存Excel
workbook.Save()
self.log_message(f"数据已写入Excel第{start_row}行?始")
# 触?"作成"按?
self.log_message("正在触?'作成'按?...")
try:
# ?找按?并点?
button = sheet.Buttons("作成")
button.OnAction = "作成按?的?理"
button.Click()
self.log_message("已触?'作成'按?")
# 等待?理完成
self.update_status("?理中...?等待")
# ??等待机制
for _ in range(30): # 最多等待30秒
if not self.processing:
break
if excel.CalculationState == 0: # 0 = xlDone
break
time.sleep(1)
self.log_message("?理中...")
self.log_message("?理完成")
self.update_status("?理完成")
except Exception as e:
# 修?TypeError: 使用f-string???常
self.log_message(f"按?操作失?: {str(e)}. ?手?点?'作成'按?")
# ??Excel
workbook.Close()
excel.Quit()
return True
except Exception as e:
# 修?TypeError: 使用f-string???常
self.log_message(f"Excel操作失?: {str(e)}\n{traceback.format_exc()}")
return False
def start_processing(self):
"""???理?程 - 修?无????"""
if self.processing:
self.log_message("警告: ?理正在?行中")
return
# ?取路径
old_path = self.old_folder_entry.get()
new_path = self.new_folder_entry.get()
excel_file = self.excel_file_entry.get()
# ??路径??
validation_errors = []
if not old_path:
validation_errors.append("原始文件?路径?空")
elif not os.path.isdir(old_path):
validation_errors.append(f"原始文件?路径无效: {old_path}")
if not new_path:
validation_errors.append("新文件?路径?空")
elif not os.path.isdir(new_path):
validation_errors.append(f"新文件?路径无效: {new_path}")
if not excel_file:
validation_errors.append("Excel文件路径?空")
elif not excel_file.lower().endswith(('.xlsx', '.xlsm')):
validation_errors.append("Excel文件必?是.xlsx或.xlsm格式")
if validation_errors:
self.log_message("??: " + "; ".join(validation_errors))
messagebox.showerror("?入??", "\n".join(validation_errors))
return
# ??WinMerge安装
winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe"
if not os.path.exists(winmerge_path):
self.log_message(f"??: WinMerge未安装在默?位置 {winmerge_path}")
messagebox.showwarning("WinMerge未安装",
"??保WinMerge已安装或更新路径配置")
return
# 禁用?行按?,?用停止按?
self.run_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.processing = True
# ???理?程
thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file))
thread.daemon = True
thread.start()
self.log_message("?理?程已??")
def process_folders(self, old_path, new_path, excel_file):
"""?理文件?比?的?程函数 - 增??常?理"""
output_html = None
try:
# ??1: 生成HTML差?文件
self.update_status("生成HTML差?文件...")
self.update_progress(20)
# 使用??文件存?HTML?告
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file:
output_html = temp_file.name
if not self.run_winmerge(old_path, new_path, output_html):
self.update_status("WinMerge?行失?")
return
# ??2: 将HTML文件与Excel放在同一目?
self.update_status("准?文件...")
self.update_progress(40)
excel_dir = os.path.dirname(excel_file)
if excel_dir:
target_html = os.path.join(excel_dir, "diff_report.html")
try:
shutil.copy(output_html, target_html)
self.log_message(f"已将HTML文件?制到: {target_html}")
except Exception as e:
self.log_message(f"文件?制失?: {str(e)}")
return
# ??3: 解析HTML差?文件
self.update_status("解析差?数据...")
self.update_progress(60)
diff_data = self.parse_html_diff(output_html)
if not diff_data:
self.update_status("HTML解析失?")
return
# ??4: 写入Excel并触?按?
self.update_status("写入Excel并触??理...")
self.update_progress(80)
if not self.write_to_excel(excel_file, diff_data):
self.update_status("Excel操作失?")
return
# 完成
self.update_progress(100)
self.update_status("?理完成!")
self.log_message("文件?比?流程?行完?")
messagebox.showinfo("完成", "文件?比??理成功完成")
except Exception as e:
error_msg = f"?行?程中?生??: {str(e)}\n{traceback.format_exc()}"
self.log_message(error_msg)
self.update_status("?行失?")
messagebox.showerror("??", f"?理失?: {str(e)}")
finally:
# 重新?用?行按?
if self.processing:
self.stop_processing()
# 清理??文件
if output_html and os.path.exists(output_html):
try:
os.remove(output_html)
except:
pass
def run_winmerge(self, path1, path2, output_html):
"""?用WinMerge生成HTML差?文件 - 增????理"""
winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe"
# ??WinMerge可?行文件
if not os.path.exists(winmerge_path):
self.log_message(f"??: WinMerge路径不存在 {winmerge_path}")
return False
winmerge_cmd = [
winmerge_path,
'/u',
'/dl', 'Base',
'/dr', 'Modified',
'/or', output_html,
path1, path2
]
# 添加????
if self.recursive_var.get():
winmerge_cmd.insert(1, '/r')
self.log_message(f"?行WinMerge命令: {' '.join(winmerge_cmd)}")
try:
result = subprocess.run(
winmerge_cmd,
capture_output=True,
text=True,
timeout=120,
creationflags=subprocess.CREATE_NO_WINDOW # 避免控制台窗口??
)
if result.returncode == 0:
self.log_message(f"HTML差??告生成完成: {output_html}")
return True
else:
error_msg = f"WinMerge?行失?(退出?{result.returncode}): {result.stderr}"
self.log_message(error_msg)
return False
except subprocess.TimeoutExpired:
self.log_message("WinMerge?行超?(120秒),????入文件大小")
return False
except Exception as e:
self.log_message(f"WinMerge?行??: {str(e)}")
return False
def write_to_excel(self, excel_path, diff_data):
"""将差?数据写入Excel - 增?健壮性"""
self.log_message("正在写入Excel文件...")
excel = None
workbook = None
try:
# ??Excel文件存在
if not os.path.exists(excel_path):
self.log_message(f"??: Excel文件不存在 {excel_path}")
return False
# 使用win32com打?Excel
excel = win32.gencache.EnsureDispatch('Excel.Application')
excel.Visible = True
excel.DisplayAlerts = False # 禁用警告提示
# ??打?工作簿
try:
workbook = excel.Workbooks.Open(os.path.abspath(excel_path))
except Exception as e:
self.log_message(f"打?Excel文件失?: {str(e)}")
return False
# ??工作表是否存在
sheet_names = [sheet.Name for sheet in workbook.Sheets]
if "一覧" not in sheet_names:
self.log_message("??: Excel文件中缺少'一覧'工作表")
return False
sheet = workbook.Sheets("一覧")
# 从第6行?始写入数据
start_row = 6
for i, row_data in enumerate(diff_data):
for j, value in enumerate(row_data[:6]):
# ?保?是字符串?型
sheet.Cells(start_row + i, j + 1).Value = str(value)
# 保存Excel
workbook.Save()
self.log_message(f"数据已写入Excel第{start_row}行?始")
# 触?"作成"按?
self.log_message("正在触?'作成'按?...")
try:
# ?找按?并点?
button = sheet.Buttons("作成")
button.OnAction = "作成按?的?理"
button.Click()
self.log_message("已触?'作成'按?")
# 等待?理完成
self.update_status("?理中...?等待")
wait_time = 0
max_wait = 60 # 最大等待60秒
while self.processing and wait_time < max_wait:
if excel.CalculationState == 0: # 0 = xlDone
break
time.sleep(1)
wait_time += 1
self.log_message(f"?理中...({wait_time}秒)")
if wait_time >= max_wait:
self.log_message("警告: ?理超?")
else:
self.log_message("?理完成")
return True
except Exception as e:
self.log_message(f"按?操作失?: {str(e)}. ?手?点?'作成'按?")
return False
except Exception as e:
self.log_message(f"Excel操作失?: {str(e)}\n{traceback.format_exc()}")
return False
finally:
# ?保正???Excel
try:
if workbook:
workbook.Close(SaveChanges=False)
if excel:
excel.Quit()
except Exception as e:
self.log_message(f"??Excel?出?: {str(e)}")
def stop_processing(self):
"""停止?理"""
self.processing = False
self.stop_button.config(state=tk.DISABLED)
self.run_button.config(state=tk.NORMAL)
self.update_status("操作已停止")
def process_folders(self, old_path, new_path, excel_file):
"""?理文件?比?的?程函数"""
try:
# ??1: 生成HTML差?文件
self.update_status("生成HTML差?文件...")
self.update_progress(20)
# 使用??文件存?HTML?告
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file:
output_html = temp_file.name
if not self.run_winmerge(old_path, new_path, output_html):
return
# ??2: 将HTML文件与Excel放在同一目?
self.update_status("准?文件...")
self.update_progress(40)
excel_dir = os.path.dirname(excel_file)
if excel_dir:
target_html = os.path.join(excel_dir, "diff_report.html")
shutil.copy(output_html, target_html)
self.log_message(f"已将HTML文件?制到: {target_html}")
# ??3: 解析HTML差?文件
self.update_status("解析差?数据...")
self.update_progress(60)
diff_data = self.parse_html_diff(output_html)
if not diff_data:
return
# ??4: 写入Excel并触?按?
self.update_status("写入Excel并触??理...")
self.update_progress(80)
self.write_to_excel(excel_file, diff_data)
# 完成
self.update_progress(100)
self.update_status("?理完成!")
self.log_message("文件?比?流程?行完?")
except Exception as e:
# 修?TypeError: 使用f-string???常
error_msg = f"?行?程中?生??: {str(e)}\n{traceback.format_exc()}"
self.log_message(error_msg)
self.update_status("?行失?")
finally:
# 重新?用?行按?
if self.processing:
self.stop_processing()
# 清理??文件
if os.path.exists(output_html):
try:
os.remove(output_html)
except:
pass
if __name__ == "__main__":
root = tk.Tk()
app = DiffProcessorApp(root)
root.mainloop()
我想用这个代码完成上述功能