经常匹配数据,又想根据模板输出,故写这篇笔记。
加入有两份数据和一个模板文件,如下:


模板数据只有表头

运行界面和效果如下:


一、工具核心功能与整体架构
1. 核心功能概述
编写的这个工具是一个可视化的 Excel 数据处理应用,主要实现三大核心功能:
- 加载多个 Excel 文件(左表、右表、模板表)
- 基于指定字段将左表和右表进行数据连接(类似数据库的 JOIN 操作)
- 根据模板表的字段结构,将连接后的数据按映射关系填充到模板中,并导出结果
2. 整体架构设计
工具采用面向对象(OOP) 设计,核心类为ExcelMatcherApp,整体架构遵循 MVC 思想(模型 - 视图 - 控制器):
- 模型层:由 Pandas 的 DataFrame 对象管理 Excel 数据(left_df、right_df、template_df 等)
- 视图层:由 Tkinter 组件构建可视化界面(按钮、列表框、文本框等)
- 控制器层:各类方法处理用户交互(文件选择、数据连接、字段映射、结果导出等)
二、关键技术点详解
1. 环境依赖与初始化
工具依赖三个核心库,需提前安装:
python
pip install tkinter pandas openpyxl
tkinter:Python 内置的 GUI 库,用于构建可视化界面pandas:数据处理核心库,用于 Excel 读写和数据操作openpyxl:Pandas 读写 xlsx 文件的依赖引擎
类初始化方法__init__
python
def __init__(self, root):
self.root = root
self.root.title("Excel数据匹配工具")
self.root.geometry("1400x900")
# 初始化数据存储变量
self.left_df = None # 左表数据
self.right_df = None # 右表数据
self.template_df = None # 模板表数据
self.merged_df = None # 连接后的数据
self.result_df = None # 最终结果数据
# 初始化列名列表
self.left_columns = []
self.right_columns = []
self.template_columns = []
# 初始化界面变量
self.merge_key_left = tk.StringVar()
self.merge_key_right = tk.StringVar()
self.merge_type = tk.StringVar(value="inner")
# 字段映射字典 {模板字段: 连接后字段}
self.field_mapping = {}
self.setup_ui() # 构建界面
关键说明:
- 所有数据存储变量初始化为
None,确保后续加载数据时能正确判断状态 StringVar是 Tkinter 的变量类型,用于绑定界面组件的值(如连接方式下拉框)field_mapping字典是字段映射的核心存储结构,键为模板字段,值为连接后字段
2. 界面构建(setup_ui 方法)
这是工具的 "骨架",负责创建所有可视化组件,核心设计思路:
(1)布局管理
采用grid布局(网格布局),通过columnconfigure和rowconfigure设置权重,实现界面自适应:
python
self.root.columnconfigure(0, weight=1) # 列0随窗口拉伸
self.root.rowconfigure(0, weight=1) # 行0随窗口拉伸
weight=1表示该列 / 行会占用窗口的剩余空间,实现界面元素的自适应缩放
(2)界面组件分区
| 组件区域 | 核心组件 | 功能 |
|---|---|---|
| 文件选择区 | Entry + Button | 显示文件路径,触发文件选择对话框 |
| 表连接设置区 | Combobox + Button | 选择连接字段和连接方式,执行数据连接 |
| 字段映射设置区 | Listbox + Scrollbar + Button | 展示模板字段和连接后字段,管理映射关系 |
| 结果操作区 | Button | 生成结果、导出结果 |
| 结果展示区 | Text + Scrollbar | 显示操作日志和数据预览 |
3. Excel 文件加载与解析
以左表加载为例,核心方法select_left_file和load_left_file:
python
def select_left_file(self):
file_path = filedialog.askopenfilename(
title="选择左表",
filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
self.left_path_var.set(file_path)
self.load_left_file(file_path)
def load_left_file(self, file_path):
try:
self.left_df = pd.read_excel(file_path) # 读取Excel文件
self.left_columns = list(self.left_df.columns) # 获取列名列表
self.left_key_combo['values'] = self.left_columns # 更新下拉框选项
if self.left_columns:
self.left_key_combo.current(0) # 默认选中第一个列
messagebox.showinfo("成功", f"左表加载成功,共{len(self.left_df)}行,{len(self.left_columns)}列")
except Exception as e:
messagebox.showerror("错误", f"加载左表失败: {str(e)}")
关键技术点:
filedialog.askopenfilename:Tkinter 文件选择对话框,返回用户选择的文件路径pd.read_excel:Pandas 读取 Excel 文件,返回 DataFrame 对象(支持.xlsx/.xls 格式)- 异常处理
try-except:捕获文件读取错误(如文件损坏、路径错误),提升用户体验 messagebox:Tkinter 消息框,用于提示操作结果
4. 数据连接(表合并)核心实现
execute_merge方法是工具的核心逻辑之一,实现类似 SQL 的 JOIN 操作:
python
def execute_merge(self):
if self.left_df is None or self.right_df is None:
messagebox.showwarning("警告", "请先加载左表和右表")
return
left_key = self.left_key_combo.get()
right_key = self.right_key_combo.get()
if not left_key or not right_key:
messagebox.showwarning("警告", "请选择连接字段")
return
try:
merge_type = self.merge_type.get()
self.merged_df = pd.merge(
self.left_df,
self.right_df,
left_on=left_key,
right_on=right_key,
how=merge_type,
suffixes=('_left', '_right')
)
# 后续更新界面逻辑...
except Exception as e:
messagebox.showerror("错误", f"连接失败: {str(e)}")
Pandas 合并原理详解
pd.merge是 Pandas 实现数据连接的核心函数,关键参数说明:
| 参数 | 作用 |
|---|---|
left/right |
要合并的两个 DataFrame(左表 / 右表) |
left_on/right_on |
左表 / 右表用于连接的字段名 |
how |
连接方式:inner(内连接)、left(左连接)、right(右连接)、outer(外连接) |
suffixes |
字段名重复时的后缀(如左表的 name 字段变为 name_left) |
四种连接方式对比:
inner:只保留两个表中连接字段匹配的行(交集)left:保留左表所有行,右表匹配不到的字段填充 NaNright:保留右表所有行,左表匹配不到的字段填充 NaNouter:保留两个表的所有行(并集),匹配不到的字段填充 NaN
5. 字段映射管理
字段映射是连接后数据与模板表的桥梁,核心方法包括add_mapping、remove_mapping、clear_mappings:
python
def add_mapping(self):
template_indices = self.template_listbox.curselection()
merged_indices = self.merged_listbox.curselection()
if len(template_indices) == 0 or len(merged_indices) == 0:
messagebox.showwarning("警告", "请分别选择模板字段和连接后字段")
return
if self.merged_df is None:
messagebox.showwarning("警告", "请先执行表连接")
return
template_col = self.template_columns[template_indices[0]]
merged_col = list(self.merged_df.columns)[merged_indices[0]]
self.field_mapping[template_col] = merged_col
self.display_mappings()
messagebox.showinfo("成功", f"已添加映射: {template_col} <- {merged_col}")
关键技术点:
curselection():获取 Listbox 中用户选中的项的索引field_mapping字典:存储映射关系,键为模板字段,值为连接后字段display_mappings():更新 Text 组件,展示当前所有映射关系
6. 结果生成与数据填充
generate_result方法实现核心的数据填充逻辑,将连接后的数据按映射关系填充到模板表中:
python
def generate_result(self):
# 前置检查...
try:
template_rows = len(self.template_df)
merged_rows = len(self.merged_df)
# 初始化结果DataFrame
if template_rows == 0:
self.result_df = pd.DataFrame(columns=self.template_df.columns)
for i in range(merged_rows):
self.result_df.loc[i] = [''] * len(self.template_df.columns)
else:
self.result_df = self.template_df.copy()
# 遍历映射关系,填充数据
for template_col, merged_col in self.field_mapping.items():
if merged_col in self.merged_df.columns:
data_values = self.merged_df[merged_col].values
result_rows = len(self.result_df)
# 处理数据长度不一致的情况
if template_rows == 0:
filled_values = data_values[:merged_rows]
filled_rows = min(merged_rows, len(data_values))
elif template_rows <= merged_rows:
filled_values = data_values[:template_rows]
filled_rows = template_rows
else:
filled_values = list(data_values) + [''] * (template_rows - len(data_values))
filled_rows = len(data_values)
self.result_df[template_col] = filled_values
# 后续展示预览逻辑...
except Exception as e:
messagebox.showerror("错误", f"生成结果失败: {str(e)}")
核心逻辑解析:
- 初始化结果 DataFrame:如果模板表为空,则创建与模板字段结构一致的空表;否则复制模板表
- 遍历字段映射关系,获取连接后字段的数值
- 处理数据长度不一致问题:
- 模板表行数 < 连接后数据行数:只取前 N 行(N 为模板表行数)
- 模板表行数 > 连接后数据行数:不足部分填充空字符串
- 将处理后的数据赋值给结果 DataFrame 的对应字段
7. 结果导出
export_result方法将最终的结果 DataFrame 保存为 Excel 文件:
python
def export_result(self):
if self.result_df is None:
messagebox.showwarning("警告", "请先生成结果")
return
file_path = filedialog.asksaveasfilename(
title="保存结果",
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
)
if file_path:
try:
self.result_df.to_excel(file_path, index=False) # index=False不保存行索引
messagebox.showinfo("成功", f"结果已导出到: {file_path}")
except Exception as e:
messagebox.showerror("错误", f"导出失败: {str(e)}")
关键参数:
filedialog.asksaveasfilename:保存文件对话框,返回用户选择的保存路径to_excel的index=False:避免将 Pandas 的行索引保存到 Excel 中(提升文件可读性)
三、关键优化点与扩展建议
1. 当前代码的优化空间
- 性能优化:大数据量下(如 10 万行 +),Pandas 操作可能卡顿,可添加进度条
- 数据校验:增加字段类型校验、空值处理逻辑
- 界面优化:添加数据预览表格(使用 ttk.Treeview),替代纯文本展示
- 功能扩展:支持多字段连接、批量映射、数据清洗(去重、格式转换)
2. 常见问题解决方案
- Excel 读取失败:确保安装 openpyxl(xlsx)和 xlrd(xls)库
- 中文乱码 :读取时指定编码
pd.read_excel(file_path, encoding='utf-8') - 数据连接后字段重复 :通过
suffixes参数区分,或手动重命名 - 导出文件损坏:确保保存路径无特殊字符,关闭已打开的目标 Excel 文件
四、完整代码
python
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
from pathlib import Path
class ExcelMatcherApp:
def __init__(self, root):
self.root = root
self.root.title("Excel数据匹配工具")
self.root.geometry("1400x900")
self.left_df = None
self.right_df = None
self.template_df = None
self.merged_df = None
self.result_df = None
self.left_columns = []
self.right_columns = []
self.template_columns = []
self.merge_key_left = tk.StringVar()
self.merge_key_right = tk.StringVar()
self.merge_type = tk.StringVar(value="inner")
self.field_mapping = {}
self.setup_ui()
def setup_ui(self):
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(3, weight=1)
ttk.Label(main_frame, text="Excel数据匹配工具", font=("Arial", 16, "bold")).grid(row=0, column=0, columnspan=3, pady=(0, 20))
file_frame = ttk.Frame(main_frame)
file_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
ttk.Label(file_frame, text="左表:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.left_path_var = tk.StringVar()
ttk.Entry(file_frame, textvariable=self.left_path_var, width=50).grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
ttk.Button(file_frame, text="选择文件", command=self.select_left_file).grid(row=0, column=2, padx=5, pady=5)
ttk.Label(file_frame, text="右表:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.right_path_var = tk.StringVar()
ttk.Entry(file_frame, textvariable=self.right_path_var, width=50).grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)
ttk.Button(file_frame, text="选择文件", command=self.select_right_file).grid(row=1, column=2, padx=5, pady=5)
ttk.Label(file_frame, text="模板表:").grid(row=2, column=0, sticky=tk.W, pady=5)
self.template_path_var = tk.StringVar()
ttk.Entry(file_frame, textvariable=self.template_path_var, width=50).grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5)
ttk.Button(file_frame, text="选择文件", command=self.select_template_file).grid(row=2, column=2, padx=5, pady=5)
file_frame.columnconfigure(1, weight=1)
merge_frame = ttk.LabelFrame(main_frame, text="表连接设置", padding="10")
merge_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
ttk.Label(merge_frame, text="左表连接字段:").grid(row=0, column=0, sticky=tk.W, padx=5)
self.left_key_combo = ttk.Combobox(merge_frame, state="readonly", width=30)
self.left_key_combo.grid(row=0, column=1, padx=5, pady=5)
ttk.Label(merge_frame, text="右表连接字段:").grid(row=0, column=2, sticky=tk.W, padx=5)
self.right_key_combo = ttk.Combobox(merge_frame, state="readonly", width=30)
self.right_key_combo.grid(row=0, column=3, padx=5, pady=5)
ttk.Label(merge_frame, text="连接方式:").grid(row=1, column=0, sticky=tk.W, padx=5)
merge_type_combo = ttk.Combobox(merge_frame, textvariable=self.merge_type, state="readonly", width=28)
merge_type_combo['values'] = ('inner', 'left', 'right', 'outer')
merge_type_combo.grid(row=1, column=1, padx=5, pady=5)
ttk.Button(merge_frame, text="执行连接", command=self.execute_merge).grid(row=1, column=2, columnspan=2, padx=5, pady=5)
mapping_frame = ttk.LabelFrame(main_frame, text="字段映射设置", padding="10")
mapping_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
mapping_frame.columnconfigure(0, weight=1)
mapping_frame.columnconfigure(1, weight=1)
mapping_frame.rowconfigure(1, weight=1)
ttk.Label(mapping_frame, text="模板字段", font=("Arial", 11, "bold")).grid(row=0, column=0, pady=5)
ttk.Label(mapping_frame, text="连接后字段", font=("Arial", 11, "bold")).grid(row=0, column=1, pady=5)
template_frame = ttk.Frame(mapping_frame)
template_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
self.template_listbox = tk.Listbox(template_frame, selectmode=tk.SINGLE, height=15, exportselection=False)
self.template_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
template_scrollbar = ttk.Scrollbar(template_frame, orient=tk.VERTICAL, command=self.template_listbox.yview)
template_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.template_listbox.config(yscrollcommand=template_scrollbar.set)
merged_frame = ttk.Frame(mapping_frame)
merged_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 0))
self.merged_listbox = tk.Listbox(merged_frame, selectmode=tk.SINGLE, height=15, exportselection=False)
self.merged_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
merged_scrollbar = ttk.Scrollbar(merged_frame, orient=tk.VERTICAL, command=self.merged_listbox.yview)
merged_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.merged_listbox.config(yscrollcommand=merged_scrollbar.set)
mapping_button_frame = ttk.Frame(mapping_frame)
mapping_button_frame.grid(row=2, column=0, columnspan=2, pady=10)
ttk.Button(mapping_button_frame, text="添加映射 ->", command=self.add_mapping, width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(mapping_button_frame, text="<- 删除映射", command=self.remove_mapping, width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(mapping_button_frame, text="清除所有映射", command=self.clear_mappings, width=15).pack(side=tk.LEFT, padx=5)
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=4, column=0, columnspan=3, pady=10)
ttk.Button(button_frame, text="生成结果", command=self.generate_result, width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="导出结果", command=self.export_result, width=15).pack(side=tk.LEFT, padx=5)
self.result_text = tk.Text(main_frame, height=20, width=100, wrap=tk.WORD)
self.result_text.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
result_scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.result_text.yview)
result_scrollbar.grid(row=5, column=3, sticky=(tk.N, tk.S))
self.result_text.config(yscrollcommand=result_scrollbar.set)
main_frame.rowconfigure(5, weight=1)
def select_left_file(self):
file_path = filedialog.askopenfilename(
title="选择左表",
filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
self.left_path_var.set(file_path)
self.load_left_file(file_path)
def select_right_file(self):
file_path = filedialog.askopenfilename(
title="选择右表",
filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
self.right_path_var.set(file_path)
self.load_right_file(file_path)
def select_template_file(self):
file_path = filedialog.askopenfilename(
title="选择模板表",
filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
self.template_path_var.set(file_path)
self.load_template_file(file_path)
def load_left_file(self, file_path):
try:
self.left_df = pd.read_excel(file_path)
self.left_columns = list(self.left_df.columns)
self.left_key_combo['values'] = self.left_columns
if self.left_columns:
self.left_key_combo.current(0)
messagebox.showinfo("成功", f"左表加载成功,共{len(self.left_df)}行,{len(self.left_columns)}列")
except Exception as e:
messagebox.showerror("错误", f"加载左表失败: {str(e)}")
def load_right_file(self, file_path):
try:
self.right_df = pd.read_excel(file_path)
self.right_columns = list(self.right_df.columns)
self.right_key_combo['values'] = self.right_columns
if self.right_columns:
self.right_key_combo.current(0)
messagebox.showinfo("成功", f"右表加载成功,共{len(self.right_df)}行,{len(self.right_columns)}列")
except Exception as e:
messagebox.showerror("错误", f"加载右表失败: {str(e)}")
def load_template_file(self, file_path):
try:
self.template_df = pd.read_excel(file_path)
self.template_columns = list(self.template_df.columns)
self.template_listbox.delete(0, tk.END)
for col in self.template_columns:
self.template_listbox.insert(tk.END, col)
messagebox.showinfo("成功", f"模板表加载成功,共{len(self.template_df)}行,{len(self.template_columns)}列")
except Exception as e:
messagebox.showerror("错误", f"加载模板表失败: {str(e)}")
def execute_merge(self):
if self.left_df is None or self.right_df is None:
messagebox.showwarning("警告", "请先加载左表和右表")
return
left_key = self.left_key_combo.get()
right_key = self.right_key_combo.get()
if not left_key or not right_key:
messagebox.showwarning("警告", "请选择连接字段")
return
try:
merge_type = self.merge_type.get()
self.merged_df = pd.merge(
self.left_df,
self.right_df,
left_on=left_key,
right_on=right_key,
how=merge_type,
suffixes=('_left', '_right')
)
merged_columns = list(self.merged_df.columns)
self.merged_listbox.delete(0, tk.END)
for col in merged_columns:
self.merged_listbox.insert(tk.END, col)
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, f"连接成功!\n")
self.result_text.insert(tk.END, f"连接方式: {merge_type}\n")
self.result_text.insert(tk.END, f"左表连接字段: {left_key}\n")
self.result_text.insert(tk.END, f"右表连接字段: {right_key}\n")
self.result_text.insert(tk.END, f"结果行数: {len(self.merged_df)}\n")
self.result_text.insert(tk.END, f"结果列数: {len(merged_columns)}\n")
self.result_text.insert(tk.END, f"\n连接后字段列表:\n")
for col in merged_columns:
self.result_text.insert(tk.END, f" - {col}\n")
messagebox.showinfo("成功", f"连接成功,共{len(self.merged_df)}行,{len(merged_columns)}列")
except Exception as e:
messagebox.showerror("错误", f"连接失败: {str(e)}")
def add_mapping(self):
template_indices = self.template_listbox.curselection()
merged_indices = self.merged_listbox.curselection()
if len(template_indices) == 0 or len(merged_indices) == 0:
messagebox.showwarning("警告", "请分别选择模板字段和连接后字段")
return
if self.merged_df is None:
messagebox.showwarning("警告", "请先执行表连接")
return
template_col = self.template_columns[template_indices[0]]
merged_col = list(self.merged_df.columns)[merged_indices[0]]
self.field_mapping[template_col] = merged_col
self.display_mappings()
messagebox.showinfo("成功", f"已添加映射: {template_col} <- {merged_col}")
def remove_mapping(self):
template_indices = self.template_listbox.curselection()
if len(template_indices) == 0:
messagebox.showwarning("警告", "请选择要删除的模板字段")
return
template_col = self.template_columns[template_indices[0]]
if template_col in self.field_mapping:
del self.field_mapping[template_col]
self.display_mappings()
messagebox.showinfo("成功", f"已删除映射: {template_col}")
else:
messagebox.showwarning("警告", "该字段尚未映射")
def clear_mappings(self):
self.field_mapping.clear()
self.result_text.delete(1.0, tk.END)
messagebox.showinfo("成功", "已清除所有映射")
def display_mappings(self):
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, "字段映射关系:\n")
self.result_text.insert(tk.END, "=" * 70 + "\n")
for template_col, merged_col in self.field_mapping.items():
self.result_text.insert(tk.END, f"{template_col} <- {merged_col}\n")
def generate_result(self):
if self.template_df is None:
messagebox.showwarning("警告", "请先加载模板表")
return
if self.merged_df is None:
messagebox.showwarning("警告", "请先执行表连接")
return
if not self.field_mapping:
messagebox.showwarning("警告", "请先设置字段映射")
return
try:
template_rows = len(self.template_df)
merged_rows = len(self.merged_df)
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, "字段映射详情:\n")
self.result_text.insert(tk.END, "=" * 70 + "\n")
self.result_text.insert(tk.END, f"模板行数: {template_rows}\n")
self.result_text.insert(tk.END, f"连接后数据行数: {merged_rows}\n")
self.result_text.insert(tk.END, "=" * 70 + "\n\n")
if template_rows == 0:
self.result_text.insert(tk.END, "模板表为空,根据连接后数据创建新行\n")
self.result_text.insert(tk.END, "=" * 70 + "\n\n")
self.result_df = pd.DataFrame(columns=self.template_df.columns)
for i in range(merged_rows):
self.result_df.loc[i] = [''] * len(self.template_df.columns)
else:
self.result_df = self.template_df.copy()
for template_col, merged_col in self.field_mapping.items():
self.result_text.insert(tk.END, f"模板字段: {template_col}\n")
self.result_text.insert(tk.END, f" <- 连接后字段: {merged_col}\n")
if merged_col in self.merged_df.columns:
data_values = self.merged_df[merged_col].values
result_rows = len(self.result_df)
if template_rows == 0:
filled_values = data_values[:merged_rows]
filled_rows = min(merged_rows, len(data_values))
elif template_rows <= merged_rows:
filled_values = data_values[:template_rows]
filled_rows = template_rows
else:
filled_values = list(data_values) + [''] * (template_rows - len(data_values))
filled_rows = len(data_values)
self.result_df[template_col] = filled_values
self.result_text.insert(tk.END, f" -> 填充行数: {filled_rows}/{result_rows}\n")
self.result_text.insert(tk.END, f" -> 状态: 成功\n")
else:
self.result_text.insert(tk.END, f" -> 状态: 失败 - 字段不存在\n")
self.result_text.insert(tk.END, "-" * 70 + "\n")
self.result_text.insert(tk.END, f"\n数据预览 (前10行):\n")
self.result_text.insert(tk.END, "=" * 70 + "\n")
self.result_text.insert(tk.END, self.result_df.head(10).to_string())
messagebox.showinfo("成功", f"结果生成成功,共{len(self.result_df)}行")
except Exception as e:
messagebox.showerror("错误", f"生成结果失败: {str(e)}")
def export_result(self):
if self.result_df is None:
messagebox.showwarning("警告", "请先生成结果")
return
file_path = filedialog.asksaveasfilename(
title="保存结果",
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
)
if file_path:
try:
self.result_df.to_excel(file_path, index=False)
messagebox.showinfo("成功", f"结果已导出到: {file_path}")
except Exception as e:
messagebox.showerror("错误", f"导出失败: {str(e)}")
def main():
root = tk.Tk()
app = ExcelMatcherApp(root)
root.mainloop()
if __name__ == "__main__":
main()