本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴

关于Excel图片批量插入的痛点

单张插入的重复性劳动
传统 Excel 仅支持 逐张插入图片(通过「插入图片」功能或复制粘贴),插入 1000 张图需重复操作千次,即便熟练用户每分钟插入 10 张,也需 16 小时以上,严重影响工作效率。
就好比我们老师在批量登记我们学生的成绩,脑子突然抽抽下信息就填错了

缺乏原生批量导入功能
Excel 本身未提供「批量选择文件夹内所有图片」的入口,用户需借助第三方插件或 VBA 代码实现批量插入,对非技术用户门槛极高。
_常见问题_:图片遮挡数据、单元格高度混乱、跨页显示不全等,需反复调试。

图片位置与对齐失控
手动拖动图片时,难以精准对齐单元格网格线,尤其在多列多行间操作,易出现「图片错位、行列间距不均」等问题,影响表格美观度。

嵌入图片导致文件爆炸式增大
每张高清图片嵌入 Excel 后,文件体积可能从几十 KB 飙升至数百 MB(如 1000 张 500KB 的图片直接使文件超过 500MB),导致 Excel 打开缓慢、保存卡顿,甚至触发「内存不足」报错。

传统 Excel 痛点CodeBuddy 双模式的解决方案
手动插入耗时、批量功能缺失「双模式」(文件夹批量导入 + 指定图片路径)一键处理,1000 张图分钟级完成
图片格式与单元格适配困难自动匹配单元格尺寸、锁定位置,支持批量设置格式(如缩放比例、对齐方式)
文件体积大、性能卡顿提供「链接模式」(轻量)与「嵌入模式」(安全),按需选择,减少资源占用
操作门槛高(需 VBA 或插件)可视化界面操作,无需代码基础,新手可快速上手

随着CodeBuddy的爆火,我是否想能否生成一款属于我自己的批量化Excel文件图片的插入操作应用呢?
想法存在,那么我们立刻开始实操

使用CodeBuddy实现一个强大的Excel图片批量插入工具

我们打开vscode进入到拓展中直接搜索CodeBuddy,点击安装这个插件就行了
image.png
我们点击左侧的插件图标就能进行使用了
我这里准备了一张关于这款应用简单介绍的README文件,我们让CodeBuddy帮我们优化下
image.png

他这里介绍的很详细
image.png
那么我们直接再次@这个README文件,让他依据这个文件进行开发操作
这里也是很快的将应用代码生成出来了
image.png
我们在终端输入命令code main.py运行试试
这里我们直接将报错信息复制给CodeBuddy让它处理下
这里他开始进行了快速分析以及代码生成修改
image.png
代码修改好了之后,我们继续进行测试
这里我们成功打开了应用,看着风格还是不错的
image.png

我们选择一个图片文件夹
image.png
选中我们想插入的Excel文件
我们这里也是成功的将图片进行了插入操作了
因为我们这里没有将框框拉开,所以显现的有点狭窄了,但是整体的实现效果还是不错的,大家也可以去试试
image.png
image.png
示例代码如下:

import os

import re

import tkinter as tk

import webbrowser

from tkinter import filedialog, messagebox, ttk

from typing import Dict, Optional, Tuple

  

import xlwings as xw

  
  

