for i, (name, code) in enumerate(colors):解释

for i, (name, code) in enumerate(colors):

是 Python 中的一个 for 循环语句,它用于遍历一个可迭代对象 colors,并获取每个元素的索引以及元素本身。

逐步解析这行代码:

1. for 循环:

for 是用来遍历可迭代对象(如列表、元组、字典、字符串等)的关键字。在这里,它用于遍历 colors

2. enumerate(colors)

  • enumerate 是一个内置的 Python 函数,它将一个可迭代对象(例如列表)与索引配对,并返回一个迭代器。enumerate 会返回一个由索引和元素组成的元组。
  • 如果 colors 是一个列表或其他类型的可迭代对象,enumerate(colors) 会产生一系列 (索引, 元素) 对。

例如,假设 colors = [("red", "#FF0000"), ("green", "#00FF00"), ("blue", "#0000FF")],那么 enumerate(colors) 会生成以下内容:

请将原代码中,绘图所需的数据,保存至瀚高数据库中,保存至数据库的代码,参考以下: 保存至数据库的参考代码如下: def save_sunburst_data_to_db(sunburst_data): “”" 将旭日图数据保存到瀚高数据库 参数: conn: 数据库连接对象 sunburst_data: 包含所有需要存储的数据的字典 """ try: # 连接数据库 conn = create_connection() cursor = conn.cursor() db_config = get_db_config() dbtype = db_config['dbtype'] # 1. 存储元数据 metadata_query = """ INSERT INTO shap_sunburst_metadata (id, site_code, site_name, project_name, data_date, image_path) VALUES (%s, %s, %s, %s, %s, %s) ON CONFLICT (site_code, project_name, data_date) DO NOTHING """ metadata_id = str(uuid.uuid4()) cursor.execute(metadata_query, ( metadata_id, sunburst_data['site_code'], sunburst_data['site_name'], sunburst_data['project_name'], sunburst_data['data_date'], sunburst_data['image_path'] )) # 检测冲突(关键修改点) if cursor.rowcount == 0: logger.info( f"sunburst数据重复! site_code={sunburst_data['site_code']}, data_dt={sunburst_data['data_date']}, project={sunburst_data['project_name']}已存在,无需重新入库!") return # 直接返回,不执行后续操作 # 2. 存储特征数据 feature_query = """ INSERT INTO shap_sunburst_features (id, metadata_id, feature_name, shap_value, category) VALUES (%s, %s, %s, %s, %s) ON CONFLICT (metadata_id, feature_name) DO NOTHING """ for feature in sunburst_data['features']: feature_id = str(uuid.uuid4()) cursor.execute(feature_query, ( feature_id, metadata_id, feature['feature_name'], feature['shap_value'], feature['category'] )) conn.commit() logger.info(f"成功保存 {len(sunburst_data['features'])} 条特征数据") return True except Exception as e: logger.error(f"数据库保存失败: {str(e)}") conn.rollback() return False finally: # 确保关闭连接 if 'connection' in locals() and conn.is_connected(): cursor.close() conn.close() 原代码如下: def loadings(factor_profiles, data_dt, output_dir, site_code, site_name): font() # Name = pd.read_excel(inp/name.xlsx’)[‘PMF’] # factor_profiles.columns = Name[:len(factor_profiles.columns)] pmf_data_name = get_pmf_data_name() factor_profiles.columns = pmf_data_name[:len(factor_profiles.columns)] df = factor_profiles.copy() # 设置图形参数 plt.figure(figsize=(14, 15)) n_factors = len(df.columns) n_projects = len(df.index) # 生成足够多的颜色(使用tab20c色盘,共20种颜色,循环使用) colors = plt.cm.tab20c(np.linspace(0, 1, min(n_projects, 20))) if n_projects > 20: colors = np.tile(colors, (n_projects // 20 + 1, 1))[:n_projects] # 创建颜色映射:项目名称 → 颜色 color_map = {project_name: color for project_name, color in zip(df.index, colors)} # 遍历每个主因子,创建子图 for i, factor in enumerate(df.columns): ax = plt.subplot(n_factors, 1, i + 1) data = df[factor] # 按项目名称获取对应的固定颜色 project_colors = [color_map[project_name] for project_name in data.index] # 绘制柱形图(每个柱子颜色不同,同项目在不同子图中颜色相同) bars = ax.bar( data.index, data.values, color=project_colors, width=0.6, edgecolor='black' ) # 计算每个物种在所有因子中的浓度总和 total_concentration_per_species = df.sum(axis=1) # 计算占比 percentages = data / total_concentration_per_species * 100 # 创建右侧Y轴 ax2 = ax.twinx() ax2.set_ylim(0, 100) ax2.set_ylabel('占比 (%)', fontsize=10) ax2.grid(False) # 在对应组分位置绘制红色方块标记占比 for j, bar in enumerate(bars): x_pos = bar.get_x() + bar.get_width() / 2 # 获取柱子中心x坐标 percentage = percentages.iloc[j] ax2.plot(x_pos, percentage, 's', color='red', markersize=4) # 美化子图 ax.set_title(f'{site_name}{factor} 负载分布({data_dt}(LTC))', fontsize=12) ax.set_ylabel('负载值', fontsize=12) # 只在最后一个子图显示x轴标签 if i < n_factors - 1: ax.set_xticklabels([]) else: ax.tick_params(axis='x', rotation=60, labelsize=7) plt.setp(ax.get_xticklabels(), ha='right') ax.grid(axis='y', linestyle='--', alpha=0.7) # 添加颜色图例(说明每个项目对应的颜色) if n_projects <= 30: handles = [plt.Rectangle((0, 0), 1, 1, color=color_map[project_name]) for project_name in df.index] plt.figlegend(handles, df.index, loc='lower center', ncol=5, fontsize=8) plt.subplots_adjust(bottom=0.05 + n_projects * 0.002) # 添加右侧Y轴的图例说明 custom_lines = [Line2D([0], [0], color='red', marker='s', linestyle='', markersize=8)] plt.figlegend(custom_lines, ['占比 (%)'], loc='upper right', fontsize=8) # 调整子图间距 plt.tight_layout(h_pad=3) # 保存图形 savepath = output_dir / f'{data_dt}' / f'{site_code}' if not os.path.exists(savepath): os.makedirs(savepath) plt.savefig(savepath / f'pmf_loadings_{data_dt}.png', dpi=800, bbox_inches='tight') plt.close()
最新发布
10-18
# -*- 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"]) # 创建主框架 - 使用grid布局更精确控制 main_frame = ttk.Frame(master) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 减少主框架外边距 # 创建搜索面板(顶部) search_frame = ttk.LabelFrame( main_frame, text="⚙️ 搜索选项", padding=(10, 8), # 减少内边距 style="Search.TLabelframe" ) search_frame.grid(row=0, column=0, sticky="ew", padx=0, pady=(0, 5)) # 减少下边距 # 配置搜索面板样式 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=3) # 减少垂直间距 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=5) 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=(8, 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) # 创建结果面板 - 使用grid放置在下层 results_frame = ttk.Frame(main_frame) results_frame.grid(row=1, column=0, sticky="nsew", pady=0) # 无垂直间距 # 配置网格权重 - 结果面板占据剩余空间 main_frame.rowconfigure(1, weight=1) main_frame.columnconfigure(0, weight=1) # 分割窗格(文件列表和预览左右排列) pane = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) pane.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 无内边距 # 文件列表(左侧) file_frame = ttk.LabelFrame( pane, text="📂 搜索结果", padding=(8, 6) # 减少内边距 ) pane.add(file_frame, weight=1) # 文件预览(右侧) preview_frame = ttk.LabelFrame( pane, text="🔍 预览内容", padding=(8, 6) # 减少内边距 ) pane.add(preview_frame, weight=2) # 创建Treeview显示文件列表 file_tree_frame = ttk.Frame(file_frame) file_tree_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 无内边距 # 定义树状视图列时添加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=8, # 减少内边距 pady=8, # 减少内边距 bg="#FFFFFF", fg=self.colors["text"], relief="flat" ) self.preview_text.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 配置高亮标签 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
# 使用此指令前,请确保安装必要的Python库,例如使用以下命令安装: # pip install pandas openpyxl import pandas as pd import os from collections import defaultdict import openpyxl from openpyxl.styles import PatternFill, Font, Alignment, Border, Side from openpyxl.utils import get_column_letter from typing import * try: from xbot.app.logging import trace as print except: from xbot import print def process_excel_by_batch_with_config(order_file, config_file, output_file): """ title: 按工艺批次整理Excel数据(订单优化版) description: 对表格内数据进行整理,按相同工艺分组,严格控制每批货品数量不超过配置表中的"每批最多数量",确保相同工艺下的相同订单号必须排在一起。输出结果按批次号排序,只对批次汇总行应用颜色标记,使表格更加简洁清晰。 inputs: - order_file (file): 订单数据Excel文件,eg: "订单数据.xlsx" - config_file (file): 工艺配置Excel文件,包含每批最大数量,eg: "工艺配置.xlsx" - output_file (str): 处理后保存的Excel文件路径,eg: "订单数据_批次处理.xlsx" outputs: - output_file (str): 处理后的Excel文件路径,eg: "订单数据_批次处理.xlsx" """ # 检查输入文件是否存在 for file_path in [order_file, config_file]: if not os.path.exists(file_path): raise FileNotFoundError(f"输入文件 '{file_path}' 不存在") # 检查输出路径是否有效 output_dir = os.path.dirname(output_file) if output_dir and not os.path.exists(output_dir): raise FileNotFoundError(f"输出目录 '{output_dir}' 不存在") # 读取订单Excel文件 try: df_orders = pd.read_excel(order_file) except Exception as e: raise ValueError(f"读取订单Excel文件失败: {str(e)}") # 读取配置Excel文件 try: df_config = pd.read_excel(config_file) except Exception as e: raise ValueError(f"读取配置Excel文件失败: {str(e)}") # 检查订单文件中的工艺列 craft_column_name = None possible_craft_columns = ['工艺', '工艺名称'] for col in possible_craft_columns: if col in df_orders.columns: craft_column_name = col break if craft_column_name is None: raise ValueError(f"订单Excel文件中缺少工艺列,请确保文件包含 '工艺' 或 '工艺名称' 列") # 检查配置文件中的工艺列 config_craft_column = None for col in possible_craft_columns: if col in df_config.columns: config_craft_column = col break if config_craft_column is None: raise ValueError(f"配置Excel文件中缺少工艺列,请确保文件包含 '工艺' 或 '工艺名称' 列") # 检查必要的列是否存在 required_order_columns = [craft_column_name, '货品数量', '物流单编号'] for col in required_order_columns: if col not in df_orders.columns: raise ValueError(f"订单Excel文件中缺少必要的列: '{col}'") required_config_columns = [config_craft_column, '一波最少数量', '每批最多数量'] for col in required_config_columns: if col not in df_config.columns: raise ValueError(f"配置Excel文件中缺少必要的列: '{col}'") # 检查是否有订单编号列 has_order_id = '订单编号' in df_orders.columns if not has_order_id: print("警告: 订单Excel文件中缺少'订单编号'列,将无法按订单号分组") # 创建工艺配置字典 craft_config = {} for _, row in df_config.iterrows(): craft_name = row[config_craft_column] min_qty = row['一波最少数量'] max_qty = row['每批最多数量'] craft_config[craft_name] = {'min': min_qty, 'max': max_qty} # 默认配置,当工艺在配置表中找不到时使用 default_config = {'min': 5, 'max': 30} # 按工艺分组并计算批次 def _calculate_batches(): # 创建一个新的DataFrame用于存储结果 result_df = df_orders.copy() # 检查是否已存在批次列,如果存在则删除 if '批次' in result_df.columns: result_df = result_df.drop(columns=['批次']) # 添加批次列 batch_col_idx = df_orders.columns.get_loc('物流单编号') + 1 result_df.insert(batch_col_idx, '批次', '') # 按工艺分组处理 craft_groups = defaultdict(list) if has_order_id: # 首先按工艺和订单号分组 craft_order_groups = defaultdict(lambda: defaultdict(list)) for idx, row in df_orders.iterrows(): craft = row[craft_column_name] order_id = row['订单编号'] qty = row['货品数量'] craft_order_groups[craft][order_id].append((idx, qty)) # 将订单作为整体添加到工艺组 for craft, order_groups in craft_order_groups.items(): for order_id, items in order_groups.items(): indices = [item[0] for item in items] total_qty = sum(item[1] for item in items) craft_groups[craft].append((indices, total_qty, order_id)) else: # 如果没有订单编号列,直接按工艺分组 for idx, row in df_orders.iterrows(): craft = row[craft_column_name] qty = row['货品数量'] craft_groups[craft].append(([idx], qty, None)) # 为每个工艺组分配批次 batch_colors = { 0: 'FFFF00', # 黄色 1: 'FF99CC', # 粉色 2: 'CCFFFF', # 浅蓝色 3: 'CCFFCC', # 浅绿色 4: 'FFCC99', # 橙色 } batch_info = {} # 存储每行的批次信息 {index: (batch_name, color)} batch_summary = {} # 存储每个批次的汇总信息 {batch_name: (total_qty, color, craft)} for craft, items in craft_groups.items(): # 获取该工艺的配置,如果不存在则使用默认配置 # 先尝试完全匹配 config = craft_config.get(craft, None) # 如果完全匹配失败,尝试部分匹配 if config is None: for craft_name, cfg in craft_config.items(): if craft_name in craft or craft in craft_name: config = cfg break # 如果仍然没有匹配,使用默认配置 if config is None: config = default_config min_qty = config['min'] max_qty = config['max'] # 对订单按数量排序,优先处理大订单 sorted_items = sorted(items, key=lambda x: x[1], reverse=True) batch_num = 1 batches = [] # 存储所有批次 [(batch_indices, batch_qty)] current_batch_indices = [] current_batch_qty = 0 for order_indices, order_qty, order_id in sorted_items: # 如果当前订单的数量已经超过最大批次数量,单独作为一个批次 if order_qty > max_qty: batches.append((order_indices, order_qty)) continue # 尝试将订单添加到现有批次中 added_to_existing = False # 尝试填充现有批次 for i, (batch_indices, batch_qty) in enumerate(batches): if batch_qty + order_qty <= max_qty: # 更新批次 batches[i] = (batch_indices + order_indices, batch_qty + order_qty) added_to_existing = True break if not added_to_existing: # 如果添加当前订单会超过最大批次数量,先完成当前批次 if current_batch_qty + order_qty > max_qty and current_batch_indices: batches.append((current_batch_indices, current_batch_qty)) current_batch_indices = order_indices current_batch_qty = order_qty else: # 添加到当前批次 current_batch_indices.extend(order_indices) current_batch_qty += order_qty # 处理最后一个批次 if current_batch_indices: batches.append((current_batch_indices, current_batch_qty)) # 分配批次号和颜色 for i, (batch_indices, batch_qty) in enumerate(batches): if batch_qty >= min_qty: # 只处理达到最小数量的批次 color_idx = i % len(batch_colors) color = batch_colors[color_idx] batch_name = f"{craft}-第{batch_num}批" for idx in batch_indices: batch_info[idx] = (batch_name, color) # 记录批次汇总信息 batch_summary[batch_name] = (batch_qty, color, craft) batch_num += 1 else: # 对于不满足最小数量的批次,尝试合并到其他批次 # 如果没有其他批次或无法合并,仍然创建一个新批次 if batches and i > 0: # 尝试合并到前一个批次 prev_batch_indices, prev_batch_qty = batches[i-1] if prev_batch_qty + batch_qty <= max_qty: # 可以合并 color_idx = (i-1) % len(batch_colors) color = batch_colors[color_idx] batch_name = f"{craft}-第{batch_num-1}批" for idx in batch_indices: batch_info[idx] = (batch_name, color) # 更新批次汇总信息 batch_summary[batch_name] = (prev_batch_qty + batch_qty, color, craft) else: # 无法合并,创建新批次 color_idx = i % len(batch_colors) color = batch_colors[color_idx] batch_name = f"{craft}-第{batch_num}批" for idx in batch_indices: batch_info[idx] = (batch_name, color) # 记录批次汇总信息 batch_summary[batch_name] = (batch_qty, color, craft) batch_num += 1 else: # 没有其他批次,创建新批次 color_idx = i % len(batch_colors) color = batch_colors[color_idx] batch_name = f"{craft}-第{batch_num}批" for idx in batch_indices: batch_info[idx] = (batch_name, color) # 记录批次汇总信息 batch_summary[batch_name] = (batch_qty, color, craft) batch_num += 1 return result_df, batch_info, batch_summary # 计算批次 result_df, batch_info, batch_summary = _calculate_batches() # 将批次信息填入DataFrame for idx, (batch_name, _) in batch_info.items(): result_df.loc[idx, '批次'] = batch_name # 按批次排序,确保同一批次的订单排在一起 # 如果有订单编号列,则按批次、订单编号排序,确保相同订单号排在一起 if has_order_id: result_df = result_df.sort_values(by=['批次', '订单编号', craft_column_name]) else: result_df = result_df.sort_values(by=['批次', craft_column_name]) # 保存到Excel文件,并设置不同批次的颜色和汇总行 try: # 先将DataFrame保存到Excel result_df.to_excel(output_file, index=False) # 然后使用openpyxl添加汇总行和设置样式 workbook = openpyxl.load_workbook(output_file) worksheet = workbook.active # 获取列数 num_cols = len(result_df.columns) # 获取批次列的索引 batch_col_idx = result_df.columns.get_loc('批次') + 1 # Excel列从1开始 qty_col_idx = result_df.columns.get_loc('货品数量') + 1 # Excel列从1开始 craft_col_idx = result_df.columns.get_loc(craft_column_name) + 1 # Excel列从1开始 # 获取物流单编号和订单编号列的索引(如果存在) logistics_col_idx = result_df.columns.get_loc('物流单编号') + 1 if '物流单编号' in result_df.columns else None order_col_idx = result_df.columns.get_loc('订单编号') + 1 if has_order_id else None # 检查是否有商家编码列 merchant_code_col_idx = None possible_merchant_code_columns = ['商家编码', '商品编码', '货号'] for col in possible_merchant_code_columns: if col in result_df.columns: merchant_code_col_idx = result_df.columns.get_loc(col) + 1 break # 设置单元格样式 - 只用于汇总行 def _set_summary_cell_style(cell, color): cell.fill = PatternFill(start_color=color, end_color=color, fill_type="solid") cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center', vertical='center') thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) cell.border = thin_border # 设置普通单元格样式 - 只添加边框,不添加颜色 def _set_normal_cell_style(cell): cell.alignment = Alignment(vertical='center', wrap_text=True) thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) cell.border = thin_border # 设置标题行样式 header_fill = PatternFill(start_color="D9D9D9", end_color="D9D9D9", fill_type="solid") header_font = Font(bold=True) header_alignment = Alignment(horizontal='center', vertical='center') header_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) for col_idx in range(1, num_cols + 1): cell = worksheet.cell(row=1, column=col_idx) cell.fill = header_fill cell.font = header_font cell.alignment = header_alignment cell.border = header_border # 为所有数据行添加边框 for row_idx in range(2, worksheet.max_row + 1): for col_idx in range(1, num_cols + 1): cell = worksheet.cell(row=row_idx, column=col_idx) _set_normal_cell_style(cell) # 找出每个批次的最后一行 batch_last_rows = {} current_batch = None current_batch_last_row = None for row_idx in range(2, worksheet.max_row + 1): batch_cell = worksheet.cell(row=row_idx, column=batch_col_idx) if batch_cell.value: if batch_cell.value != current_batch: if current_batch: batch_last_rows[current_batch] = current_batch_last_row current_batch = batch_cell.value current_batch_last_row = row_idx # 添加最后一个批次 if current_batch: batch_last_rows[current_batch] = current_batch_last_row # 按批次添加汇总行 # 记录已插入的行数,用于调整后续行的索引 inserted_rows = 0 # 按批次顺序处理 for batch_name, last_row in sorted(batch_last_rows.items()): if batch_name in batch_summary: batch_qty, color, craft = batch_summary[batch_name] # 在批次的最后一行后面插入汇总行 adjusted_last_row = last_row + inserted_rows worksheet.insert_rows(adjusted_last_row + 1) inserted_rows += 1 # 设置汇总行 summary_row = adjusted_last_row + 1 # 设置工艺列 craft_summary_cell = worksheet.cell(row=summary_row, column=craft_col_idx) craft_summary_cell.value = craft _set_summary_cell_style(craft_summary_cell, color) # 设置批次列 batch_summary_cell = worksheet.cell(row=summary_row, column=batch_col_idx) batch_summary_cell.value = f"{batch_name}汇总" _set_summary_cell_style(batch_summary_cell, color) # 设置数量列 qty_summary_cell = worksheet.cell(row=summary_row, column=qty_col_idx) qty_summary_cell.value = batch_qty _set_summary_cell_style(qty_summary_cell, color) # 设置其他列 for col_idx in range(1, num_cols + 1): if col_idx != batch_col_idx and col_idx != qty_col_idx and col_idx != craft_col_idx: cell = worksheet.cell(row=summary_row, column=col_idx) _set_summary_cell_style(cell, color) # 自动调整列宽 column_widths = {} # 首先计算每列的最大宽度 for col_idx in range(1, num_cols + 1): column = get_column_letter(col_idx) max_length = 0 for row_idx in range(1, worksheet.max_row + 1): cell = worksheet.cell(row=row_idx, column=col_idx) if cell.value: cell_length = len(str(cell.value)) if cell_length > max_length: max_length = cell_length # 设置列宽,但对特定列进行特殊处理 if col_idx == logistics_col_idx or col_idx == order_col_idx or col_idx == merchant_code_col_idx: # 物流单号、订单编号和商家编码列设置更宽一些,确保完整显示 adjusted_width = max(max_length + 2, 20) else: # 其他列根据内容自动调整,但设置最小和最大宽度 adjusted_width = min(max(max_length + 2, 10), 30) worksheet.column_dimensions[column].width = adjusted_width # 设置物流单号、订单编号和商家编码列的数字格式为文本格式,确保完整显示 text_format_columns = [logistics_col_idx, order_col_idx, merchant_code_col_idx] for col_idx in text_format_columns: if col_idx: for row_idx in range(2, worksheet.max_row + 1): cell = worksheet.cell(row=row_idx, column=col_idx) if cell.value: cell.number_format = '@' # 设置为文本格式 # 冻结首行 worksheet.freeze_panes = 'A2' # 保存修改后的Excel文件 workbook.save(output_file) except Exception as e: raise ValueError(f"保存Excel文件失败: {str(e)}") return output_file
09-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

heda3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值