[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import os
from pathlib import Path
import time
import shutil
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
from copy import copy
import traceback
import sys
def resource_path(relative_path):
""" 获取资源的绝对路径 """
try:
# PyInstaller创建临时文件夹,将路径存储在_MEIPASS中
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
class ExcelQueryApp:
def __init__(self, root):
self.root = root
self.root.title("Excel查询工具-志伟呀@_@qzw")
self.root.geometry("1200x800")
# 配置根窗口的网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
# 数据相关变量
self.df = None
self.default_save_path = os.path.join(os.path.expanduser("~"), "Desktop")
self.create_widgets()
def create_widgets(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))
# 创建左右分栏
left_frame = ttk.Frame(main_frame)
left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
right_frame = ttk.Frame(main_frame)
right_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置左侧框架的网格权重
left_frame.columnconfigure(0, weight=1)
left_frame.rowconfigure(1, weight=1) # 让工作表设置区域可以扩展
# 左侧内容 - 顶部文件选择
file_frame = ttk.Frame(left_frame)
file_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
ttk.Label(file_frame, text="选择Excel文件:").pack(side=tk.LEFT)
self.file_path = tk.StringVar()
ttk.Entry(file_frame, textvariable=self.file_path, width=50).pack(side=tk.LEFT, padx=5)
ttk.Button(file_frame, text="浏览", command=self.browse_file).pack(side=tk.LEFT)
# Sheet信息显示和设置(使用滚动框架)
sheet_container = ttk.LabelFrame(left_frame, text="工作表设置", padding="5")
sheet_container.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置sheet_container的网格权重
sheet_container.columnconfigure(0, weight=1)
sheet_container.rowconfigure(0, weight=1)
# 创建Canvas和Scrollbar
canvas = tk.Canvas(sheet_container)
scrollbar = ttk.Scrollbar(sheet_container, orient="vertical", command=canvas.yview)
# 创建可滚动的框架
self.sheet_frame = ttk.Frame(canvas)
self.sheet_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
# 配置canvas的网格权重
canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# 在Canvas中创建窗口
canvas.create_window((0, 0), window=self.sheet_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# 绑定鼠标滚轮事件
def _on_mousewheel(event):
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
canvas.bind_all("<MouseWheel>", _on_mousewheel)
# 统一设置选项
self.unified_settings = tk.BooleanVar(value=True)
ttk.Checkbutton(self.sheet_frame, text="统一设置所有工作表",
variable=self.unified_settings,
command=self.toggle_settings).grid(row=0, column=0, columnspan=2, sticky=tk.W)
# 统一设置框架
self.unified_frame = ttk.Frame(self.sheet_frame)
self.unified_frame.grid(row=1, column=0, columnspan=2, pady=5, sticky=(tk.W, tk.E))
ttk.Label(self.unified_frame, text="表头行号:").grid(row=0, column=0, sticky=tk.W)
self.unified_header_row = tk.StringVar(value="1")
ttk.Entry(self.unified_frame, textvariable=self.unified_header_row, width=10).grid(row=0, column=1)
ttk.Label(self.unified_frame, text="关键词列号:").grid(row=0, column=2, sticky=tk.W, padx=(20,0))
self.unified_keyword_column = tk.StringVar(value="1")
ttk.Entry(self.unified_frame, textvariable=self.unified_keyword_column, width=10).grid(row=0, column=3)
# 独立设置框架(初始隐藏)
self.individual_frame = ttk.Frame(self.sheet_frame)
self.sheet_settings = {}
# 左侧底部控件
bottom_frame = ttk.Frame(left_frame)
bottom_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=10)
# 关键词输入
keyword_frame = ttk.Frame(bottom_frame)
keyword_frame.pack(fill=tk.X, pady=(0, 5))
ttk.Label(keyword_frame, text="关键词(用逗号分隔):").pack(side=tk.LEFT)
self.keywords = tk.StringVar()
ttk.Entry(keyword_frame, textvariable=self.keywords, width=50).pack(side=tk.LEFT, fill=tk.X, expand=True)
# 进度条
self.progress = ttk.Progressbar(bottom_frame, length=300, mode='determinate')
self.progress.pack(fill=tk.X, pady=5)
# 操作按钮
button_frame = ttk.Frame(bottom_frame)
button_frame.pack(fill=tk.X)
ttk.Button(button_frame, text="查询并保存", command=self.search_and_save).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="保存原始数据", command=self.save_original_data).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="设置默认保存路径", command=self.set_default_save_path).pack(side=tk.LEFT, padx=5)
# 右侧内容 - 日志显示
ttk.Label(right_frame, text="操作日志:").grid(row=0, column=0, sticky=tk.W)
self.log_text = tk.Text(right_frame, width=50, height=40)
self.log_text.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 日志滚动条
log_scrollbar = ttk.Scrollbar(right_frame, orient="vertical", command=self.log_text.yview)
log_scrollbar.grid(row=1, column=1, sticky=(tk.N, tk.S))
self.log_text.configure(yscrollcommand=log_scrollbar.set)
# 配置网格权重,使得窗口可以正确调整大小
main_frame.columnconfigure(0, weight=3) # 左侧占比更大
main_frame.columnconfigure(1, weight=2) # 右侧占比较小
right_frame.columnconfigure(0, weight=1)
right_frame.rowconfigure(1, weight=1)
def toggle_settings(self):
"""切换统一设置和独立设置"""
if self.unified_settings.get():
self.unified_frame.grid()
self.individual_frame.grid_remove()
else:
self.unified_frame.grid_remove()
self.individual_frame.grid()
def browse_file(self):
file_path = filedialog.askopenfilename(
filetypes=[("Excel files", "*.xlsx *.xls")]
)
if file_path:
self.file_path.set(file_path)
self.update_sheet_info(file_path)
def update_sheet_info(self, file_path):
"""更新工作表信息"""
try:
# 读取Excel文件中的sheet信息
wb = load_workbook(file_path, read_only=True)
sheet_names = wb.sheetnames
wb.close()
self.log_message(f"发现 {len(sheet_names)} 个工作表")
# 清除现有的sheet设置
for widget in self.individual_frame.winfo_children():
widget.destroy()
self.sheet_settings.clear()
# 为每个sheet创建设置控件
for idx, sheet_name in enumerate(sheet_names):
frame = ttk.LabelFrame(self.individual_frame, text=f"工作表: {sheet_name}", padding="5")
frame.grid(row=idx, column=0, sticky=(tk.W, tk.E), pady=2)
ttk.Label(frame, text="表头行号:").grid(row=0, column=0, sticky=tk.W)
header_var = tk.StringVar(value="1")
ttk.Entry(frame, textvariable=header_var, width=10).grid(row=0, column=1)
ttk.Label(frame, text="关键词列号:").grid(row=0, column=2, sticky=tk.W, padx=(20,0))
keyword_var = tk.StringVar(value="1")
ttk.Entry(frame, textvariable=keyword_var, width=10).grid(row=0, column=3)
self.sheet_settings[sheet_name] = {
'header_row': header_var,
'keyword_column': keyword_var
}
self.individual_frame.grid(row=2, column=0, columnspan=2, pady=5, sticky=(tk.W, tk.E))
if self.unified_settings.get():
self.individual_frame.grid_remove()
except Exception as e:
self.log_message(f"读取工作表信息时发生错误:{str(e)}", "ERROR")
def set_default_save_path(self):
path = filedialog.askdirectory()
if path:
self.default_save_path = path
messagebox.showinfo("成功", f"默认保存路径已设置为:{path}")
def log_message(self, message, level="INFO"):
"""添加日志消息到日志窗口并立即更新显示"""
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
formatted_message = f"[{timestamp}] [{level}] {message}\n"
self.log_text.insert(tk.END, formatted_message)
self.log_text.see(tk.END) # 自动滚动到最新消息
self.root.update() # 使用update()而不是update_idletasks()来强制更新界面
def search_and_save(self):
try:
# 清空之前的日志
self.log_text.delete(1.0, tk.END)
# 获取输入参数
file_path = self.file_path.get()
keywords = [str(k.strip()) for k in self.keywords.get().split(",") if k.strip()]
if not all([file_path, keywords]):
self.log_message("错误:请填写所有必要信息", "ERROR")
messagebox.showerror("错误", "请填写所有必要信息")
return
# 获取工作表设置
sheet_configs = {}
if self.unified_settings.get():
# 使用统一设置
header_row = int(self.unified_header_row.get()) - 1
keyword_col = int(self.unified_keyword_column.get()) - 1
wb = load_workbook(file_path, read_only=True)
for sheet_name in wb.sheetnames:
sheet_configs[sheet_name] = {
'header_row': header_row,
'keyword_col': keyword_col
}
wb.close()
else:
# 使用独立设置
for sheet_name, settings in self.sheet_settings.items():
sheet_configs[sheet_name] = {
'header_row': int(settings['header_row'].get()) - 1,
'keyword_col': int(settings['keyword_column'].get()) - 1
}
self.log_message(f"开始处理文件: {file_path}")
self.log_message(f"搜索关键词: {keywords}")
total_steps = len(keywords) * 2
current_step = 0
# 为每个关键词处理
for keyword in keywords:
self.log_message(f"\n开始处理关键词: {keyword}")
# 创建安全的文件名
safe_keyword = "".join(c for c in keyword if c.isalnum() or c in (' ', '-', '_'))
save_path = os.path.join(self.default_save_path, f"查询结果_{safe_keyword}.xlsx")
# 复制整个文件
self.log_message(f"正在复制文件...")
shutil.copy2(file_path, save_path)
self.log_message(f"已创建文件副本: {save_path}")
current_step += 1
self.progress['value'] = (current_step / total_steps) * 100
# 打开复制的文件
wb = load_workbook(save_path)
total_sheets = len(wb.sheetnames)
# 处理每个sheet
for sheet_idx, sheet_name in enumerate(wb.sheetnames, 1):
config = sheet_configs[sheet_name]
self.log_message(f"\n正在处理工作表 ({sheet_idx}/{total_sheets}): {sheet_name}")
self.log_message(f"使用设置 - 表头行号: {config['header_row'] + 1}, 关键词列号: {config['keyword_col'] + 1}")
# 读取原始数据用于查询
df = pd.read_excel(file_path, sheet_name=sheet_name, header=config['header_row'])
# 过滤数据
filtered_df = df[df.iloc[:, config['keyword_col']].astype(str) == keyword]
# 获当前sheet
sheet = wb[sheet_name]
# 保留表头行,删除其他所有行
if sheet.max_row > config['header_row'] + 1:
sheet.delete_rows(config['header_row'] + 2,
sheet.max_row - (config['header_row'] + 1))
if not filtered_df.empty:
self.log_message(f"找到 {len(filtered_df)} 行匹配数据")
# 添加过滤后的数据
for row_idx, (_, row) in enumerate(filtered_df.iterrows(), 1):
if row_idx % 100 == 0:
self.log_message(f"已写入 {row_idx}/{len(filtered_df)} 行...")
sheet.append(list(row))
else:
self.log_message(f"未找到匹配数据", "WARNING")
# 更新进度条
sheet_progress = (sheet_idx / total_sheets) * (100 / total_steps)
self.progress['value'] = ((current_step - 1) / total_steps * 100) + sheet_progress
self.root.update()
# 保存文件
wb.save(save_path)
self.log_message(f"关键词 '{keyword}' 的结果文件处理完成!", "SUCCESS")
current_step += 1
self.progress['value'] = (current_step / total_steps) * 100
self.log_message("\n所有处理完成!", "SUCCESS")
messagebox.showinfo("成功", "所有关键词的查询结果已保存完成!")
except Exception as e:
error_msg = f"发生错误:{str(e)}"
self.log_message(error_msg, "ERROR")
self.log_message(traceback.format_exc(), "ERROR")
messagebox.showerror("错误", error_msg)
finally:
self.progress['value'] = 0
def save_original_data(self):
try:
file_path = self.file_path.get()
if not file_path:
self.log_message("错误:请先选择Excel文件", "ERROR")
messagebox.showerror("错误", "请先选择Excel文件")
return
save_path = os.path.join(self.default_save_path, "原始数据备份.xlsx")
self.log_message(f"正在保存原始数据到: {save_path}")
import xlwings as xw
wb = xw.Book(file_path)
wb.save(save_path)
wb.close()
self.log_message("原始数据保存完成!", "SUCCESS")
messagebox.showinfo("成功", f"原始数据已保存至:{save_path}")
except Exception as e:
error_msg = f"保存原始数据时发生错误:{str(e)}"
self.log_message(error_msg, "ERROR")
self.log_message(traceback.format_exc(), "ERROR")
messagebox.showerror("错误", error_msg)
if __name__ == "__main__":
root = tk.Tk()
app = ExcelQueryApp(root)
root.mainloop()