直接上代码
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import re
import xlwings as xw
import os
from os import path as osp
import subprocess
import glob
import sys
from datetime import datetimeclass ExcelConverterApp:def __init__(self, root):self.root = rootself.root.title("CSV批量转Excel工具V1.0")self.root.geometry("700x600") self.root.resizable(False, False)self.folder_path = tk.StringVar()self.output_format = tk.StringVar(value="xlsx") self.app = Noneself.setup_ui()def setup_ui(self):style = ttk.Style()style.theme_use('clam')main_frame = ttk.Frame(self.root, padding="10")main_frame.pack(fill=tk.BOTH, expand=True)title_label = ttk.Label(main_frame,text="CSV批量转Excel转换工具",font=("微软雅黑", 16, "bold"),foreground="#2c3e50")title_label.pack(pady=(0, 15))folder_frame = ttk.LabelFrame(main_frame, text="选择文件夹", padding=10)folder_frame.pack(fill=tk.X, pady=5)ttk.Label(folder_frame, text="文件夹路径:").grid(row=0, column=0, sticky=tk.W)folder_entry = ttk.Entry(folder_frame, textvariable=self.folder_path, width=40)folder_entry.grid(row=0, column=1, padx=5, sticky=tk.EW)browse_btn = ttk.Button(folder_frame,text="浏览...",command=self.browse_folder,style="Accent.TButton")browse_btn.grid(row=0, column=2, padx=5)folder_frame.columnconfigure(1, weight=1) format_frame = ttk.LabelFrame(main_frame, text="输出格式", padding=10)format_frame.pack(fill=tk.X, pady=10)ttk.Radiobutton(format_frame,text="Excel 2007及以上 (.xlsx)",variable=self.output_format,value="xlsx").pack(anchor=tk.W, pady=2)ttk.Radiobutton(format_frame,text="Excel 97-2003 (.xls)",variable=self.output_format,value="xls").pack(anchor=tk.W, pady=2)button_frame = ttk.Frame(main_frame)button_frame.pack(pady=10)convert_btn = ttk.Button(button_frame,text="开始转换",command=self.convert_files,style="Accent.TButton")convert_btn.pack(side=tk.LEFT, padx=5)clear_log_btn = ttk.Button(button_frame,text="清空日志",command=self.clear_log)clear_log_btn.pack(side=tk.LEFT, padx=5)log_frame = ttk.LabelFrame(main_frame, text="转换日志", padding=5)log_frame.pack(fill=tk.BOTH, expand=True, pady=(10, 0))self.log_text = scrolledtext.ScrolledText(log_frame,wrap=tk.WORD,width=50,height=5,font=('Consolas', 10))self.log_text.pack(fill=tk.BOTH, expand=True)self.status_var = tk.StringVar(value="准备就绪")status_bar = ttk.Label(main_frame,textvariable=self.status_var,anchor=tk.W,padding=5)status_bar.pack(fill=tk.X, pady=(10, 0))self.configure_styles()def configure_styles(self):style = ttk.Style()style.configure('.', background="#ecf0f1", foreground="#2c3e50")style.configure('TLabel', background="#ecf0f1")style.configure('TFrame', background="#ecf0f1")style.configure('TLabelframe', background="#ecf0f1")style.configure('TLabelframe.Label', background="#ecf0f1")style.configure('TButton', padding=6, font=("微软雅黑", 10))style.configure('Accent.TButton', foreground="white", background="#3498db")style.map('Accent.TButton',background=[('active', '#2980b9'), ('pressed', '#2c3e50')])style.configure('TEntry', fieldbackground="white")style.configure('TRadiobutton', background="#ecf0f1")self.log_text.configure(background="white",foreground="black",insertbackground="black")def log_message(self, message):"""向日志框添加消息"""timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")log_entry = f"[{timestamp}] {message}\n"self.log_text.insert(tk.END, log_entry)self.log_text.see(tk.END) self.root.update() def clear_log(self):"""清空日志框"""self.log_text.delete(1.0, tk.END)self.log_message("日志已清空")def browse_folder(self):folder_selected = filedialog.askdirectory()if folder_selected:self.folder_path.set(folder_selected)self.log_message(f"已选择文件夹: {folder_selected}")self.status_var.set(f"已选择文件夹: {osp.basename(folder_selected)}")def load_excel(self):"""加载Excel应用"""self.log_message("正在启动Excel应用...")try:app = xw.App(visible=False)self.log_message("Excel应用启动成功")return appexcept Exception as e:self.log_message(f"打开Excel程序失败: {str(e)}")raise Exception("打开Excel程序失败") from edef natural_sort_key(self, s):"""自然排序的键生成函数"""return [int(text) if text.isdigit() else text.lower()for text in re.split(r'(\d+)', s)]def convert_files(self):"""执行转换操作"""folder = self.folder_path.get()if not folder:messagebox.showwarning("警告", "请先选择包含CSV文件的文件夹")self.log_message("警告: 未选择文件夹")returntry:self.status_var.set("正在初始化Excel...")self.log_message("开始转换过程...")self.root.update()self.app = self.load_excel()self.status_var.set("正在查找CSV文件...")self.log_message(f"正在搜索文件夹: {folder}")self.root.update()csv_files = glob.glob(osp.join(folder, '**', '*.csv'), recursive=True)csv_files.sort(key=lambda x: self.natural_sort_key(x), reverse=False)if not csv_files:messagebox.showinfo("提示", "没有找到任何CSV文件")self.log_message("未找到任何CSV文件")returntotal_files = len(csv_files)format_name = "XLSX" if self.output_format.get() == "xlsx" else "XLS"self.log_message(f"共找到 {total_files} 个CSV文件,将转换为{format_name}格式")confirm = messagebox.askyesno("确认",f"找到 {total_files} 个CSV文件,将转换为{format_name}格式,是否继续?")if not confirm:self.log_message("用户取消了转换操作")returnsuccess_count = 0for i, csv in enumerate(csv_files, 1):try:current_status = f"正在处理 ({i}/{total_files}): {osp.basename(csv)}"self.status_var.set(current_status)self.log_message(current_status)self.root.update()self.log_message(f"打开文件: {csv}")excel_path = osp.splitext(csv)[0] + ('.xlsx' if self.output_format.get() == "xlsx" else '.xls')wb = self.app.books.open(csv)self.log_message(f"正在保存为: {excel_path}")file_format = 51 if self.output_format.get() == "xlsx" else 56 wb.save(excel_path)wb.close()success_count += 1self.log_message(f"成功转换: {osp.basename(csv)}")except Exception as e:error_msg = f"处理文件 {osp.basename(csv)} 时出错: {str(e)}"self.log_message(error_msg)print(error_msg)result_msg = f"转换完成!成功: {success_count}/{total_files} 个文件"messagebox.showinfo("完成", result_msg)self.log_message(result_msg)self.status_var.set(f"转换完成,共处理 {success_count}/{total_files} 个文件")except Exception as e:error_msg = f"转换过程中出错: {str(e)}"messagebox.showerror("错误", error_msg)self.log_message(error_msg)self.status_var.set("转换出错")finally:if hasattr(self, 'app') and self.app:self.log_message("正在退出Excel应用...")self.app.quit()def main():root = tk.Tk()app = ExcelConverterApp(root)root.mainloop()if __name__ == "__main__":main()