class ExcelImageMatcherPro:

    def __init__(self, master):

        self.master = master

        master.title("Excel批量匹配插图工具")

        master.geometry("600x700")

        # 应用主题和配色方案

        self.setup_theme()

        # 初始化变量

        self.col_image_map: Dict[str, str] = {}

        self.row_image_map: Dict[str, str] = {}

        self.topmost_var = tk.BooleanVar(value=True)

        # 创建主界面

        self.create_main_interface()

        # 窗口居中

        self.center_window(master)

        # 初始化帮助系统

        self._create_help_tags()

        self.show_help_guide()

        # 绑定事件

        self.notebook.bind("<<NotebookTabChanged>>", self.on_tab_changed)

        master.attributes('-topmost', self.topmost_var.get())

  

    def setup_theme(self):

        """设置应用主题和配色方案"""

        style = ttk.Style()

        # 主色调 - 紫色系

        primary_color = "#7B1FA2"

        secondary_color = "#9C27B0"

        accent_color = "#E1BEE7"

        # 文本颜色

        text_color = "#333333"

        light_text = "#FFFFFF"

        # 状态颜色

        success_color = "#4CAF50"

        warning_color = "#FFC107"

        error_color = "#F44336"

        info_color = "#2196F3"

        # 配置主题

        style.theme_create("custom_theme", parent="clam", settings={

            "TFrame": {"configure": {"background": "#F5F5F5"}},

            "TLabel": {"configure": {"foreground": text_color, "background": "#F5F5F5", "font": ('Microsoft YaHei', 9)}},

            "TButton": {

                "configure": {

                    "foreground": light_text,

                    "background": primary_color,

                    "font": ('Microsoft YaHei', 9),

                    "padding": 5,

                    "borderwidth": 1,

                    "relief": "raised"

                },

                "map": {

                    "background": [("active", secondary_color), ("disabled", "#CCCCCC")],

                    "foreground": [("disabled", "#999999")]

                }

            },

            "TEntry": {

                "configure": {

                    "fieldbackground": "white",

                    "foreground": text_color,

                    "insertcolor": text_color,

                    "font": ('Microsoft YaHei', 9)

                }

            },

            "TCombobox": {

                "configure": {

                    "fieldbackground": "white",

                    "foreground": text_color,

                    "selectbackground": accent_color,

                    "font": ('Microsoft YaHei', 9)

                }

            },

            "TNotebook": {

                "configure": {

                    "background": "#F5F5F5",

                    "tabmargins": [2, 5, 2, 0]

                }

            },

            "TNotebook.Tab": {

                "configure": {

                    "background": "#E0E0E0",

                    "foreground": text_color,

                    "padding": [10, 5],

                    "font": ('Microsoft YaHei', 9, 'bold')

                },

                "map": {

                    "background": [("selected", "#FFFFFF"), ("active", "#EEEEEE")],

                    "expand": [("selected", [1, 1, 1, 0])]

                }

            },

            "TScrollbar": {

                "configure": {

                    "background": "#E0E0E0",

                    "troughcolor": "#F5F5F5",

                    "arrowcolor": text_color

                }

            },

            "Horizontal.TProgressbar": {

                "configure": {

                    "background": primary_color,

                    "troughcolor": "#E0E0E0",

                    "borderwidth": 0,

                    "lightcolor": primary_color,

                    "darkcolor": primary_color

                }

            }

        })

        style.theme_use("custom_theme")

  

    def create_main_interface(self):

        """创建主界面组件"""

        # 主容器

        main_frame = ttk.Frame(self.master)

        main_frame.pack(fill="both", expand=True, padx=10, pady=10)

        # 标题栏

        title_frame = ttk.Frame(main_frame)

        title_frame.pack(fill="x", pady=(0, 10))

        title_label = ttk.Label(

            title_frame,

            text="Excel批量匹配插图工具",

            font=('Microsoft YaHei', 12, 'bold'),

            foreground="#7B1FA2"

        )

        title_label.pack(side="left")

        # 标签页控件

        self.notebook = ttk.Notebook(main_frame)

        self.notebook.pack(fill="both", expand=True)

        # 创建两个标签页

        self.create_column_tab()

        self.create_row_tab()

        # 状态栏

        self.create_status_bar()

  

    def create_column_tab(self):

        """创建列匹配模式标签页"""

        tab = ttk.Frame(self.notebook)

        self.notebook.add(tab, text="列匹配模式")

        # 描述区域

        desc_frame = ttk.LabelFrame(

            tab,

            text="说明",

            padding=10,

            style="Custom.TLabelframe"

        )

        desc_frame.pack(fill="x", padx=5, pady=5)

        ttk.Label(

            desc_frame,

            text="列匹配模式:按垂直方向匹配插入,适合单列数据匹配。\n图片名称需与指定列中的单元格内容完全匹配。",

            foreground="#616161",

            font=('Microsoft YaHei', 9)

        ).pack(anchor="w")

        # Excel文件选择区域

        excel_frame = ttk.LabelFrame(tab, text="Excel文件设置", padding=10)

        excel_frame.pack(fill="x", padx=5, pady=5)

        self.col_excel_var = tk.StringVar(value="使用当前活动工作簿")

        ttk.Label(excel_frame, text="Excel文件:").grid(row=0, column=0, padx=5, pady=5, sticky="e")

        excel_entry = ttk.Entry(

            excel_frame,

            textvariable=self.col_excel_var,

            width=40,

            state="readonly",

            style="Custom.TEntry"

        )

        excel_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")

        btn_frame = ttk.Frame(excel_frame)

        btn_frame.grid(row=0, column=2, padx=5, pady=5, sticky="e")

        ttk.Button(

            btn_frame,

            text="浏览...",

            command=lambda: self.select_excel_file(self.col_excel_var),

            style="Accent.TButton"

        ).pack(side="left", padx=2)

        ttk.Button(

            btn_frame,

            text="清除",

            command=lambda: self.col_excel_var.set("使用当前活动工作簿")

        ).pack(side="left", padx=2)

        # 参数设置区域

        param_frame = ttk.LabelFrame(tab, text="匹配参数设置", padding=10)

        param_frame.pack(fill="x", padx=5, pady=5)

        # 第一行参数

        row1_frame = ttk.Frame(param_frame)

        row1_frame.pack(fill="x", pady=5)

        ttk.Label(row1_frame, text="起始行号:").pack(side="left", padx=5)

        self.col_start_row = ttk.Entry(row1_frame, width=8)

        self.col_start_row.pack(side="left", padx=5)

        self.col_start_row.insert(0, "2")

        ttk.Label(row1_frame, text="匹配列:").pack(side="left", padx=5)

        self.col_match = ttk.Entry(row1_frame, width=8)

        self.col_match.pack(side="left", padx=5)

        self.col_match.insert(0, "A")

        ttk.Label(row1_frame, text="插入列:").pack(side="left", padx=5)

        self.col_insert = ttk.Entry(row1_frame, width=8)

        self.col_insert.pack(side="left", padx=5)

        self.col_insert.insert(0, "B")

        # 第二行参数

        row2_frame = ttk.Frame(param_frame)

        row2_frame.pack(fill="x", pady=5)

        ttk.Label(row2_frame, text="边距:").pack(side="left", padx=5)

        self.col_margin = ttk.Entry(row2_frame, width=8)

        self.col_margin.pack(side="left", padx=5)

        self.col_margin.insert(0, "2")

        # 图片文件夹选择

        folder_frame = ttk.Frame(param_frame)

        folder_frame.pack(fill="x", pady=10)

        self.col_folder_var = tk.StringVar()

        ttk.Label(folder_frame, text="图片文件夹:").pack(side="left", padx=5)

        folder_entry = ttk.Entry(

            folder_frame,

            textvariable=self.col_folder_var,

            width=40,

            state="readonly"

        )

        folder_entry.pack(side="left", padx=5, expand=True, fill="x")

        ttk.Button(

            folder_frame,

            text="浏览...",

            command=lambda: self.select_folder(self.col_folder_var, mode="column"),

            style="Accent.TButton"

        ).pack(side="left", padx=5)

        # 执行按钮

        btn_frame = ttk.Frame(tab)

        btn_frame.pack(fill="x", padx=5, pady=10)

        ttk.Button(

            btn_frame,

            text="执行列匹配插入",

            command=self.run_column_match,

            style="Primary.TButton"

        ).pack(fill="x", expand=True)

        # 日志区域

        log_frame = ttk.LabelFrame(tab, text="操作日志", padding=10)

        log_frame.pack(fill="both", expand=True, padx=5, pady=5)

        self.col_log = tk.Text(

            log_frame,

            wrap=tk.WORD,

            height=10,

            state="disabled",

            font=('Microsoft YaHei', 9),

            bg="white",

            fg="#333333",

            padx=5,

            pady=5

        )

        scroll = ttk.Scrollbar(log_frame, command=self.col_log.yview)

        self.col_log.configure(yscrollcommand=scroll.set)

        self.col_log.pack(side="left", fill="both", expand=True)

        scroll.pack(side="right", fill="y")

  

    def create_row_tab(self):

        """创建行匹配模式标签页"""

        tab = ttk.Frame(self.notebook)

        self.notebook.add(tab, text="行匹配模式")

        # 描述区域

        desc_frame = ttk.LabelFrame(tab, text="说明", padding=10)

        desc_frame.pack(fill="x", padx=5, pady=5)

        ttk.Label(

            desc_frame,

            text="行匹配模式:按水平方向匹配插入,适合单行数据匹配。\n图片名称需与指定行中的单元格内容完全匹配。",

            foreground="#616161",

            font=('Microsoft YaHei', 9)

        ).pack(anchor="w")

        # Excel文件选择区域

        excel_frame = ttk.LabelFrame(tab, text="Excel文件设置", padding=10)

        excel_frame.pack(fill="x", padx=5, pady=5)

        self.row_excel_var = tk.StringVar(value="使用当前活动工作簿")

        ttk.Label(excel_frame, text="Excel文件:").grid(row=0, column=0, padx=5, pady=5, sticky="e")

        excel_entry = ttk.Entry(

            excel_frame,

            textvariable=self.row_excel_var,

            width=40,

            state="readonly"

        )

        excel_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")

        btn_frame = ttk.Frame(excel_frame)

        btn_frame.grid(row=0, column=2, padx=5, pady=5, sticky="e")

        ttk.Button(

            btn_frame,

            text="浏览...",

            command=lambda: self.select_excel_file(self.row_excel_var),

            style="Accent.TButton"

        ).pack(side="left", padx=2)

        ttk.Button(

            btn_frame,

            text="清除",

            command=lambda: self.row_excel_var.set("使用当前活动工作簿")

        ).pack(side="left", padx=2)

        # 参数设置区域

        param_frame = ttk.LabelFrame(tab, text="匹配参数设置", padding=10)

        param_frame.pack(fill="x", padx=5, pady=5)

        # 参数行

        row_frame = ttk.Frame(param_frame)

        row_frame.pack(fill="x", pady=10)

        ttk.Label(row_frame, text="匹配行:").pack(side="left", padx=5)

        self.row_match = ttk.Entry(row_frame, width=8)

        self.row_match.pack(side="left", padx=5)

        self.row_match.insert(0, "1")

        ttk.Label(row_frame, text="插入行:").pack(side="left", padx=5)

        self.row_insert = ttk.Entry(row_frame, width=8)

        self.row_insert.pack(side="left", padx=5)

        self.row_insert.insert(0, "2")

        ttk.Label(row_frame, text="边距:").pack(side="left", padx=5)

        self.row_margin = ttk.Entry(row_frame, width=8)

        self.row_margin.pack(side="left", padx=5)

        self.row_margin.insert(0, "2")

        # 图片文件夹选择

        folder_frame = ttk.Frame(param_frame)

        folder_frame.pack(fill="x", pady=10)

        self.row_folder_var = tk.StringVar()

        ttk.Label(folder_frame, text="图片文件夹:").pack(side="left", padx=5)

        folder_entry = ttk.Entry(

            folder_frame,

            textvariable=self.row_folder_var,

            width=40,

            state="readonly"

        )

        folder_entry.pack(side="left", padx=5, expand=True, fill="x")

        ttk.Button(

            folder_frame,

            text="浏览...",

            command=lambda: self.select_folder(self.row_folder_var, mode="row"),

            style="Accent.TButton"

        ).pack(side="left", padx=5)

        # 执行按钮

        btn_frame = ttk.Frame(tab)

        btn_frame.pack(fill="x", padx=5, pady=10)

        ttk.Button(

            btn_frame,

            text="执行行匹配插入",

            command=self.run_row_match,

            style="Primary.TButton"

        ).pack(fill="x", expand=True)

        # 日志区域

        log_frame = ttk.LabelFrame(tab, text="操作日志", padding=10)

        log_frame.pack(fill="both", expand=True, padx=5, pady=5)

        self.row_log = tk.Text(

            log_frame,

            wrap=tk.WORD,

            height=10,

            state="disabled",

            font=('Microsoft YaHei', 9),

            bg="white",

            fg="#333333",

            padx=5,

            pady=5

        )

        scroll = ttk.Scrollbar(log_frame, command=self.row_log.yview)

        self.row_log.configure(yscrollcommand=scroll.set)

        self.row_log.pack(side="left", fill="both", expand=True)

        scroll.pack(side="right", fill="y")

  

    def create_status_bar(self):

        """创建状态栏"""

        status_frame = ttk.Frame(self.master, padding=(10, 5))

        status_frame.pack(side="bottom", fill="x")

        # 窗口置顶按钮

        ttk.Checkbutton(

            status_frame,

            text="窗口置顶",

            variable=self.topmost_var,

            command=lambda: self.master.attributes('-topmost', self.topmost_var.get())

        ).pack(side="left", padx=(0, 10))

        # 帮助按钮

        ttk.Button(

            status_frame,

            text="帮助",

            width=8,

            command=self.show_help_guide

        ).pack(side="left", padx=(0, 10))

        # 版本信息

        version_label = ttk.Label(

            status_frame,

            text="版本: 1.0.0",

            foreground="gray"

        )

        version_label.pack(side="left", padx=(0, 10))

        # 作者信息

        author_label = tk.Label(

            status_frame,

            text="By 创客白泽",

            fg="gray",

            cursor="hand2",

            font=('Microsoft YaHei', 9)

        )

        author_label.bind("<Enter>", lambda e: author_label.config(fg="#7B1FA2"))

        author_label.bind("<Leave>", lambda e: author_label.config(fg="gray"))

        author_label.bind(

            "<Button-1>",

            lambda e: webbrowser.open("https://www.52pojie.cn/thread-2030255-1-1.html")

        )

        author_label.pack(side="right")

  

    def _create_help_tags(self):

        """创建日志文本标签样式"""

        for log in [self.col_log, self.row_log]:

            log.tag_config("title", foreground="#7B1FA2", font=('Microsoft YaHei', 10, 'bold'))

            log.tag_config("success", foreground="#4CAF50")

            log.tag_config("warning", foreground="#FF9800")

            log.tag_config("error", foreground="#F44336")

            log.tag_config("info", foreground="#2196F3")

            log.tag_config("preview", foreground="#616161")

            log.tag_config("highlight", background="#E1BEE7")

  

    def center_window(self, window):

        """窗口居中显示"""

        window.update_idletasks()

        width = window.winfo_width()

        height = window.winfo_height()

        screen_width = window.winfo_screenwidth()

        screen_height = window.winfo_screenheight()

        x = (screen_width - width) // 2

        y = (screen_height - height) // 2

        window.geometry(f"{width}x{height}+{x}+{y}")

  

    def show_help_guide(self, target_log=None):

        """显示帮助指南"""

        help_text = """【新手操作指南 - 点下方"帮助"按钮可再次显示】

  

1. 准备工作:

    - 选择Excel文件或使用当前活动工作簿

    - 准备图片文件夹(支持jpg/png/webp/bmp格式)

  

2. 参数设置:

    - Excel文件:选择要操作的工作簿(可选)

    - 起始行号:从哪一行开始匹配(默认为2)

    - 匹配列/行:包含名称的列或行(如A列或1行)

    - 插入列/行:图片要插入的位置(如B列或2行)

    - 边距:图片与单元格边界的距离(推荐2,0表示撑满)

  

3. 执行步骤:

    (1) 选择Excel文件(可选)

    (2) 选择图片文件夹

    (3) 点击"执行匹配插入"按钮

  

★ 注意事项:

    - 图片名称需与单元格内容完全一致(不区分大小写)

    - 示例:单元格"产品A" → 图片"产品A.jpg"

    - 插入过程中请不要操作Excel

    """

  

        if target_log is None:

            current_tab = self.notebook.index("current")

            target_log = self.col_log if current_tab == 0 else self.row_log

  

        self.log_message(help_text, target_log, append=False, tags="info")

  

    def select_excel_file(self, var_tk_stringvar):

        """选择Excel文件"""

        file_path = filedialog.askopenfilename(

            filetypes=[("Excel文件", "*.xls *.xlsx *.xlsm"), ("所有文件", "*.*")])

        if file_path:

            var_tk_stringvar.set(file_path)

  

    def select_folder(self, var_tk_stringvar, mode):

        """选择图片文件夹"""

        folder_path = filedialog.askdirectory()

        if folder_path:

            var_tk_stringvar.set(folder_path)

            log_widget = self.col_log if mode == "column" else self.row_log

  

            self.log_message("开始加载图片...", log_widget, append=False)

  

            current_image_map = self.build_image_map_from_folder(folder_path)

  

            if mode == "column":

                self.col_image_map = current_image_map

            elif mode == "row":

                self.row_image_map = current_image_map

  

            if len(current_image_map) > 0:

                self.log_message(

                    f"加载完成:找到 {len(current_image_map)} 张支持的图片。",

                    log_widget,

                    tags="success"

                )

            else:

                self.log_message("警告: 未找到任何支持的图片文件。", log_widget, tags="warning")

  

            self.preview_insert_positions(mode)

  

    def build_image_map_from_folder(self, folder_path: str) -> Dict[str, str]:

        """从文件夹构建图片名称到路径的映射"""

        image_map: Dict[str, str] = {}

        extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.webp')

        try:

            for root, _, files in os.walk(folder_path):

                for file in files:

                    if file.lower().endswith(extensions):

                        name_without_ext = os.path.splitext(file)[0].strip().lower()

                        image_map[name_without_ext] = os.path.abspath(os.path.join(root, file))

        except Exception as e:

            self.log_message(

                f"构建图片映射出错: {e}",

                self.col_log if 'col' in self._current_mode else self.row_log,

                tags="error"

            )

        return image_map

  

    def validate_column_params(self) -> Dict:

        """验证列匹配参数"""

        params = {

            'match_col': self.col_match.get().upper(),

            'insert_col': self.col_insert.get().upper(),

            'start_row': self.col_start_row.get(),

            'margin': self.col_margin.get(),

            'excel_file': self.col_excel_var.get()

        }

        if not re.match(r'^[A-Z]{1,3}$', params['match_col']):

            raise ValueError("匹配列格式错误 (例如: A, B, AA)")

        if not re.match(r'^[A-Z]{1,3}$', params['insert_col']):

            raise ValueError("插入列格式错误 (例如: A, B, AA)")

        if not params['start_row'].isdigit() or int(params['start_row']) < 1:

            raise ValueError("起始行号必须是大于0的数字")

        if not params['margin'].isdigit() or int(params['margin']) < 0:

            raise ValueError("边距必须是非负数字")

        return {

            'match_col': params['match_col'],

            'insert_col': params['insert_col'],

            'start_row': int(params['start_row']),

            'margin': int(params['margin']),

            'excel_file': params['excel_file']

        }

  

    def validate_row_params(self) -> Dict:

        """验证行匹配参数"""

        params = {

            'match_row': self.row_match.get(),

            'insert_row': self.row_insert.get(),

            'margin': self.row_margin.get(),

            'excel_file': self.row_excel_var.get()

        }

        if not params['match_row'].isdigit() or int(params['match_row']) < 1:

            raise ValueError("匹配行必须是大于0的数字")

        if not params['insert_row'].isdigit() or int(params['insert_row']) < 1:

            raise ValueError("插入行必须是大于0的数字")

        if not params['margin'].isdigit() or int(params['margin']) < 0:

            raise ValueError("边距必须是非负数字")

        return {

            'match_row': int(params['match_row']),

            'insert_row': int(params['insert_row']),

            'margin': int(params['margin']),

            'excel_file': params['excel_file']

        }

  

    def get_excel_app(self) -> xw.App:

        """获取Excel应用实例,优先使用已打开的实例"""

        try:

            # 尝试获取已打开的Excel应用

            app = xw.apps.active

            if app is not None:

                return app

            # 如果没有打开的Excel,尝试获取第一个Excel实例

            if len(xw.apps) > 0:

                return xw.apps[0]

            # 如果都没有,则创建新实例

            return xw.App(visible=True)

        except Exception as e:

            raise Exception(f"无法获取Excel应用实例: {str(e)}")

  

    def excel_operation(self, excel_file: Optional[str] = None) -> Tuple[xw.Book, xw.Sheet]:

        """Excel操作上下文管理器,正确处理已打开的工作簿"""

        app = self.get_excel_app()

        try:

            if excel_file and excel_file != "使用当前活动工作簿":

                # 检查工作簿是否已经打开

                for book in app.books:

                    if book.fullname.lower() == os.path.abspath(excel_file).lower():

                        wb = book

                        break

                else:

                    wb = app.books.open(excel_file)

            else:

                # 使用活动工作簿或第一个工作簿

                wb = app.books.active

                if wb is None and len(app.books) > 0:

                    wb = app.books[0]

                if wb is None:

                    raise Exception("没有可用的工作簿,请先打开或创建一个工作簿")

            ws = wb.sheets.active

            if ws is None:

                raise Exception("工作簿中没有活动的工作表")

            return wb, ws

        except Exception as e:

            raise Exception(f"Excel操作错误: {str(e)}")

  

    def insert_image(self, ws, image_path, cell_addr, margin, log_widget):

        """在Excel中插入图片"""

        try:

            abs_image_path = os.path.abspath(image_path)

            if not os.path.exists(abs_image_path):

                self.log_message(f"错误: 图片文件不存在 - {abs_image_path}", log_widget, tags="error")

                return False

  

            target_cell = ws.range(cell_addr)

  

            left = target_cell.left + margin

            top = target_cell.top + margin

            width = target_cell.width - 2 * margin

            height = target_cell.height - 2 * margin

  

            ws.pictures.add(

                abs_image_path,

                left=left,

                top=top,

                width=width,

                height=height

            )

            return True

  

        except Exception as e:

            error_msg = f"插入图片失败: {str(e)}"

            self.log_message(error_msg, log_widget, tags="error")

            return False

  

    def run_column_match(self):

        """执行列匹配插入"""

        self.log_message("开始列匹配处理...", self.col_log, append=False, tags="title")

        try:

            if not self.col_folder_var.get():

                messagebox.showwarning("提示", "请先选择图片文件夹!")

                self.log_message("错误: 未选择图片文件夹。", self.col_log, tags="error")

                return

  

            params = self.validate_column_params()

            wb, ws = self.excel_operation(params['excel_file'])

            max_row = ws.used_range.last_cell.row

            success_inserts = 0

            processed_excel_rows = 0

            non_empty_match_cells = 0

  

            self.log_message(

                f"将在列 {params['match_col']} 中查找名称,图片插入到列 {params['insert_col']},从行 {params['start_row']} 开始。",

                self.col_log,

                tags="info"

            )

  

            for row_num_excel in range(params['start_row'], max_row + 1):

                processed_excel_rows += 1

                match_cell_addr = f"{params['match_col']}{row_num_excel}"

  

                cell_value = ws.range(match_cell_addr).value

                name_to_match = str(cell_value).strip() if cell_value is not None else ""

  

                if not name_to_match:

                    continue

  

                non_empty_match_cells += 1

                insert_cell_addr = f"{params['insert_col']}{row_num_excel}"

                lower_name_to_match = name_to_match.lower()

  

                if lower_name_to_match in self.col_image_map:

                    image_file_path = os.path.abspath(self.col_image_map[lower_name_to_match])

                    if not os.path.exists(image_file_path):

                        self.log_message(

                            f"错误: 图片文件不存在 - {image_file_path}",

                            self.col_log,

                            tags="error"

                        )

                        continue

  

                    if self.insert_image(ws, image_file_path, insert_cell_addr, params['margin'], self.col_log):

                        success_inserts += 1

                        self.log_message(

                            f"{match_cell_addr}:{name_to_match} → {insert_cell_addr}:{os.path.basename(image_file_path)}",

                            self.col_log,

                            tags="success"

                        )

                else:

                    self.log_message(

                        f"{match_cell_addr}:{name_to_match} → 未找到匹配图片",

                        self.col_log,

                        tags="warning"

                    )

  

            summary = f"\n共处理 {processed_excel_rows} 行数据,成功插入 {success_inserts} 张图片。"

            self.log_message(summary, self.col_log, tags="info")

  

        except Exception as e:

            error_msg = f"列匹配错误: {str(e)}"

            self.log_message(error_msg, self.col_log, tags="error")

            messagebox.showerror("错误", error_msg)

  

    def run_row_match(self):

        """执行行匹配插入"""

        self.log_message("开始行匹配处理...", self.row_log, append=False, tags="title")

        try:

            if not self.row_folder_var.get():

                messagebox.showwarning("提示", "请先选择图片文件夹!")

                self.log_message("错误: 未选择图片文件夹。", self.row_log, tags="error")

                return

  

            params = self.validate_row_params()

            wb, ws = self.excel_operation(params['excel_file'])

            max_col = ws.used_range.last_cell.column

            success_inserts = 0

            processed_excel_cols = 0

            non_empty_match_cells = 0

  

            self.log_message(

                f"将在行 {params['match_row']} 中查找名称,图片插入到行 {params['insert_row']}。",

                self.row_log,

                tags="info"

            )

  

            for col_num_excel in range(1, max_col + 1):

                processed_excel_cols += 1

                match_cell_addr = f"{xw.utils.col_name(col_num_excel)}{params['match_row']}"

  

                cell_value = ws.range(match_cell_addr).value

                name_to_match = str(cell_value).strip() if cell_value is not None else ""

  

                if not name_to_match:

                    continue

  

                non_empty_match_cells += 1

                insert_cell_addr = f"{xw.utils.col_name(col_num_excel)}{params['insert_row']}"

                lower_name_to_match = name_to_match.lower()

  

                if lower_name_to_match in self.row_image_map:

                    image_file_path = os.path.abspath(self.row_image_map[lower_name_to_match])

                    if not os.path.exists(image_file_path):

                        self.log_message(

                            f"错误: 图片文件不存在 - {image_file_path}",

                            self.row_log,

                            tags="error"

                        )

                        continue

  

                    if self.insert_image(ws, image_file_path, insert_cell_addr, params['margin'], self.row_log):

                        success_inserts += 1

                        self.log_message(

                            f"{match_cell_addr}:{name_to_match} → {insert_cell_addr}:{os.path.basename(image_file_path)}",

                            self.row_log,

                            tags="success"

                        )

                else:

                    self.log_message(

                        f"{match_cell_addr}:{name_to_match} → 未找到匹配图片",

                        self.row_log,

                        tags="warning"

                    )

  

            summary = f"\n共处理 {processed_excel_cols} 列数据,成功插入 {success_inserts} 张图片。"

            self.log_message(summary, self.row_log, tags="info")

  

        except Exception as e:

            error_msg = f"行匹配错误: {str(e)}"

            self.log_message(error_msg, self.row_log, tags="error")

            messagebox.showerror("错误", error_msg)

  

    def preview_insert_positions(self, mode):

        """预览插入位置"""

        image_map = self.col_image_map if mode == "column" else self.row_image_map

        log_widget = self.col_log if mode == "column" else self.row_log

  

        if not image_map:

            self.log_message("没有图片可供预览。请先选择图片文件夹。", log_widget, tags="warning")

            return

  

        self.log_message("【插入位置预览】", log_widget, tags="title")

        for name, path in image_map.items():

            self.log_message(

                f"{name} -> {os.path.basename(path)}",

                log_widget,

                tags="preview"

            )

  

    def log_message(self, message, log_widget, append=True, tags=None, clear=False):

        """记录日志消息"""

        log_widget.config(state="normal")

        if clear:

            log_widget.delete(1.0, tk.END)

        if not append:

            log_widget.delete(1.0, tk.END)

        log_widget.insert(tk.END, message + "\n", tags)

        log_widget.see(tk.END)

        log_widget.config(state="disabled")

  

    def on_tab_changed(self, event):

        """标签页切换事件处理"""

        self.show_help_guide()

  
  

if __name__ == "__main__":

    root = tk.Tk()

    app = ExcelImageMatcherPro(root)

    root.mainloop()

总结

我们搞编程的都清楚,在数据处理这块,效率就是王道。Excel 大家都用得多,但它原生的图片插入功能,碰到大量图片时就拉胯了。一张一张插,重复操作累死人不说,格式调整也麻烦,还特别占性能,处理起来真糟心。

CodeBuddy 生成的这工具就不一样了,双模式批量处理的设计挺巧妙。不管是文件夹批量导入,还是指定图片路径,都能快速把大量图片塞进 Excel 里,而且在格式适配和资源占用控制上也有一手,还没什么操作门槛。

从我们的专业视角看,它给了咱新思路,怎么用简洁的代码实现高效功能,降低用户使用难度。真心希望 CodeBuddy 能不断优化,也盼着以后能有更多这种实用工具冒出来,让咱搞数据处理轻松些,代码少写点弯路,工作更顺溜!


Undoom
1 声望0 粉丝