Excel 数据匹配工具 -笔记

经常匹配数据,又想根据模板输出,故写这篇笔记。

加入有两份数据和一个模板文件,如下:

模板数据只有表头

运行界面和效果如下:

一、工具核心功能与整体架构

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布局(网格布局),通过columnconfigurerowconfigure设置权重,实现界面自适应:

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_fileload_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:保留左表所有行,右表匹配不到的字段填充 NaN
  • right:保留右表所有行,左表匹配不到的字段填充 NaN
  • outer:保留两个表的所有行(并集),匹配不到的字段填充 NaN

5. 字段映射管理

字段映射是连接后数据与模板表的桥梁,核心方法包括add_mappingremove_mappingclear_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)}")

核心逻辑解析

  1. 初始化结果 DataFrame:如果模板表为空,则创建与模板字段结构一致的空表;否则复制模板表
  2. 遍历字段映射关系,获取连接后字段的数值
  3. 处理数据长度不一致问题:
    • 模板表行数 < 连接后数据行数:只取前 N 行(N 为模板表行数)
    • 模板表行数 > 连接后数据行数:不足部分填充空字符串
  4. 将处理后的数据赋值给结果 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_excelindex=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()
相关推荐
朔北之忘 Clancy2 小时前
2020 年 6 月青少年软编等考 C 语言二级真题解析
c语言·开发语言·c++·学习·青少年编程·题解·尺取法
知识分享小能手2 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04 中安装 Docker 容器 —— 知识点详解(26)
学习·ubuntu·docker
数据轨迹0012 小时前
CVPR Efficient ViM:视觉 Mamba 的轻量化
经验分享·笔记·facebook·oneapi·twitter
其美杰布-富贵-李2 小时前
PyTorch Lightning
人工智能·pytorch·python·training
开开心心_Every2 小时前
安卓做菜APP:家常菜谱详细步骤无广简洁
服务器·前端·python·学习·edge·django·powerpoint
SiYuanFeng2 小时前
pytorch常用张量构造词句表和nn.组件速查表
人工智能·pytorch·python
MistaCloud2 小时前
Pytorch深入浅出(十四)之完整的模型训练测试套路
人工智能·pytorch·python·深度学习
知乎的哥廷根数学学派2 小时前
基于物理信息嵌入与多维度约束的深度学习地基承载力智能预测与可解释性评估算法(以模拟信号为例,Pytorch)
人工智能·pytorch·python·深度学习·算法·机器学习
wdfk_prog2 小时前
WIN11如何可以安装ISO
linux·笔记·学习