frame页面的隐藏和打开左侧和上方

本文介绍了如何利用jQuery实现iframe的大小变化、隐藏与显示功能,包括调整iframe的行数和列数,以及通过点击按钮控制iframe的可见状态。
本示例主要通过jquery改变frame的大小,实现隐藏和展示功能
1、index.jsp

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">

<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>

<frameset rows="80,*" cols="*" frameborder="no" border="0" framespacing="0">
<frame src="top.jsp" name="top" scrolling="No" noresize="noresize" id="top" />
<frameset cols="100,*" frameborder="no" border="0" framespacing="0">
<frame src="a.jsp" name="a" scrolling="No" noresize="noresize" id="a" />
<frame src="b.jsp" name="b" id="b" />
</frameset>
</frameset>
<noframes><body>
</body>
</noframes>
</html>



2、a.jsp页面

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@ page import="java.util.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
<!-- 引入jquery的库 -->
<script type="text/javascript" src="js/jquery-1.7.1.js"></script>
<script type="text/javascript">
$(document).ready(function (){
$("#aId").click(function(){
if($("#left_staus").val() == "打开"){
$("#left_staus").val("关闭");
closeFrameset();
}else if($("#left_staus").val() == "关闭"){
$("#left_staus").val("打开");
openFrameset();
}
})
});
function closeFrameset(){
var fs = parent.document.getElementsByTagName("frameset")[1];
fs.cols = "25,*";
document.getElementById("aId").innerHTML = "a页面打开";
}
function openFrameset(){
var fs = parent.document.getElementsByTagName("frameset")[1];
fs.cols = "65,*";
document.getElementById("aId").innerHTML = "a页面关闭";
}
</script>
</head>
<body bgcolor="red">
<div id="aId">a页面打开</div>
<input type="hidden" name="left_staus" id="left_staus" value="打开">
</body>
</html>


3、b.jsp页面

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body bgcolor="green">
<div id="b">b页面</div>
</body>
</html>


4、top.jsp页面

<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>top.jsp</title>
<!-- 引入jquery的库 -->
<script type="text/javascript" src="js/jquery-1.7.1.js"></script>
<script type="text/javascript">
$(document).ready(function (){
$("#top").click(function(){
if($("#top_staus").val() == "打开"){
$("#top_staus").val("关闭");
closeFrameset();
}else if($("#top_staus").val() == "关闭"){
$("#top_staus").val("打开");
openFrameset();
}
})
});
function closeFrameset(){
var fs = parent.document.getElementsByTagName("frameset")[0];
fs.rows = "25,*";
document.getElementById("top").innerHTML = "top页面打开";
}
function openFrameset(){
var fs = parent.document.getElementsByTagName("frameset")[0];
fs.rows = "65,*";
document.getElementById("top").innerHTML = "top页面关闭";
}
</script>
</head>
<body bgcolor="blue">
<div id="top">top页面打开</div>
<input type="hidden" name="top_staus" id="top_staus" value="打开">
</body>
</html>


