吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 33|回复: 0
收起左侧

[原创工具] 通用Excel模板批量填充工具

[复制链接]
bigcarp 发表于 2026-2-4 20:57
本帖最后由 bigcarp 于 2026-2-4 21:32 编辑

一个小工具

操作界面截图

操作界面截图

使用说明

使用说明
测试_模板及数据.zip (20.8 KB, 下载次数: 4)


下载链接 https://wwati.lanzouu.com/irCEb3hs2z7i
密码:52pj

-  工具简介
你是否遇到过这样的场景:
手里有一份上百人的工资表、学生成绩单或客户对账清单,需要把每个人的数据填入到一个特定的 Excel 模板里,并保存为独立的 Sheet 或文件?手动复制粘贴不仅效率低,而且极易出错。

这款通用Excel模板批量填充工具就是为了解决这个问题而生的。

它基于 Python 开发,采用“所见即所得”的替换逻辑,只要你会写双大括号 {{}},就能轻松上手。

-  典型应用场景
  • 人力资源(HR):从员工花名册批量生成“个人调薪通知单”、“离职证明”或“月度工资条”。
  • 教育/培训:从成绩汇总表批量生成每个学生的“成绩报告单”或“综合素质评价表”。
  • 财务/商务:从欠款清单批量生成“客户对账单”、“催款通知书”或“入库单”。
  • 行政/文员:从设备清单批量生成“资产卡片”或“设备巡检记录表”。
  • 项目管理:根据任务清单批量生成“周报/日报模板”。

-  
功能亮点
  • 零学习成本:无需编写宏(VBA),只需在模板单元格中填写 {{列名}} 即可完成绑定。
  • 完美格式保留:深度复制原模板的单元格样式(字体、颜色、边框、背景、合并单元格、行高列宽等)。
  • 自动命名:默认将数据表的第一列作为生成的 Sheet 名称,方便检索。
  • 进度实时反馈:带进度条显示,处理上千条数据时也能清晰掌握进度。
  • 一键打包:支持通过 PyInstaller 打包成单文件 .exe,绿色免安装。

-  使用说明(三步走)
  • 准备模板:在你的 Excel 模板里,把需要填数据的地方写成 {{姓名}}、{{日期}} 等。
  • 准备数据:准备一个 Excel 表,第一行表头包含“姓名”、“日期”等,下方是具体数据。
  • 开始生成:运行工具,选好文件,点击“开始”,稍等片刻即可得到结果。
-  技术栈
  • 语言:Python 3.x
  • 核心库:pandas (数据处理), openpyxl (Excel操作), Tkinter (GUI界面)

希望这个工具能帮大家准时下班!欢迎测试反馈。



[Python] 纯文本查看 复制代码
import pandas as pd
import openpyxl
from openpyxl.utils import get_column_letter
from copy import copy
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk
import os
import sys
import time
import threading
import subprocess


def resource_path(relative_path):
    """ 获取资源绝对路径,兼容开发环境和 PyInstaller 打包环境 """
    try:
        # PyInstaller 创建临时文件夹并存储路径在 _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)


