import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import openpyxl
from openpyxl.utils import get_column_letter
import sys
from datetime import datetime
import re
class ExcelParamFiller:
def __init__(self):
self.log_messages = []
self.target_sheets = [
"EasySuite_Model@2",
"EasySuite_Model@3",
"EasySuite_Model@4",
"EasySuite_Model@5",
"EasySuite_Model@6"
]
def log(self, message):
"""记录日志信息"""
self.log_messages.append(message)
print(message)
def fill_parameters(self, source_file, target_file, output_file):
"""将源Excel的参数值回填到目标Excel"""
try:
start_time = datetime.now()
self.log(f"\n🚀 开始参数回填")
self.log(f"源文件: {os.path.basename(source_file)}")
self.log(f"目标文件: {os.path.basename(target_file)}")
# 加载源Excel文件
source_wb = openpyxl.load_workbook(source_file, data_only=True)
self.log(f"📂 加载源文件成功,包含 {len(source_wb.sheetnames)} 个工作表")
# 加载目标Excel文件
target_wb = openpyxl.load_workbook(target_file)
self.log(f"📂 加载目标文件成功,包含 {len(target_wb.sheetnames)} 个工作表")
# 创建新Excel文件(复制目标文件)
output_wb = openpyxl.Workbook()
# 移除默认创建的工作表
for sheet in output_wb.sheetnames:
output_wb.remove(output_wb[sheet])
# 复制目标文件的所有工作表到输出文件
for sheet_name in target_wb.sheetnames:
source_sheet = target_wb[sheet_name]
new_sheet = output_wb.create_sheet(title=sheet_name)
self.copy_sheet_content(source_sheet, new_sheet)
self.log(f"📄 复制工作表: {sheet_name}")
# 处理源文件的索引表
if "EasySuite_Model_Index" not in source_wb.sheetnames:
raise Exception("源文件中缺少 'EasySuite_Model_Index' 工作表")
source_index = source_wb["EasySuite_Model_Index"]
source_mapping = self.parse_index_sheet(source_index)
self.log(f"从源文件索引表解析出 {len(source_mapping)} 个参数表映射")
# 处理目标文件的索引表
if "EasySuite_Model_Index" not in output_wb.sheetnames:
raise Exception("目标文件中缺少 'EasySuite_Model_Index' 工作表")
target_index = output_wb["EasySuite_Model_Index"]
target_mapping = self.parse_index_sheet(target_index)
self.log(f"从目标文件索引表解析出 {len(target_mapping)} 个参数表映射")
# 回填参数值
total_params_filled = 0
for sheet_name in self.target_sheets:
if sheet_name in source_mapping and sheet_name in target_mapping:
# 获取源数据工作表
source_param_sheet_name = source_mapping[sheet_name]["param_sheet"]
source_data_sheet_name = source_mapping[sheet_name]["data_sheet"]
# 获取目标数据工作表
target_param_sheet_name = target_mapping[sheet_name]["param_sheet"]
target_data_sheet_name = target_mapping[sheet_name]["data_sheet"]
# 检查工作表是否存在
if source_param_sheet_name not in source_wb.sheetnames:
self.log(f"⚠️ 源文件中缺少参数表: {source_param_sheet_name}")
continue
if source_data_sheet_name not in source_wb.sheetnames:
self.log(f"⚠️ 源文件中缺少数据表: {source_data_sheet_name}")
continue
if target_param_sheet_name not in output_wb.sheetnames:
self.log(f"⚠️ 目标文件中缺少参数表: {target_param_sheet_name}")
continue
if target_data_sheet_name not in output_wb.sheetnames:
self.log(f"⚠️ 目标文件中缺少数据表: {target_data_sheet_name}")
continue
# 回填该参数表
params_filled = self.fill_sheet_parameters(
source_wb[source_param_sheet_name],
source_wb[source_data_sheet_name],
output_wb[target_param_sheet_name],
output_wb[target_data_sheet_name]
)
total_params_filled += params_filled
self.log(f"🔁 工作表 '{sheet_name}' 回填完成: {params_filled} 个参数已更新")
else:
self.log(f"⚠️ 参数表 '{sheet_name}' 在索引表中未找到映射")
# 保存新文件
output_wb.save(output_file)
# 计算处理时间
processing_time = datetime.now() - start_time
self.log(f"\n🎉 回填完成! 共更新 {total_params_filled} 个参数值")
self.log(f"📥 文件已保存至: {output_file}")
self.log(f"⏱️ 总处理时间: {processing_time.total_seconds():.2f}秒")
return True
except Exception as e:
import traceback
self.log(f"❌ 处理过程中出错: {str(e)}")
self.log(traceback.format_exc())
return False
def parse_index_sheet(self, index_sheet):
"""解析索引表内容"""
mapping = {}
for row in index_sheet.iter_rows(min_row=2, values_only=True):
if row and row[0] and row[1]:
data_sheet = str(row[0]).strip()
param_sheet = str(row[1]).strip()
# 检查是否是目标参数表
for target_sheet in self.target_sheets:
if target_sheet == param_sheet:
mapping[param_sheet] = {
"data_sheet": data_sheet,
"param_sheet": param_sheet
}
return mapping
def fill_sheet_parameters(self, source_param_sheet, source_data_sheet,
target_param_sheet, target_data_sheet):
"""回填单个参数表的数据"""
params_filled = 0
# 创建源参数映射: {参数名: (行, 列)}
source_param_map = {}
for row_idx, row in enumerate(source_param_sheet.iter_rows(), 1):
for col_idx, cell in enumerate(row, 1):
if cell.value and isinstance(cell.value, str):
param_name = cell.value.strip()
if param_name:
source_param_map[param_name] = (row_idx, col_idx)
# 遍历目标参数表
for row_idx, row in enumerate(target_param_sheet.iter_rows(), 1):
for col_idx, cell in enumerate(row, 1):
if cell.value and isinstance(cell.value, str):
param_name = cell.value.strip()
if param_name and param_name in source_param_map:
# 获取源数据位置
src_row, src_col = source_param_map[param_name]
# 获取源数据值
source_value = source_data_sheet.cell(
row=src_row,
column=src_col
).value
# 设置目标数据值
target_data_sheet.cell(
row=row_idx,
column=col_idx,
value=source_value
)
params_filled += 1
return params_filled
def copy_sheet_content(self, source_sheet, target_sheet):
"""复制工作表内容(格式和值)"""
# 复制行高
for idx, row_dim in source_sheet.row_dimensions.items():
target_sheet.row_dimensions[idx] = row_dim
# 复制列宽
for idx, col_dim in source_sheet.column_dimensions.items():
target_sheet.column_dimensions[idx] = col_dim
# 复制单元格值和格式
for row in source_sheet.iter_rows():
for cell in row:
new_cell = target_sheet.cell(
row=cell.row,
column=cell.column,
value=cell.value
)
# 复制样式
if cell.has_style:
new_cell.font = cell.font.copy()
new_cell.border = cell.border.copy()
new_cell.fill = cell.fill.copy()
new_cell.number_format = cell.number_format
new_cell.protection = cell.protection.copy()
new_cell.alignment = cell.alignment.copy()
# 合并单元格
for merge_range in source_sheet.merged_cells.ranges:
target_sheet.merge_cells(str(merge_range))
def save_log(self, file_path):
"""保存日志到文件"""
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write("\n".join(self.log_messages))
return True
except Exception as e:
print(f"保存日志失败: {str(e)}")
return False
class ExcelParamFillerApp:
def __init__(self, root):
self.root = root
self.root.title("Excel参数回填工具")
self.root.geometry("900x650")
self.filler = ExcelParamFiller()
# 创建样式
self.style = ttk.Style()
self.style.configure("TFrame", background="#f0f0f0")
self.style.configure("TLabel", background="#f0f0f0", font=("Arial", 10))
self.style.configure("TButton", font=("Arial", 10))
self.style.configure("Header.TLabel", font=("Arial", 14, "bold"))
self.style.configure("Success.TLabel", foreground="green", font=("Arial", 10, "bold"))
self.style.configure("Error.TLabel", foreground="red", font=("Arial", 10, "bold"))
# 创建主框架
self.main_frame = ttk.Frame(root, padding=20)
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 标题
header_frame = ttk.Frame(self.main_frame)
header_frame.pack(fill=tk.X, pady=10)
ttk.Label(header_frame, text="Excel参数回填工具", style="Header.TLabel").pack(side=tk.LEFT)
ttk.Label(header_frame, text="基于EasySuite模型参数", font=("Arial", 10)).pack(side=tk.LEFT, padx=10)
# 说明文本
instructions = ttk.Label(
self.main_frame,
text="此工具用于在两个Excel文件之间回填参数值\n"
"基于'EasySuite_Model_Index'索引表结构\n"
"处理EasySuite_Model@2到EasySuite_Model@6参数表",
font=("Arial", 10),
wraplength=800,
justify=tk.CENTER,
background="#e6f7ff",
padding=10
)
instructions.pack(fill=tk.X, pady=10)
# 创建输入框架
input_frame = ttk.LabelFrame(self.main_frame, text="文件选择", padding=10)
input_frame.pack(fill=tk.X, pady=10)
# 源Excel文件
file_row1 = ttk.Frame(input_frame)
file_row1.pack(fill=tk.X, pady=5)
ttk.Label(file_row1, text="源Excel文件:").pack(side=tk.LEFT, padx=5)
self.source_file_var = tk.StringVar()
source_file_entry = ttk.Entry(file_row1, textvariable=self.source_file_var, width=70)
source_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Button(file_row1, text="浏览...", command=self.browse_source_file, width=10).pack(side=tk.LEFT, padx=5)
# 目标Excel文件
file_row2 = ttk.Frame(input_frame)
file_row2.pack(fill=tk.X, pady=5)
ttk.Label(file_row2, text="目标Excel文件:").pack(side=tk.LEFT, padx=5)
self.target_file_var = tk.StringVar()
target_file_entry = ttk.Entry(file_row2, textvariable=self.target_file_var, width=70)
target_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Button(file_row2, text="浏览...", command=self.browse_target_file, width=10).pack(side=tk.LEFT, padx=5)
# 输出文件
file_row3 = ttk.Frame(input_frame)
file_row3.pack(fill=tk.X, pady=5)
ttk.Label(file_row3, text="输出文件:").pack(side=tk.LEFT, padx=5)
self.output_file_var = tk.StringVar()
output_file_entry = ttk.Entry(file_row3, textvariable=self.output_file_var, width=70)
output_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Button(file_row3, text="浏览...", command=self.browse_output_file, width=10).pack(side=tk.LEFT, padx=5)
# 按钮框架
button_frame = ttk.Frame(self.main_frame)
button_frame.pack(fill=tk.X, pady=20)
ttk.Button(button_frame, text="开始回填", command=self.start_filling, width=15).pack(side=tk.LEFT, padx=10)
ttk.Button(button_frame, text="保存日志", command=self.save_log, width=10).pack(side=tk.LEFT, padx=10)
ttk.Button(button_frame, text="清除日志", command=self.clear_log, width=10).pack(side=tk.LEFT, padx=10)
ttk.Button(button_frame, text="退出", command=root.quit, width=10).pack(side=tk.RIGHT, padx=10)
# 状态标签
self.status_var = tk.StringVar()
status_label = ttk.Label(button_frame, textvariable=self.status_var, style="Success.TLabel")
status_label.pack(side=tk.RIGHT, padx=20)
self.status_var.set("就绪")
# 日志框架
log_frame = ttk.LabelFrame(self.main_frame, text="处理日志", padding=10)
log_frame.pack(fill=tk.BOTH, expand=True, pady=10)
# 日志文本框
self.log_text = tk.Text(log_frame, wrap=tk.WORD, font=("Consolas", 10))
self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 添加滚动条
scrollbar = tk.Scrollbar(log_frame, command=self.log_text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.log_text.config(yscrollcommand=scrollbar.set)
# 重定向标准输出到日志文本框
sys.stdout = TextRedirector(self.log_text, self.status_var)
def browse_source_file(self):
"""浏览源Excel文件"""
file_path = filedialog.askopenfilename(
title="选择源Excel文件",
filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
self.source_file_var.set(file_path)
def browse_target_file(self):
"""浏览目标Excel文件"""
file_path = filedialog.askopenfilename(
title="选择目标Excel文件",
filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
self.target_file_var.set(file_path)
def browse_output_file(self):
"""浏览输出文件"""
file_path = filedialog.asksaveasfilename(
title="保存回填后的文件",
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
)
if file_path:
self.output_file_var.set(file_path)
def clear_log(self):
"""清除日志"""
self.log_text.config(state=tk.NORMAL)
self.log_text.delete(1.0, tk.END)
self.log_text.config(state=tk.DISABLED)
self.filler.log_messages = []
self.status_var.set("日志已清除")
def start_filling(self):
"""开始回填过程"""
# 检查所有文件路径是否已设置
if not all([
self.source_file_var.get(),
self.target_file_var.get(),
self.output_file_var.get()
]):
messagebox.showerror("错误", "请填写所有文件路径")
return
# 检查文件是否存在
if not os.path.exists(self.source_file_var.get()):
messagebox.showerror("错误", f"源文件不存在: {self.source_file_var.get()}")
return
if not os.path.exists(self.target_file_var.get()):
messagebox.showerror("错误", f"目标文件不存在: {self.target_file_var.get()}")
return
# 更新状态
self.status_var.set("处理中...")
self.root.update()
# 清空日志
self.log_text.config(state=tk.NORMAL)
self.log_text.delete(1.0, tk.END)
self.log_text.config(state=tk.DISABLED)
self.filler.log_messages = []
# 执行回填
success = self.filler.fill_parameters(
self.source_file_var.get(),
self.target_file_var.get(),
self.output_file_var.get()
)
# 更新日志显示
self.log_text.config(state=tk.NORMAL)
for message in self.filler.log_messages:
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
# 更新状态
if success:
self.status_var.set("回填成功!")
messagebox.showinfo("成功", "参数回填完成!")
# 询问是否打开输出文件
if messagebox.askyesno("成功", "回填完成,是否打开输出文件?"):
try:
os.startfile(self.output_file_var.get())
except:
messagebox.showinfo("信息", f"文件已保存至: {self.output_file_var.get()}")
else:
self.status_var.set("回填失败")
messagebox.showerror("错误", "回填过程中出错,请查看日志")
def save_log(self):
"""保存日志到文件"""
if not self.filler.log_messages:
messagebox.showinfo("提示", "没有日志可保存")
return
file_path = filedialog.asksaveasfilename(
title="保存日志文件",
defaultextension=".log",
filetypes=[("日志文件", "*.log"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if file_path:
if self.filler.save_log(file_path):
messagebox.showinfo("成功", f"日志已保存到: {file_path}")
else:
messagebox.showerror("错误", "保存日志失败")
class TextRedirector:
"""重定向标准输出到文本组件和状态标签"""
def __init__(self, widget, status_var=None):
self.widget = widget
self.status_var = status_var
def write(self, text):
# 更新日志文本框
self.widget.config(state=tk.NORMAL)
self.widget.insert(tk.END, text)
self.widget.see(tk.END)
self.widget.config(state=tk.DISABLED)
# 更新状态标签
if self.status_var:
# 只更新状态消息(以特定前缀开头的消息)
if text.startswith("状态:"):
self.status_var.set(text.replace("状态:", "").strip())
def flush(self):
pass
if __name__ == "__main__":
root = tk.Tk()
app = ExcelParamFillerApp(root)
root.mainloop()