测试浏览器:ie7
我这有两段代码,帮我合在一起,功能全都要,第一段代码import tkinter as tk from tkinter import filedialog, messagebox, ttk import pandas as pd import os import subprocess class ExcelViewerApp: def __init__(self, root): self.root = root self.root.title("TPC效率化工具") self.root.geometry("1200x800") # 使用网格布局的主容器 self.main_frame = tk.Frame(root, bg='#f0f0f0', padx=15, pady=15) self.main_frame.pack(fill=tk.BOTH, expand=True) # 配置网格 self.main_frame.columnconfigure(0, weight=0) self.main_frame.columnconfigure(1, weight=1) self.main_frame.columnconfigure(2, weight=0) self.main_frame.columnconfigure(3, weight=0) # === 主表控件 === # 文件路径行 tk.Label(self.main_frame, text="主表文件路径:", bg='#f0f0f0').grid(row=0, column=0, sticky='w', padx=(0, 5)) self.path_label = tk.Label( self.main_frame, text="未选择主表文件", anchor='w', relief=tk.SUNKEN, bg="#ffffff", padx=5, pady=5 ) self.path_label.grid(row=0, column=1, sticky='ew', padx=(0, 10)) # 工作表行 tk.Label(self.main_frame, text="主表工作表:", bg='#f0f0f0').grid(row=1, column=0, sticky='w', padx=(0, 5), pady=(10, 0)) self.sheet_label = tk.Label( self.main_frame, text="未选择主表工作表", anchor='w', relief=tk.SUNKEN, bg="#ffffff", padx=5, pady=5 ) self.sheet_label.grid(row=1, column=1, sticky='ew', padx=(0, 10), pady=(10, 0)) # 打开文件按钮 self.open_btn = tk.Button( self.main_frame, text="打开主表文件", command=self.open_excel, bg='#f0f0f0', fg='black', padx=15, pady=8, width=10 ) self.open_btn.grid(row=0, column=2, rowspan=2, sticky='ns', padx=(0, 5)) # 选择主表按钮 self.select_btn = tk.Button( self.main_frame, text="选择主表", command=self.load_excel, bg='#f0f0f0', fg='black', padx=15, pady=8, width=10 ) self.select_btn.grid(row=0, column=3, rowspan=2, sticky='ns', padx=(0, 5)) # === 副表控件 === (添加在下方) tk.Label(self.main_frame, text="副表文件路径:", bg='#f0f0f0').grid(row=2, column=0, sticky='w', padx=(0, 5), pady=(20, 0)) self.aux_path_label = tk.Label( self.main_frame, text="未选择副表文件", anchor='w', relief=tk.SUNKEN, bg="#ffffff", padx=5, pady=5 ) self.aux_path_label.grid(row=2, column=1, sticky='ew', padx=(0, 10), pady=(20, 0)) tk.Label(self.main_frame, text="副表工作表:", bg='#f0f0f0').grid(row=3, column=0, sticky='w', padx=(0, 5), pady=(10, 0)) self.aux_sheet_label = tk.Label( self.main_frame, text="未选择副表工作表", anchor='w', relief=tk.SUNKEN, bg="#ffffff", padx=5, pady=5 ) self.aux_sheet_label.grid(row=3, column=1, sticky='ew', padx=(0, 10), pady=(10, 0)) # 打开副表文件按钮 self.aux_open_btn = tk.Button( self.main_frame, text="打开副表文件", command=self.open_aux_excel, bg='#f0f0f0', fg='black', padx=15, pady=8, width=10 ) self.aux_open_btn.grid(row=2, column=2, rowspan=2, sticky='ns', padx=(0, 5), pady=(20, 0)) # 选择副表按钮 self.aux_select_btn = tk.Button( self.main_frame, text="选择副表", command=self.load_aux_excel, bg='#f0f0f0', fg='black', padx=15, pady=8, width=10 ) self.aux_select_btn.grid(row=2, column=3, rowspan=2, sticky='ns', padx=(0, 5), pady=(20, 0)) # 使用数组存储数据 self.dataset = [] # 主表数据 self.aux_dataset = [] # 副表数据 # 窗口居中 self.center_window(self.root) # 添加对比按钮 self.compare_btn = tk.Button( self.main_frame, text="对比数据", command=self.compare_data, bg='#4CAF50', fg='white', padx=15, pady=8, width=15 ) self.compare_btn.grid(row=4, column=1, columnspan=2, pady=20) # 添加过滤控制区域(在结果区域上方) self.filter_frame = tk.Frame(root, bg='#f0f0f0') self.filter_frame.pack(fill=tk.X, padx=10, pady=(0,5)) # 添加两个过滤复选框 self.show_correct_var = tk.BooleanVar(value=True) self.show_error_var = tk.BooleanVar(value=True) cb_correct = tk.Checkbutton( self.filter_frame, text="显示正确", variable=self.show_correct_var, command=self.filter_results, bg='#f0f0f0' ) cb_correct.pack(side=tk.LEFT, padx=(10,5)) cb_error = tk.Checkbutton( self.filter_frame, text="显示错误", variable=self.show_error_var, command=self.filter_results, bg='#f0f0f0' ) cb_error.pack(side=tk.LEFT, padx=(5,10)) # 创建结果显示区域 self.result_frame = tk.Frame(root, bg='#f0f0f0') self.result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0,10)) # 添加Treeview显示结果 self.tree = ttk.Treeview(self.result_frame) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 添加滚动条 scrollbar = ttk.Scrollbar(self.result_frame, orient="vertical", command=self.tree.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.tree.configure(yscrollcommand=scrollbar.set) # 状态标签 self.status_label = tk.Label(root, text="就绪", bg='#f0f0f0', anchor='w') self.status_label.pack(fill=tk.X, padx=10, pady=5) def center_window(self, window): window.update_idletasks() width = window.winfo_width() height = window.winfo_height() x = (window.winfo_screenwidth() // 2) - (width // 2) y = (window.winfo_screenheight() // 2) - (height // 2) window.geometry(f"+{x}+{y}") # ===== 主表功能 ===== def open_excel(self): """打开主表文件""" file_path = self.path_label.cget("text") if not file_path or file_path == "未选择主表文件": messagebox.showwarning("警告", "请先选择主表Excel文件") return try: if os.name == 'nt': os.startfile(file_path) elif os.name == 'posix': subprocess.call(('open', file_path)) else: subprocess.call(('xdg-open', file_path)) except Exception as e: messagebox.showerror("错误", f"打开主表文件失败: {str(e)}") def load_excel(self): """加载主表Excel""" file_path = filedialog.askopenfilename( title="选择主表Excel文件", filetypes=[("Excel文件", "*.xlsx *.xls")] ) if not file_path: return try: self.path_label.config(text=file_path) self.select_main_sheet(file_path) except Exception as e: messagebox.showerror("错误", f"读取主表文件失败: {str(e)}") def select_main_sheet(self, file_path): """选择主表工作表""" try: xl = pd.ExcelFile(file_path) sheet_names = xl.sheet_names selector = tk.Toplevel(self.root) selector.title("选择主表工作表") selector.geometry("300x150") tk.Label(selector, text="请选择主表工作表:").pack(pady=10) sheet_var = tk.StringVar(selector) combobox = ttk.Combobox( selector, textvariable=sheet_var, values=sheet_names, state="readonly", width=40 ) combobox.pack(pady=10, padx=20, fill=tk.X) combobox.current(0) tk.Button( selector, text="确认选择", command=lambda: self.process_main_sheet_selection( file_path, sheet_var.get(), selector ), bg='#f0f0f0', fg='black', padx=10, pady=5 ).pack(pady=15) self.center_window(selector) except Exception as e: messagebox.showerror("错误", f"读取主表文件失败: {str(e)}") def process_main_sheet_selection(self, file_path, sheet_name, selector): """处理主表工作表选择结果""" try: df = pd.read_excel(file_path, sheet_name=sheet_name, header=0, skiprows=list(range(0,9))) self.dataset = df.to_dict(orient='records') row_count = len(self.dataset) col_count = len(df.columns) if row_count > 0 else 0 self.sheet_label.config(text=f"{sheet_name} ({row_count}行×{col_count}列)") selector.destroy() messagebox.showinfo("加载成功", f"主表工作表 [{sheet_name}] 已载入\n" f"数据维度: {row_count}行 × {col_count}列" ) print(f"主表数据示例: {self.dataset[0] if self.dataset else '空'}") except Exception as e: messagebox.showerror("错误", f"加载主表数据失败: {str(e)}") # ===== 副表功能 ===== def open_aux_excel(self): """打开副表文件""" file_path = self.aux_path_label.cget("text") if not file_path or file_path == "未选择副表文件": messagebox.showwarning("警告", "请先选择副表Excel文件") return try: if os.name == 'nt': os.startfile(file_path) elif os.name == 'posix': subprocess.call(('open', file_path)) else: subprocess.call(('xdg-open', file_path)) except Exception as e: messagebox.showerror("错误", f"打开副表文件失败: {str(e)}") def load_aux_excel(self): """加载副表Excel""" file_path = filedialog.askopenfilename( title="选择副表Excel文件", filetypes=[("Excel文件", "*.xlsx *.xls")] ) if not file_path: return try: self.aux_path_label.config(text=file_path) self.select_aux_sheet(file_path) except Exception as e: messagebox.showerror("错误", f"读取副表文件失败: {str(e)}") def select_aux_sheet(self, file_path): """选择副表工作表""" try: xl = pd.ExcelFile(file_path) sheet_names = xl.sheet_names selector = tk.Toplevel(self.root) selector.title("选择副表工作表") selector.geometry("300x150") tk.Label(selector, text="请选择副表工作表:").pack(pady=10) sheet_var = tk.StringVar(selector) combobox = ttk.Combobox( selector, textvariable=sheet_var, values=sheet_names, state="readonly", width=40 ) combobox.pack(pady=10, padx=20, fill=tk.X) combobox.current(0) tk.Button( selector, text="确认选择", command=lambda: self.process_aux_sheet_selection( file_path, sheet_var.get(), selector ), bg='#f0f0f0', fg='black', padx=10, pady=5 ).pack(pady=15) self.center_window(selector) except Exception as e: messagebox.showerror("错误", f"读取副表文件失败: {str(e)}") def process_aux_sheet_selection(self, file_path, sheet_name, selector): """处理副表工作表选择结果""" try: #df = pd.read_excel(file_path, sheet_name=sheet_name, header=0, skiprows=list(range(0,9))) df = pd.read_excel(file_path, sheet_name=sheet_name, header=0) self.aux_dataset = df.to_dict(orient='records') row_count = len(self.aux_dataset) col_count = len(df.columns) if row_count > 0 else 0 self.aux_sheet_label.config(text=f"{sheet_name} ({row_count}行×{col_count}列)") selector.destroy() messagebox.showinfo("加载成功", f"副表工作表 [{sheet_name}] 已载入\n" f"数据维度: {row_count}行 × {col_count}列" ) print(f"副表数据示例: {self.aux_dataset[0] if self.aux_dataset else '空'}") except Exception as e: messagebox.showerror("错误", f"加载副表数据失败: {str(e)}") def compare_data(self): """对比主表副表数据并设置背景色,仅显示对比共有列,并显示具体错误列""" if not self.dataset or not self.aux_dataset: messagebox.showwarning("警告", "请先加载主表副表数据") return # 获取主表副表的列名 main_columns = set(self.dataset[0].keys()) if self.dataset else set() aux_columns = set(self.aux_dataset[0].keys()) if self.aux_dataset else set() # 获取两个表共有的列(不包括Z2列,因为Z2用于匹配) common_columns = sorted(main_columns & aux_columns - {'Z2'}) # 确保Z2列存在(用于匹配的关键列) if 'Z2' not in main_columns or 'Z2' not in aux_columns: messagebox.showerror("错误", "主表或副表缺少Z2列,无法进行对比") return # 配置Treeview列 self.tree["columns"] = ["Z2"] + common_columns self.tree["show"] = "headings" # 设置列头 self.tree.heading("Z2", text="Z2(匹配状态)") for col in common_columns: self.tree.heading(col, text=col) # 设置列宽(Z2列宽更大以显示状态信息) self.tree.column("Z2", width=200, anchor=tk.CENTER) for col in common_columns: self.tree.column(col, width=100, anchor=tk.CENTER) # 清空现有数据 for item in self.tree.get_children(): self.tree.delete(item) # 创建用于设置颜色的tag self.tree.tag_configure('match', background='#DFF0D8') # 绿色 self.tree.tag_configure('mismatch', background='#F8D7DA') # 红色 self.tree.tag_configure('not_found', background='#F8D7DA') # 红色 # 统计结果 match_count = 0 mismatch_count = 0 not_found_count = 0 # 创建从Z2值到副表行的映射(提高查找效率) aux_map = {} for aux_row in self.aux_dataset: z2_value = aux_row.get('Z2', None) if z2_value is not None: aux_map.setdefault(z2_value, []).append(aux_row) # 记录所有不匹配详情(Z2值→不匹配列列表) mismatch_details = {} # 初始化存储行状态的数据结构 self.all_items = [] # 存储所有行ID self.row_status = {} # 存储行状态 # 遍历主表每一行 for main_row in self.dataset: main_z2 = main_row.get('Z2', None) found_in_aux = False all_matched = True mismatched_columns = [] # 存储当前行不匹配的列名 # 查找匹配的副表行 matching_aux_rows = aux_map.get(main_z2, []) if main_z2 is not None else [] if matching_aux_rows: found_in_aux = True # 检查所有匹配行中是否有完全匹配的 row_match = False for aux_row in matching_aux_rows: current_match = True # 只比较共有列 for col in common_columns: if col in main_row and col in aux_row: if main_row[col] != aux_row[col]: current_match = False # 记录不匹配的列(避免重复) if col not in mismatched_columns: mismatched_columns.append(col) if current_match: row_match = True mismatched_columns = [] # 完全匹配时清空不匹配列 break all_matched = row_match # 准备Treeview行数据 if not found_in_aux: z2_display = f"{main_z2} ✗ (未找到)" not_found_count += 1 status = 'not_found' elif all_matched: z2_display = f"{main_z2} ✓" match_count += 1 status = 'match' else: # 显示不匹配的具体列(最多显示3个) error_cols = ", ".join(mismatched_columns[:3]) if len(mismatched_columns) > 3: error_cols += f" 等{len(mismatched_columns)}处" z2_display = f"{main_z2} ✗ ({error_cols})" mismatch_count += 1 mismatch_details[main_z2] = mismatched_columns status = 'mismatch' values = [z2_display] + [main_row.get(col, '') for col in common_columns] # 插入行到Treeview item_id = self.tree.insert("", "end", values=values) # 根据匹配情况设置背景色 self.tree.item(item_id, tags=(status,)) # 保存行ID状态用于过滤 self.all_items.append(item_id) self.row_status[item_id] = status # 更新状态(添加错误详情) detail_text = "" if mismatch_details: sample_errors = "\n".join( [f"Z2={k}: {', '.join(v[:3])}{'...' if len(v)>3 else ''}" for k, v in list(mismatch_details.items())[:3]] ) if len(mismatch_details) > 3: sample_errors += f"\n...等{len(mismatch_details)}行存在差异" detail_text = f"\n错误详情示例:\n{sample_errors}" self.status_label.config( text=(f"对比完成 | 匹配: {match_count}行 | 不匹配: {mismatch_count}行 | 未找到: {not_found_count}行 | " f"共有列: {', '.join(common_columns)}{detail_text}") ) # 初始显示所有行 self.filter_results() def filter_results(self): """根据复选框状态过滤结果显示""" if not hasattr(self, 'all_items') or not self.all_items: return show_correct = self.show_correct_var.get() show_error = self.show_error_var.get() # 处理所有行的显示/隐藏 for item_id in self.all_items: status = self.row_status[item_id] if status == 'match' and show_correct: self.tree.reattach(item_id, '', 'end') elif status in ['mismatch', 'not_found'] and show_error: self.tree.reattach(item_id, '', 'end') else: self.tree.detach(item_id) if __name__ == "__main__": root = tk.Tk() app = ExcelViewerApp(root) root.mainloop() 第二段代码import tkinter as tk from tkinter import ttk, filedialog, messagebox import pandas as pd import os import subprocess import itertools import platform from openpyxl import load_workbook class EnhancedSheetSelector: def __init__(self, root): self.root = root self.root.title("表格页面选择器") self.root.geometry("600x450") # 创建主框架 main_frame = ttk.Frame(root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 文件选择区域 file_frame = ttk.LabelFrame(main_frame, text="文件选择") file_frame.pack(fill=tk.X, padx=5, pady=5) self.file_path = tk.StringVar() ttk.Entry(file_frame, textvariable=self.file_path, state="readonly", width=50).pack(side=tk.LEFT, padx=5, pady=5) ttk.Button(file_frame, text="浏览...", command=self.load_file).pack(side=tk.LEFT, padx=5, pady=5) # 在文件选择区域添加"打开文件"按钮 ttk.Button(file_frame, text="打开文件", command=self.open_file).pack(side=tk.LEFT, padx=5, pady=5) # 新增按钮 # 操作按钮区域 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill=tk.X, padx=5, pady=5) # 左侧按钮组 - 新增调用按钮 left_btn_frame = ttk.Frame(btn_frame) left_btn_frame.pack(side=tk.LEFT) # 新增调用外部脚本按钮 - 放置在左下角 ttk.Button( left_btn_frame, text="生成code", command=self.call_external_script ).pack(side=tk.LEFT, padx=2) # ==== 新增按钮:对比写入 ==== ttk.Button(left_btn_frame, text="条件写入", command=self.compare_and_write).pack(side=tk.LEFT, padx=2) # 工作表列表区域 list_frame = ttk.LabelFrame(main_frame, text="工作表列表") list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建带复选框的Treeview self.tree = ttk.Treeview( list_frame, columns=("selected", "sheet_name"), show="headings", selectmode="none" ) # 设置列 self.tree.heading("selected", text="选择") self.tree.heading("sheet_name", text="工作表名称") self.tree.column("selected", width=60, anchor="center") self.tree.column("sheet_name", width=400, anchor="w") # 添加滚动条 scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(fill=tk.BOTH, expand=True) # 绑定复选框事件 self.tree.bind("<Button-1>", self.on_click) # 存储工作表数据 self.sheets = [] self.check_states = {} self.check_states = {} # 确保复选框状态字典存在 self.selected_item = None # 关键:添加单选状态跟踪 def load_file(self): file_path = filedialog.askopenfilename( title="选择表格文件", filetypes=[("Excel文件", "*.xlsx *.xls"), ("CSV文件", "*.csv"), ("所有文件", "*.*")] ) if not file_path: return self.file_path.set(file_path) try: # 读取表格文件的工作表名称 if file_path.endswith(('.xlsx', '.xls')): self.sheets = pd.ExcelFile(file_path).sheet_names elif file_path.endswith('.csv'): self.sheets = ["CSV单页"] # CSV文件只有一个工作表 else: self.sheets = ["未知格式"] # 清空Treeview for item in self.tree.get_children(): self.tree.delete(item) self.check_states = {} # 插入新数据 for sheet in self.sheets: item_id = self.tree.insert("", tk.END, values=("□", sheet)) self.check_states[item_id] = False except Exception as e: messagebox.showerror("错误", f"读取文件失败:\n{str(e)}") def open_file(self): # 新增文件打开方法 file_path = self.file_path.get() if not file_path: messagebox.showwarning("提示", "请先选择Excel文件") return if not os.path.exists(file_path): messagebox.showerror("错误", "文件不存在或路径无效") return try: # 根据操作系统使用对应方式打开文件 system = platform.system() if system == "Windows": os.startfile(file_path) # Windows直接打开 elif system == "Darwin": # macOS subprocess.run(["open", file_path]) else: # Linux及其他 subprocess.run(["xdg-open", file_path]) except Exception as e: messagebox.showerror("打开失败", f"无法打开文件:\n{str(e)}") # 在类 __init__ 中添加: self.selected_item = None def on_click(self, event): region = self.tree.identify_region(event.x, event.y) if region == "cell": column = self.tree.identify_column(event.x) item = self.tree.identify_row(event.y) if column == "#1": # 判断是否点击当前选中项 if item == self.selected_item: # 取消选中当前项 self.check_states[item] = False self.selected_item = None self.tree.item(item, values=("□", self.tree.item(item, "values")[1])) else: # 取消之前选中的项 if self.selected_item is not None: prev_item = self.selected_item self.check_states[prev_item] = False self.tree.item(prev_item, values=("□", self.tree.item(prev_item, "values")[1])) # 选中新项 self.check_states[item] = True self.selected_item = item self.tree.item(item, values=("■", self.tree.item(item, "values")[1])) def call_external_script(self): selected_sheets = [] for item, state in self.check_states.items(): if state: sheet_name = self.tree.item(item, "values")[1] selected_sheets.append(sheet_name) if not selected_sheets: messagebox.showwarning("提示", "请至少选择一个工作表") return file_path = self.file_path.get() if not file_path: messagebox.showwarning("提示", "请先选择Excel文件") return try: # 先加载整个工作簿(用于后续写入多个工作表) wb = load_workbook(file_path) # 值清洗函数 def clean_value(val): try: float_val = float(val) return str(int(float_val)) except ValueError: return val # 值拆分函数 def split_values(val): if pd.isna(val): return [] parts = [v.strip() for v in str(val).split(',')] return [v for v in parts if v] # 处理每个工作表(独立处理) for sheet in selected_sheets: # 为每个工作表初始化独立的数据结构 code_columns = [] # 当前工作表包含'code'的列名 list_of_code_arrays = [] # 存储当前工作表所有code列的数组 list_mutex = [] dict_last_code_to_cols = {} df = pd.read_excel(file_path, sheet_name=sheet) # 验证必要列存在 required_columns = ['互斥1'] if not all(col in df.columns for col in required_columns): messagebox.showwarning("警告", f"工作表 '{sheet}' 缺少必要列") continue # 收集当前工作表的code列 code_columns = [col for col in df.columns if 'code' in col.lower()] list_of_code_arrays = [[] for _ in range(len(code_columns))] all_cols = df.columns.tolist() mutex1_idx = all_cols.index('互斥1') if '互斥1' in all_cols else -1 # 找出最后一个code列的位置 last_code_idx = -1 for col in reversed(all_cols): if 'code' in col.lower(): last_code_idx = all_cols.index(col) break # 确定中间列范围 middle_cols = [] if last_code_idx != -1 and mutex1_idx != -1 and last_code_idx < mutex1_idx: middle_cols = all_cols[last_code_idx+1:mutex1_idx] # 处理当前工作表的每一行 for _, row in df.iterrows(): # 处理所有code列 for col_idx, col_name in enumerate(code_columns): if col_name in df.columns: vals = split_values(row.get(col_name, '')) list_of_code_arrays[col_idx].extend(vals) # 处理中间列关联 if last_code_idx != -1: last_code_col = all_cols[last_code_idx] last_code_vals = split_values(row.get(last_code_col, '')) for col in middle_cols: if col in df.columns: col_vals = split_values(row.get(col, '')) for col_val in col_vals: clean_col_val = clean_value(col_val) for code_val in last_code_vals: if code_val not in dict_last_code_to_cols: dict_last_code_to_cols[code_val] = [] if clean_col_val not in dict_last_code_to_cols[code_val]: dict_last_code_to_cols[code_val].append(clean_col_val) # 处理互斥列 if '互斥1' in df.columns: mutex1_val = str(row.get('互斥1', '')).strip() if mutex1_val: mutex2_val = str(row.get('互斥2', '')).strip() if '互斥2' in df.columns else "" clean_key = clean_value(mutex1_val) clean_val = clean_value(mutex2_val) if mutex2_val else "" list_mutex.append((clean_key, clean_val)) # 定义固定名称的结果表 RESULT_SHEETS = { "arr": "数组结果", "mutex": "互斥字典", "map": "code映射" } # 先删除所有旧的固定名称结果表 for sheet_name in RESULT_SHEETS.values(): if sheet_name in wb.sheetnames: del wb[sheet_name] # 初始化结果表数据结构(多个工作表的结果将合并到这里) combined_arr = {} combined_mutex = [] combined_map = {} # 处理每个工作表(结果合并到统一的表中) for sheet in selected_sheets: # ... [原始处理逻辑保持不变,包括读取数据、处理互斥、映射等] ... # 将当前工作表的结果合并到统一结构中 # 合并数组结果(按列合并) for col_idx, col_name in enumerate(code_columns): if col_name not in combined_arr: combined_arr[col_name] = [] combined_arr[col_name].extend(list_of_code_arrays[col_idx]) # 合并互斥字典(直接追加) combined_mutex.extend(list_mutex) # 合并code映射(合并相同键的值) for code_val, value_list in dict_last_code_to_cols.items(): if code_val not in combined_map: combined_map[code_val] = [] combined_map[code_val].extend(value_list) # 写入统一的数组结果表 ws_arr = wb.create_sheet(RESULT_SHEETS["arr"]) ws_arr.append([f"{col}数组" for col in combined_arr.keys()]) max_len = max(len(arr) for arr in combined_arr.values()) or 1 for i in range(max_len): row_data = [arr[i] if i < len(arr) else "" for arr in combined_arr.values()] ws_arr.append(row_data) # 写入统一的互斥字典表 ws_mutex = wb.create_sheet(RESULT_SHEETS["mutex"]) ws_mutex.append(['互斥1(键)', '互斥2(值)']) if combined_mutex: for pair in combined_mutex: ws_mutex.append([pair[0], pair[1]]) else: ws_mutex.append(["无数据", ""]) # 写入统一的code映射表 ws_map = wb.create_sheet(RESULT_SHEETS["map"]) ws_map.append(['最后一个code值', '关联中间值']) if combined_map: for code_val, value_list in combined_map.items(): for val in value_list: ws_map.append([code_val, val]) else: ws_map.append(["无数据", ""]) # 处理数据结构并写入check表 expanded_dataset = self.process_data_structures( list_of_code_arrays=list_of_code_arrays, list_mutex=list_mutex, dict_last_code_to_cols=dict_last_code_to_cols, code_columns=code_columns ) # 使用固定表名"check"替代动态生成的表名 check_sheet_name = "check" # 固定表名 if check_sheet_name in wb.sheetnames: ws_check = wb[check_sheet_name] start_row = ws_check.max_row + 1 else: # 首次执行时创建固定名称的表 ws_check = wb.create_sheet(check_sheet_name) headers = ['Z2'] + ["无"] * len(code_columns) ws_check.append(headers) start_row = 2 for row_data in expanded_dataset: ws_check.append(row_data) # 修正的统计信息部分 - 使用列表追加方式 result_info = [f"{sheet}处理完成:"] result_info += [f"{col}数组: {len(arr)}元素" for col, arr in zip(code_columns, list_of_code_arrays)] result_info.append(f"互斥数组: {len(list_mutex)}元素") result_info.append(f"code映射: {len(dict_last_code_to_cols)}个键") print("\n".join(result_info)) # 控制台输出 # 最终保存文件 wb.save(file_path) messagebox.showinfo("处理完成", f"成功处理 {len(selected_sheets)} 个工作表") except Exception as e: import traceback error_details = f"错误类型: {type(e).__name__}\n错误信息: {str(e)}\n堆栈跟踪:\n{traceback.format_exc()}" messagebox.showerror("严重错误", f"执行失败:\n{error_details}") def process_data_structures(self, list_of_code_arrays, list_mutex, dict_last_code_to_cols, code_columns): # 处理空数组 arrays = [arr or [] for arr in list_of_code_arrays] # 动态笛卡尔积 dataset = [] for combo in itertools.product(*arrays): combined_str = ''.join(combo) # 动态拼接所有code值 # 行结构: [组合字符串, code值1, code值2, ...] row = (combined_str,) + combo dataset.append(row) # 排除互斥组合 excluded_rows = [ row for row in dataset if any((mutex[0] in row) and (mutex[1] in row) for mutex in list_mutex) ] filtered_dataset = [row for row in dataset if row not in excluded_rows] # ==== 修复:确保正确匹配最后一个code值中间列值 ==== expanded_dataset = [] last_col_idx = len(code_columns) - 1 # 最后一个code列的索引 for row in filtered_dataset: # 获取最后一个code列的值(row[0]是组合字符串) last_col_val = row[last_col_idx + 1] if len(row) > last_col_idx + 1 else None # 获取该值对应的所有中间列值 middle_vals = dict_last_code_to_cols.get(last_col_val, []) # 如果有中间列值,创建新的组合行 if middle_vals: for val in middle_vals: new_combined = f"{row[0]}-{val}" new_row = (new_combined,) + row[1:] # 保留原始code值 expanded_dataset.append(new_row) else: expanded_dataset.append(row) return expanded_dataset def compare_and_write(self): selected_sheets = [self.tree.item(item, "values")[1] for item, state in self.check_states.items() if state] if not selected_sheets: messagebox.showwarning("提示", "请至少选择一个工作表作为对比表") return file_path = self.file_path.get() if not file_path: messagebox.showwarning("提示", "请先选择Excel文件") return try: wb = load_workbook(file_path) if 'check' not in wb.sheetnames: messagebox.showerror("错误", "找不到check表,请先创建") return ws_check = wb['check'] # === 关键修复1:正确的列名收集 === all_headers = set() sheet_data = {} # 存储各表原始数据 for sheet in selected_sheets: if sheet in wb.sheetnames: temp_df = pd.read_excel(file_path, sheet_name=sheet) sheet_data[sheet] = temp_df.copy() all_headers.update(temp_df.columns[1:]) # 仅收集数据列名 # === 关键修复2:重建数据框架保证ID列存在 === df_contrast = pd.DataFrame(columns=['ID_COL'] + list(all_headers)) for sheet, df in sheet_data.items(): # 创建当前表的副本并重命名ID列 temp_df = df.copy() temp_df.rename(columns={df.columns[0]: 'ID_COL'}, inplace=True) # 对齐列结构 for col in all_headers: if col not in temp_df: temp_df[col] = None # 只保留需要的列 temp_df = temp_df[['ID_COL'] + list(all_headers)] df_contrast = pd.concat([df_contrast, temp_df], ignore_index=True) if df_contrast.empty: messagebox.showwarning("警告", "对比表数据为空") return # === 列名写入保持不变 === existing_headers = {ws_check.cell(1, col).value for col in range(5, ws_check.max_column+1) if ws_check.cell(1, col).value} start_col = 5 while start_col <= ws_check.max_column and ws_check.cell(1, start_col).value is not None: start_col += 1 for header in all_headers: if header not in existing_headers: ws_check.cell(row=1, column=start_col, value=header) start_col += 1 # === 关键修复3:增强数据匹配逻辑 === added_count = 0 # 将ID列转为字符串类型确保匹配 df_contrast['ID_COL'] = df_contrast['ID_COL'].astype(str) for row_idx in range(2, ws_check.max_row + 1): col_values = {} for idx, col in enumerate([2, 3, 4], start=1): value = ws_check.cell(row_idx, col).value col_values[f'col{idx}'] = str(value) if value is not None else "" # 查找匹配行(优先匹配col2,其次col3,最后col4) matched_row = None for col_key in ['col1', 'col2', 'col3']: if col_values[col_key]: match = df_contrast[df_contrast['ID_COL'] == col_values[col_key]] if not match.empty: matched_row = match.iloc[0] break if matched_row is not None: # 动态定位写入位置 write_col = 5 while write_col <= ws_check.max_column and ws_check.cell(row_idx, write_col).value is not None: write_col += 1 # 写入所有数据列(跳过ID列) data_cols = [col for col in df_contrast.columns if col != 'ID_COL'] for col_name in data_cols: if write_col > ws_check.max_column: break ws_check.cell(row_idx, write_col, value=matched_row[col_name]) write_col += 1 added_count += 1 wb.save(file_path) messagebox.showinfo("完成", f"成功写入 {added_count} 行对比数据到check表") except Exception as e: messagebox.showerror("错误", f"对比写入失败: {str(e)}") if __name__ == "__main__": root = tk.Tk() app = EnhancedSheetSelector(root) root.mainloop()
09-12
# -*- coding: utf-8 -*- import os import re import sys import time import threading import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter.font import Font import fnmatch import subprocess import shutil import docx from openpyxl import load_workbook import PyPDF2 import zipfile import chardet import xlrd import tempfile class EnhancedFileSearchApp: def __init__(self, master): self.master = master master.title("🎯 高级文件搜索工具") master.geometry("1200x800") master.minsize(1000, 700) # 设置现代主题颜色方案 theme = "vista" if sys.platform == "win32" else "aqua" self.style = ttk.Style() self.style.theme_use(theme) # 自定义配色方案 self.colors = { "bg": "#f5f6fa", "header": "#3498db", "accent": "#2980b9", "warning": "#e74c3c", "success": "#2ecc71", "text": "#2c3e50", "highlight": "#f1c40f" } # 设置主窗口背景色 master.configure(bg=self.colors["bg"]) # 创建主框架 main_frame = ttk.Frame(master, padding=(15, 15)) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 创建搜索面板(顶部) search_frame = ttk.LabelFrame( main_frame, text="⚙️ 搜索选项", padding=(15, 10), style="Search.TLabelframe" ) search_frame.pack(side=tk.TOP, fill=tk.X, padx=0, pady=(0, 10)) # 修改为TOP布局 # 配置搜索面板样式 self.style.configure("Search.TLabelframe", background=self.colors["bg"], bordercolor=self.colors["accent"], lightcolor=self.colors["accent"]) self.style.configure("Search.TLabelframe.Label", font=("Arial", 10, "bold"), foreground=self.colors["header"]) # 搜索目录 dir_frame = ttk.Frame(search_frame) dir_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Label(dir_frame, text="📁 搜索目录:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.dir_entry = ttk.Entry(dir_frame, width=35) self.dir_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) self.dir_entry.insert(0, os.getcwd()) browse_btn = ttk.Button( dir_frame, text="浏览...", command=self.browse_directory, style="Accent.TButton" ) browse_btn.pack(side=tk.RIGHT) # 关键词 kw_frame = ttk.Frame(search_frame) kw_frame.pack(fill=tk.X, pady=5) ttk.Label(kw_frame, text="🔍 关键词:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.keyword_entry = ttk.Entry(kw_frame, width=40) self.keyword_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) # 文件过滤 filter_frame = ttk.Frame(search_frame) filter_frame.pack(fill=tk.X, pady=5) ttk.Label(filter_frame, text="📄 文件过滤:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.filter_entry = ttk.Entry(filter_frame, width=40) self.filter_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) self.filter_entry.insert(0, "*.c;*.h;*.prm;*.xlsx;*.xls;*.doc;*.docx;*.pdf") # 搜索选项 options_frame = ttk.Frame(search_frame) options_frame.pack(fill=tk.X, pady=10) self.case_var = tk.BooleanVar(value=False) case_chk = ttk.Checkbutton( options_frame, text="忽略大小写", variable=self.case_var, style="Custom.TCheckbutton" ) case_chk.pack(side=tk.LEFT, padx=(0, 15)) self.regex_var = tk.BooleanVar(value=False) regex_chk = ttk.Checkbutton( options_frame, text="正则表达式", variable=self.regex_var, style="Custom.TCheckbutton" ) regex_chk.pack(side=tk.LEFT, padx=(0, 15)) self.binary_var = tk.BooleanVar(value=False) binary_chk = ttk.Checkbutton( options_frame, text="包含二进制", variable=self.binary_var, style="Custom.TCheckbutton" ) binary_chk.pack(side=tk.LEFT, padx=(0, 15)) self.highlight_var = tk.BooleanVar(value=True) highlight_chk = ttk.Checkbutton( options_frame, text="关键字高亮", variable=self.highlight_var, style="Custom.TCheckbutton" ) highlight_chk.pack(side=tk.LEFT) # 配置选项样式 self.style.configure("Custom.TCheckbutton", font=("Arial", 9), foreground=self.colors["text"]) # 按钮组 btn_frame = ttk.Frame(search_frame) btn_frame.pack(fill=tk.X, pady=(15, 5)) self.search_btn = ttk.Button( btn_frame, text="🔎 开始搜索", command=self.start_search, style="Accent.TButton" ) self.search_btn.pack(side=tk.LEFT, padx=(0, 10)) self.stop_btn = ttk.Button( btn_frame, text="⏹️ 停止", command=self.stop_search, style="Warning.TButton", state=tk.DISABLED ) self.stop_btn.pack(side=tk.LEFT, padx=(0, 10)) self.export_btn = ttk.Button( btn_frame, text="💾 导出结果", command=self.export_results, style="Success.TButton" ) self.export_btn.pack(side=tk.LEFT) # 按钮样式 self.style.configure("Accent.TButton", font=("Arial", 10, "bold"), foreground="white", background=self.colors["accent"]) self.style.configure("Warning.TButton", foreground="white", background=self.colors["warning"]) self.style.configure("Success.TButton", foreground="white", background=self.colors["success"]) # 状态栏 status_frame = ttk.Frame(search_frame) status_frame.pack(fill=tk.X, pady=(10, 0)) self.status_label = ttk.Label( status_frame, text="🟢 就绪", foreground=self.colors["success"], font=("Arial", 9, "bold") ) self.status_label.pack(side=tk.LEFT) self.progress = ttk.Progressbar( status_frame, orient="horizontal", length=100, mode="determinate", style="Custom.Horizontal.TProgressbar" ) self.progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) # 进度条样式 self.style.configure("Custom.Horizontal.TProgressbar", thickness=20, background=self.colors["accent"], troughcolor=self.colors["bg"]) self.stats_label = ttk.Label( status_frame, text="", foreground=self.colors["text"], font=("Arial", 9) ) self.stats_label.pack(side=tk.RIGHT) # 创建结果面板 results_frame = ttk.Frame(main_frame) results_frame.pack(fill=tk.BOTH, expand=True) # 创建结果面板(在搜索面板下方) results_frame = ttk.Frame(main_frame) results_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=10) # 修改为TOP布局 # 分割窗格(文件列表预览左右排列) pane = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) pane.pack(fill=tk.BOTH, expand=True) # 文件列表(左侧) file_frame = ttk.LabelFrame(pane, text="📂 搜索结果", padding=10) pane.add(file_frame, weight=1) # 文件预览(右侧) preview_frame = ttk.LabelFrame(pane, text="🔍 预览内容", padding=10) pane.add(preview_frame, weight=2) # 创建Treeview显示文件列表 file_tree_frame = ttk.Frame(file_frame) file_tree_frame.pack(fill=tk.BOTH, expand=True) # 定义树状视图列时添加fullpath列 columns = ("filename", "size", "modified", "fullpath") self.file_tree = ttk.Treeview( file_tree_frame, columns=columns, show="headings", selectmode="browse" ) # 配置列时隐藏fullpath列 self.file_tree.column("fullpath", width=0, stretch=False) # 隐藏列 # 配置Treeview样式 self.style.configure("Custom.Treeview", font=("Arial", 9), rowheight=25) self.style.configure("Custom.Treeview.Heading", font=("Arial", 9, "bold"), background=self.colors["accent"], foreground="white") # 配置列 self.file_tree.heading("filename", text="文件名", anchor="w") self.file_tree.heading("size", text="大小", anchor="center") self.file_tree.heading("modified", text="修改时间", anchor="w") self.file_tree.column("filename", width=250, anchor="w") self.file_tree.column("size", width=80, anchor="center") self.file_tree.column("modified", width=150, anchor="w") scroll_y = ttk.Scrollbar(file_tree_frame, orient="vertical", command=self.file_tree.yview) scroll_x = ttk.Scrollbar(file_tree_frame, orient="horizontal", command=self.file_tree.xview) self.file_tree.configure(yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set) self.file_tree.grid(row=0, column=0, sticky="nsew") scroll_y.grid(row=0, column=1, sticky="ns") scroll_x.grid(row=1, column=0, sticky="ew") file_tree_frame.rowconfigure(0, weight=1) file_tree_frame.columnconfigure(0, weight=1) self.preview_text = scrolledtext.ScrolledText( preview_frame, wrap=tk.WORD, font=("Consolas", 10), padx=10, pady=10, bg="#FFFFFF", fg=self.colors["text"], relief="flat" ) self.preview_text.pack(fill=tk.BOTH, expand=True) # 配置高亮标签 self.preview_text.tag_configure("highlight", background=self.colors["highlight"]) self.preview_text.tag_configure("match", background="#c8e6c9") self.preview_text.tag_configure("header", foreground=self.colors["success"], font=("Arial", 10, "bold")) self.preview_text.tag_configure("warning", foreground=self.colors["warning"], font=("Arial", 9, "italic")) self.preview_text.tag_configure("linenum", foreground="#7f8c8d") # 初始化变量 self.search_thread = None self.stop_requested = False self.results = {} self.all_files = [] # 绑定事件 self.file_tree.bind("<<TreeviewSelect>>", self.show_file_content) self.file_tree.bind('<Double-1>', self.open_selected_file) # 配置网格布局 results_frame.columnconfigure(0, weight=1) results_frame.rowconfigure(0, weight=1) def browse_directory(self): """浏览目录""" directory = filedialog.askdirectory(title="选择搜索目录") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) def start_search(self): """开始搜索""" # 重置状态 self.progress["value"] = 0 self.stop_requested = False self.results = {} self.all_files = [] # 清空UI for item in self.file_tree.get_children(): self.file_tree.delete(item) self.preview_text.delete("1.0", tk.END) # 获取参数 directory = self.dir_entry.get().strip() keyword = self.keyword_entry.get().strip() file_filter = self.filter_entry.get().strip() # 验证输入 if not directory or not os.path.isdir(directory): messagebox.showerror("错误", "请选择有效的搜索目录") return if not keyword: messagebox.showerror("错误", "请输入搜索关键词") return self.status_label.config(text="🟡 正在搜索...", foreground=self.colors["warning"]) self.search_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) # 解析文件过滤器 filter_patterns = [pat.strip() for pat in file_filter.split(';')] if ';' in file_filter else [file_filter] # 编译搜索模式 flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): pattern = re.compile(keyword, flags) else: pattern = re.compile(re.escape(keyword), flags) except re.error as e: messagebox.showerror("正则表达式错误", f"无效的正则表达式: {str(e)}") self.search_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) return # 在后台线程搜索 self.search_thread = threading.Thread( target=self.perform_search, args=(directory, filter_patterns, pattern), daemon=True ) self.search_thread.start() def perform_search(self, directory, filter_patterns, pattern): """执行文件搜索""" try: # 收集所有匹配的文件 all_files = [] for root, _, files in os.walk(directory): if self.stop_requested: self.master.after(0, lambda: self.status_label.config( text="🟠 搜索已取消", foreground=self.colors["warning"])) return for file in files: file_path = os.path.join(root, file) # 检查文件大小限制(避免处理超大文件) try: file_size = os.path.getsize(file_path) limit = 100 * 1024 * 1024 # 100MB if file_size > limit: continue except: continue # 检查是否符合任一过滤模式 if any(fnmatch.fnmatch(file, pat) for pat in filter_patterns): all_files.append(file_path) self.all_files = all_files total_files = len(all_files) # 更新UI self.master.after(0, lambda: self.progress.configure(maximum=total_files)) self.master.after(0, lambda: self.stats_label.config(text=f"0/{total_files}")) # 搜索每个文件 self.results = {} processed = 0 matches_found = 0 for file_path in all_files: if self.stop_requested: break processed += 1 # 更新进度 self.master.after(0, lambda v=processed: self.progress.configure(value=v)) if processed % 10 == 0: # 每处理10个文件更新一次进度 self.master.after(0, lambda p=processed, t=total_files: self.stats_label.config(text=f"{p}/{t} ({p/t*100:.1f}%)")) # 忽略二进制文件(除非用户选择包含) if not self.binary_var.get() and self.is_binary(file_path): continue # 获取文件扩展名 _, ext = os.path.splitext(file_path) ext_lower = ext.lower() # 处理不同文件类型 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.search_in_office_file(file_path, pattern) elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.search_in_archive(file_path, pattern) else: matches = self.search_in_text_file(file_path, pattern) if matches: self.results[file_path] = matches matches_found += len(matches) # 获取文件信息 file_name = os.path.basename(file_path) try: size = os.path.getsize(file_path) size_str = f"{size/1024:.1f}KB" if size < 1024*1024 else f"{size/(1024*1024):.1f}MB" mod_time = time.ctime(os.path.getmtime(file_path)) except: size_str = "N/A" mod_time = "N/A" # 在UI线程中添加文件到列表 self.master.after(0, lambda fp=file_path, fn=file_name, sz=size_str, mt=mod_time: self.file_tree.insert("", "end", values=(fn, sz, mt, fp))) # 更新完成状态 if self.stop_requested: status_text = f"🟠 搜索已取消 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" else: status_text = f"🟢 搜索完成 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" self.master.after(0, lambda: self.status_label.config(text=status_text, foreground=self.colors["success"])) self.master.after(0, lambda: self.stats_label.config(text=f"已处理 {processed}/{total_files} 文件")) self.master.after(0, lambda: self.progress.configure(value=total_files)) except Exception as e: # 记录详细错误日志 error_info = f"搜索错误: {type(e).__name__} - {str(e)}" print(error_info) with open("search_errors.log", "a") as log: log.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_info}\n") import traceback traceback.print_exc(file=log) self.master.after(0, lambda: messagebox.showerror( "搜索错误", f"发生严重错误: {error_info}\n详细信息已记录到日志" )) finally: self.master.after(0, lambda: self.search_btn.config(state=tk.NORMAL)) self.master.after(0, lambda: self.stop_btn.config(state=tk.DISABLED)) self.search_thread = None def search_in_text_file(self, filepath, pattern): """在文本文件中搜索匹配项""" matches = [] try: encoding = self.detect_encoding(filepath) try: with open(filepath, 'r', encoding=encoding, errors='replace') as f: for line_num, line in enumerate(f, 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except UnicodeDecodeError: # 特殊编码处理回退 with open(filepath, 'rb') as f: content = f.read() try: text = content.decode('utf-8', errors='replace') except: text = content.decode('latin-1', errors='replace') for line_num, line in enumerate(text.splitlines(), 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"读取文本文件失败 {filepath}: {str(e)}") return matches def search_in_office_file(self, filepath, pattern): """在Office文件中搜索文本内容""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # DOCX文件处理 if ext_lower == '.docx': doc = docx.Document(filepath) # 搜索段落 for i, para in enumerate(doc.paragraphs, 1): if para.text and pattern.search(para.text): matches.append((i, f"段落 {i}: {para.text[:100]}" + ("..." if len(para.text) > 100 else ""))) # 搜索表格 for table in doc.tables: for row_idx, row in enumerate(table.rows, 1): for cell_idx, cell in enumerate(row.cells, 1): if cell.text and pattern.search(cell.text): content = cell.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((row_idx, f"表格 行{row_idx}列{cell_idx}: {content}")) # XLSX/XLS文件处理 elif ext_lower in ('.xlsx', '.xls', '.xlsm'): # 处理新格式Excel文件 if ext_lower in ('.xlsx', '.xlsm'): wb = load_workbook(filepath, read_only=True, data_only=True) for sheet_name in wb.sheetnames: sheet = wb[sheet_name] for row_idx, row in enumerate(sheet.iter_rows(values_only=True), 1): for col_idx, cell in enumerate(row, 1): if cell is not None and pattern.search(str(cell)): cell_ref = f"{chr(64+col_idx)}{row_idx}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx, f"工作表 '{sheet_name}' 单元格 {cell_ref}: {cell_value}")) # 处理旧格式Excel文件 elif ext_lower == '.xls': wb = xlrd.open_workbook(filepath) for sheet_idx in range(wb.nsheets): sheet = wb.sheet_by_index(sheet_idx) for row_idx in range(sheet.nrows): for col_idx in range(sheet.ncols): cell = sheet.cell_value(row_idx, col_idx) if cell and pattern.search(str(cell)): cell_ref = f"{chr(65+col_idx)}{row_idx+1}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx+1, f"工作表 '{sheet.name}' 单元格 {cell_ref}: {cell_value}")) # PPTX文件处理 elif ext_lower == '.pptx': from pptx import Presentation ppt = Presentation(filepath) # 搜索幻灯片文本 for slide_idx, slide in enumerate(ppt.slides, 1): for shape in slide.shapes: if hasattr(shape, "text"): if shape.text and pattern.search(shape.text): content = shape.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((slide_idx, f"幻灯片 {slide_idx}: {content}")) # PDF文件处理 elif ext_lower == '.pdf': with open(filepath, 'rb') as f: pdf = PyPDF2.PdfReader(f) for page_num in range(len(pdf.pages)): page_text = pdf.pages[page_num].extract_text() if page_text and pattern.search(page_text): # 提取匹配内容 matches_found = [] for match in pattern.finditer(page_text): context = page_text[max(0, match.start()-20):match.end()+20] context = context.replace('\n', ' ').strip() matches_found.append(context) # 添加到结果 if matches_found: preview = "; ".join(matches_found[:3]) # 显示前3个匹配 if len(matches_found) > 3: preview += f" ... (+{len(matches_found)-3} 更多)" matches.append((page_num+1, f"页面 {page_num+1}: {preview}")) # 旧版DOC文件处理 elif ext_lower == '.doc': try: # 尝试使用antiword转换DOC为文本 result = subprocess.run(['antiword', filepath], capture_output=True, text=True, timeout=10) if result.returncode == 0: doc_text = result.stdout for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception: # 备用方法:使用python-doc处理 import win32com.client word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(filepath) doc_text = doc.Content.Text doc.Close() word.Quit() for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"处理Office文件失败 {filepath}: {str(e)}") return matches def search_in_archive(self, filepath, pattern): """在压缩文件中搜索匹配项""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # ZIP文件处理 if ext_lower in ('.zip', '.jar', '.war'): with zipfile.ZipFile(filepath, 'r') as archive: for name in archive.namelist(): # 只处理文本文件Office文档 if not name.endswith(('/')) and not self.is_binary(name): try: with archive.open(name) as file: content = file.read(4096) # 只读取前4KB # 尝试检测编码 result = chardet.detect(content) encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8' # 解码内容并搜索 try: text_content = content.decode(encoding, errors='replace') if pattern.search(text_content): matches.append((name, f"压缩文件中的文件: {name}")) except: # 二进制内容搜索 if pattern.search(content): matches.append((name, f"压缩文件中的文件(二进制内容): {name}")) except Exception: continue # 其他压缩格式(需要外部工具) elif ext_lower in ('.rar', '.7z', '.tar', '.gz'): # 使用7zip命令行工具解压并搜索 temp_dir = tempfile.mkdtemp() try: subprocess.run(['7z', 'x', filepath, f'-o{temp_dir}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=60) # 递归搜索解压的目录 for root, _, files in os.walk(temp_dir): for file in files: full_path = os.path.join(root, file) _, file_ext = os.path.splitext(file) file_ext = file_ext.lower() # 只在文本/Office文件中搜索 if file_ext in ['', '.txt', '.py', '.java', '.c', '.cpp', '.h', '.html', '.xml', '.json', '.csv', '.docx', '.xlsx', '.pptx', '.pdf']: if file_ext in ['.docx', '.xlsx', '.pptx', '.pdf']: file_matches = self.search_in_office_file(full_path, pattern) else: file_matches = self.search_in_text_file(full_path, pattern) if file_matches: matches.append((file, f"压缩文件中的文件: {file}")) finally: shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: print(f"处理压缩文件失败 {filepath}: {str(e)}") return matches def detect_encoding(self, filepath): """改进的文件编码检测方法""" try: # 尝试读取文件前4KB进行编码检测 with open(filepath, 'rb') as f: raw_data = f.read(4096) # 使用chardet进行编码检测 result = chardet.detect(raw_data) # 优先使用检测到的编码,否则尝试常见编码 if result['confidence'] > 0.7: return result['encoding'] # 中文环境常用编码回退策略 common_encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1'] for encoding in common_encodings: try: # 尝试解码验证 raw_data.decode(encoding, errors='strict') return encoding except UnicodeDecodeError: continue # 默认使用UTF-8 return 'utf-8' except Exception: return 'utf-8' def is_binary(self, filepath): """检查文件是否为二进制文件""" try: with open(filepath, 'rb') as f: chunk = f.read(1024) if b'\0' in chunk: # 空字节是二进制文件的标志 return True # 检查高字节值 if any(byte >= 0x80 for byte in chunk): return True return False except: return False def stop_search(self): """停止当前搜索""" self.stop_requested = True self.status_label.config(text="🟠 正在停止...", foreground=self.colors["warning"]) self.stop_btn.config(state=tk.DISABLED) def export_results(self): """导出搜索结果""" if not self.results: messagebox.showinfo("导出结果", "没有可导出的搜索结果") return file_path = filedialog.asksaveasfilename( title="保存搜索结果为", defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("文本文件", "*.txt")] ) if not file_path: return try: with open(file_path, 'w', encoding='utf-8') as f: # 写出CSV头部 f.write("文件路径,匹配行号,匹配内容\n") # 写出每项结果 for file, matches in self.results.items(): for line_num, match_content in matches: # 清理内容中的逗号 cleaned_content = match_content.replace('"', '""').replace(',', ';') f.write(f'"{file}",{line_num},"{cleaned_content}"\n') messagebox.showinfo("导出成功", f"搜索结果已保存到:\n{file_path}") except Exception as e: messagebox.showerror("导出错误", f"导出失败: {str(e)}") def show_file_content(self, event=None): """在预览区域显示文件内容""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return # 获取完整文件路径 selected_item = self.file_tree.selection()[0] filepath = self.file_tree.item(selected_item, "values")[3] # 索引3是完整路径 # 清空预览区域 self.preview_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 显示文件路径标题 self.preview_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项 for i, (line_num, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理文本文件 else: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.preview_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.preview_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.preview_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.preview_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.preview_text.insert(tk.END, line[start_idx:]) else: self.preview_text.insert(tk.END, line) except UnicodeDecodeError: self.preview_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def open_selected_file(self, event=None): """用系统默认程序打开选中的文件""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, "values")[3] try: if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": # macOS subprocess.run(["open", filepath]) else: # Linux subprocess.run(["xdg-open", filepath]) except Exception as e: messagebox.showerror("打开文件失败", f"无法打开文件: {str(e)}") def open_file_location(self): """在文件资源管理器中打开文件所在位置""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] folder = os.path.dirname(filepath) try: if sys.platform == "win32": subprocess.run(["explorer", "/select,", filepath]) elif sys.platform == "darwin": # macOS subprocess.run(["open", "-R", filepath]) else: # Linux subprocess.run(["xdg-open", folder]) except Exception as e: messagebox.showerror("打开位置失败", f"无法打开位置: {str(e)}") def show_file_context_menu(self, event): """显示文件列表的右键菜单""" item = self.file_tree.identify_row(event.y) if item: self.file_tree.selection_set(item) self.file_menu.tk_popup(event.x_root, event.y_root) def copy_selected_text(self): """复制预览区域中选中的文本""" selected_text = self.preview_text.get(tk.SEL_FIRST, tk.SEL_LAST) if selected_text: self.master.clipboard_clear() self.master.clipboard_append(selected_text) # 程序入口 if __name__ == "__main__": root = tk.Tk() app = EnhancedFileSearchApp(root) # 添加图标(如果有) try: if sys.platform == "win32": root.iconbitmap("search_icon.ico") else: img = tk.PhotoImage(file="search_icon.png") root.iconphoto(True, img) except: pass root.mainloop() 搜索结果与搜索选项之间的间隔太大了,特别是我全屏的时候,中间空出一大片
09-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值