class GeneralExcelGenerator:
    def __init__(self, root):
        self.root = root
        self.root.title("通用Excel模板批量填充工具")
        self.root.geometry("1000x800")  # 调高窗口以容纳图片

        self.template_path = tk.StringVar()
        self.data_path = tk.StringVar()
        self.output_path = tk.StringVar()

        # 缓存原图用于弹出预览
        self.full_image = None

        self.setup_ui()

    def setup_ui(self):
        # 创建 Notebook (标签页控件)
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill="both", expand=True, padx=10, pady=10)

        # 创建两个 Tab
        self.tab_operate = ttk.Frame(self.notebook)
        self.tab_manual = ttk.Frame(self.notebook)

        self.notebook.add(self.tab_operate, text="  操作界面  ")
        self.notebook.add(self.tab_manual, text="  使用说明  ")

        self.create_operate_widgets()
        self.create_manual_widgets()

    def create_operate_widgets(self):
        """第一个Tab:核心操作区域"""
        style = ttk.Style()
        style.configure("Accent.TButton", font=("微软雅黑", 11, "bold"))

        # 1. 模板配置
        frame1 = ttk.LabelFrame(self.tab_operate, text="Step 1: 模板配置", padding="10")
        frame1.pack(fill="x", padx=20, pady=10)
        ttk.Entry(frame1, textvariable=self.template_path, width=70).grid(row=0, column=0, padx=5)
        ttk.Button(frame1, text="选择模板", command=self.select_template).grid(row=0, column=1, padx=5)

        # 2. 数据源配置
        frame2 = ttk.LabelFrame(self.tab_operate, text="Step 2: 数据源配置", padding="10")
        frame2.pack(fill="x", padx=20, pady=10)
        ttk.Entry(frame2, textvariable=self.data_path, width=70).grid(row=0, column=0, padx=5)
        ttk.Button(frame2, text="选择数据", command=self.select_data).grid(row=0, column=1, padx=5)

        # 3. 输出路径
        frame3 = ttk.LabelFrame(self.tab_operate, text="Step 3: 保存路径", padding="10")
        frame3.pack(fill="x", padx=20, pady=10)
        ttk.Entry(frame3, textvariable=self.output_path, width=70).grid(row=0, column=0, padx=5)

        # 4. 执行按钮
        btn_frame = ttk.Frame(self.tab_operate)
        btn_frame.pack(pady=15)
        self.generate_btn = ttk.Button(
            btn_frame, text="开始批量填充生成",
            command=self.start_generate_thread,
            style="Accent.TButton", width=25
        )
        self.generate_btn.pack()

        # 5. 进度反馈
        self.progress_bar = ttk.Progressbar(self.tab_operate, orient="horizontal", length=800, mode="determinate")
        self.progress_bar.pack(pady=10)

        self.progress_var = tk.StringVar(value="准备就绪")
        ttk.Label(self.tab_operate, textvariable=self.progress_var, font=("微软雅黑", 10), foreground="#2E8B57").pack()

    def create_manual_widgets(self):
        """第二个Tab:带说明文字和图片预览"""
        paned = tk.PanedWindow(self.tab_manual, orient=tk.VERTICAL)
        paned.pack(fill="both", expand=True)

        # 上半部分:文字说明区域
        text_frame = ttk.Frame(paned)
        paned.add(text_frame, height=300)

        scrollbar = ttk.Scrollbar(text_frame)
        scrollbar.pack(side=tk.RIGHT, fill="y")

        manual_text = (
            "【通用Excel模板批量填充工具 - 使用手册】\n\n"
            "1. 工作原理:\n"
            "   本工具通过读取【数据文件】(Excel)中的每一行信息,将其填充到【模板文件】(Excel)\n"
            "   并为每一行数据生成一个独立的 Sheet(工作表)。\n\n"
            "2. 模板准备:\n"
            "   - 在 Excel 模板的单元格中输入 {{字段名}}(注意是双大括号)。\n"
            "   - 这里的“字段名”必须与数据文件中的“列标题”完全一致。\n\n"
            "3. 数据源准备:\n"
            "   - 数据文件第一行应为表头(列名)。\n"
            "   - 【第一列】的值将被用作生成后 Sheet 的名称。\n\n"
            "4. 注意事项:\n"
            "   - 生成过程中请勿打开正在使用的模板文件或目标输出文件。\n"
            "   - (提示:点击下方图片可查看原图解)\n"
        )

        self.text_area = tk.Text(
            text_frame,
            wrap=tk.WORD,
            font=("微软雅黑", 10),
            yscrollcommand=scrollbar.set,
            padx=20,
            pady=10,
            bg="#F9F9F9"
        )
        self.text_area.insert(tk.END, manual_text)
        self.text_area.config(state=tk.DISABLED)
        self.text_area.pack(fill="both", expand=True)
        scrollbar.config(command=self.text_area.yview)

        # 下半部分:图片说明区域
        img_frame = ttk.LabelFrame(paned, text="操作图解 (点击图片查看原图)")
        paned.add(img_frame)

        self.img_label = ttk.Label(img_frame, text="正在加载说明图片...", cursor="hand2")
        self.img_label.pack(fill="both", expand=True, padx=10, pady=10)

        # 绑定点击事件
        self.img_label.bind("<Button-1>", lambda e: self.show_full_image())

        self.load_manual_image()

    def load_manual_image(self):
        """加载 help2.png"""
        try:
            img_path = resource_path("help2.png")
            if os.path.exists(img_path):
                self.full_image = Image.open(img_path)
                display_img = self.full_image.copy()
                display_img.thumbnail((900, 400))
                self.photo = ImageTk.PhotoImage(display_img)
                self.img_label.config(image=self.photo, text="")
            else:
                self.img_label.config(text=f"未找到说明图片: help2.png\n请确保打包或运行目录下存在该图片。")
        except Exception as e:
            self.img_label.config(text=f"图片加载失败: {str(e)}")

    def show_full_image(self):
        """弹出大图预览窗并最大化"""
        if self.full_image is None:
            return

        top = tk.Toplevel(self.root)
        top.title("操作图解预览")

        try:
            top.state('zoomed')  # 最大化
        except:
            top.attributes("-fullscreen", True)

        img_w, img_h = self.full_image.size

        canvas = tk.Canvas(top, highlightthickness=0)
        hbar = ttk.Scrollbar(top, orient=tk.HORIZONTAL, command=canvas.xview)
        vbar = ttk.Scrollbar(top, orient=tk.VERTICAL, command=canvas.yview)

        canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)

        hbar.pack(side=tk.BOTTOM, fill=tk.X)
        vbar.pack(side=tk.RIGHT, fill=tk.Y)
        canvas.pack(side=tk.LEFT, expand=True, fill=tk.BOTH)

        self.full_photo = ImageTk.PhotoImage(self.full_image)
        canvas.create_image(0, 0, anchor=tk.NW, image=self.full_photo)
        canvas.config(scrollregion=(0, 0, img_w, img_h))

    # --- 原有逻辑保持不变 ---

    def select_template(self):
        path = filedialog.askopenfilename(filetypes=[("Excel", "*.xlsx")])
        if path: self.template_path.set(path)

    def select_data(self):
        path = filedialog.askopenfilename(filetypes=[("Excel", "*.xlsx")])
        if path:
            self.data_path.set(path)
            base = os.path.splitext(path)[0]
            self.output_path.set(f"{base}_结果_{time.strftime('%H%M%S')}.xlsx")

    def _copy_worksheet(self, source_ws, target_ws):
        for row in range(1, source_ws.max_row + 1):
            for col in range(1, source_ws.max_column + 1):
                source_cell = source_ws.cell(row=row, column=col)
                target_cell = target_ws.cell(row=row, column=col, value=source_cell.value)
                if source_cell.has_style:
                    target_cell.font = copy(source_cell.font)
                    target_cell.border = copy(source_cell.border)
                    target_cell.fill = copy(source_cell.fill)
                    target_cell.number_format = source_cell.number_format
                    target_cell.alignment = copy(source_cell.alignment)

        for col in range(1, source_ws.max_column + 1):
            col_letter = get_column_letter(col)
            target_ws.column_dimensions[col_letter].width = source_ws.column_dimensions[col_letter].width
        for row in range(1, source_ws.max_row + 1):
            if source_ws.row_dimensions[row].height:
                target_ws.row_dimensions[row].height = source_ws.row_dimensions[row].height
        for merged_range in source_ws.merged_cells.ranges:
            target_ws.merge_cells(str(merged_range))

    def start_generate_thread(self):
        if not all([self.template_path.get(), self.data_path.get()]):
            messagebox.showwarning("提示", "请确保已选择模板文件和数据文件!")
            return
        self.generate_btn.config(state=tk.DISABLED)
        threading.Thread(target=self.generate_excel_logic, daemon=True).start()

    def generate_excel_logic(self):
        try:
            self.progress_var.set("正在加载数据...")
            df = pd.read_excel(self.data_path.get())
            total_rows = len(df)

            template_wb = openpyxl.load_workbook(self.template_path.get())
            template_ws = template_wb.active
            new_wb = openpyxl.Workbook()
            new_wb.remove(new_wb.active)

            for index, row in df.iterrows():
                raw_sheet_val = str(row.iloc[0])
                safe_name = "".join([c for c in raw_sheet_val if c not in r'\*?:/[]'])[:30]

                self.progress_var.set(f"正在处理 ({index + 1}/{total_rows}): {safe_name}")
                self.root.after(0, lambda v=((index + 1) / total_rows * 100): self.update_progress(v))

                new_ws = new_wb.create_sheet(title=safe_name)
                self._copy_worksheet(template_ws, new_ws)

                for r_idx in range(1, new_ws.max_row + 1):
                    for c_idx in range(1, new_ws.max_column + 1):
                        cell = new_ws.cell(row=r_idx, column=c_idx)
                        if cell.value and isinstance(cell.value, str) and "{{" in cell.value:
                            field_name = cell.value.replace("{{", "").replace("}}", "").strip()
                            found = False
                            for col_name in df.columns:
                                clean_col_name = col_name.split('.')[0] if '.' in col_name else col_name
                                if clean_col_name == field_name:
                                    cell.value = row[col_name] if pd.notna(row[col_name]) else ""
                                    found = True
                                    break

            self.progress_var.set("保存中...")
            out_file = self.output_path.get()
            new_wb.save(out_file)

            self.root.after(0, lambda: self.ask_to_open_file(out_file))

        except Exception as e:
            self.root.after(0, lambda err=str(e): messagebox.showerror("错误", err))
        finally:
            self.root.after(0, self.reset_ui)

    def ask_to_open_file(self, file_path):
        ans = messagebox.askyesno("生成成功", f"&#9989; 文件已生成!\n路径:{file_path}\n\n是否立即打开该文件?")
        if ans:
            try:
                if os.name == 'nt':
                    os.startfile(file_path)
                elif os.name == 'posix':
                    subprocess.call(['open' if sys.platform == 'darwin' else 'xdg-open', file_path])
            except Exception as e:
                messagebox.showwarning("提醒", f"无法自动打开文件。\n{e}")

    def update_progress(self, value):
        self.progress_bar['value'] = value

    def reset_ui(self):
        self.generate_btn.config(state=tk.NORMAL)
        self.progress_var.set("准备就绪")
        self.progress_bar['value'] = 0


if __name__ == "__main__":
    try:
        from ctypes import windll

        windll.shcore.SetProcessDpiAwareness(1)
    except:
        pass
    root = tk.Tk()
    app = GeneralExcelGenerator(root)
    root.mainloop()





下载链接https://wwati.lanzouu.com/irCEb3hs2z7i
密码:52pj

免费评分

参与人数 1吾爱币 +7 热心值 +1 收起 理由
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-2-7 05:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表