diff --git "a/\345\220\257\345\212\250\345\231\250.py" "b/\345\220\257\345\212\250\345\231\250.py" index 50d0b22..691388f 100644 --- "a/\345\220\257\345\212\250\345\231\250.py" +++ "b/\345\220\257\345\212\250\345\231\250.py" @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -OCR订单处理系统启动器 +益选-OCR订单处理系统启动器 ----------------- 提供简单的图形界面,方便用户选择功能 """ @@ -13,53 +13,116 @@ import time import subprocess import shutil import tkinter as tk -from tkinter import messagebox, filedialog, scrolledtext +from tkinter import messagebox, filedialog, scrolledtext, ttk +from tkinter import font as tkfont from threading import Thread import datetime +import json +import re +from typing import Dict, List, Optional, Any -def ensure_directories(): - """确保必要的目录结构存在""" - directories = ["data/input", "data/output", "data/temp", "logs"] - for directory in directories: - if not os.path.exists(directory): - os.makedirs(directory, exist_ok=True) - print(f"创建目录: {directory}") +# 全局变量,用于跟踪任务状态 +RUNNING_TASK = None +THEME_MODE = "light" # 默认浅色主题 -class LogRedirector: - """日志重定向器,用于捕获命令输出并显示到界面""" - def __init__(self, text_widget): - self.text_widget = text_widget - self.buffer = "" - self.terminal = sys.__stdout__ # 保存原始的stdout引用 +# 定义浅色和深色主题颜色 +THEMES = { + "light": { + "bg": "#f0f0f0", + "fg": "#000000", + "button_bg": "#e0e0e0", + "button_fg": "#000000", + "log_bg": "#ffffff", + "log_fg": "#000000", + "highlight_bg": "#4a6984", + "highlight_fg": "#ffffff", + "border": "#cccccc", + "success": "#28a745", + "error": "#dc3545", + "warning": "#ffc107", + "info": "#17a2b8" + }, + "dark": { + "bg": "#2d2d2d", + "fg": "#ffffff", + "button_bg": "#444444", + "button_fg": "#ffffff", + "log_bg": "#1e1e1e", + "log_fg": "#e0e0e0", + "highlight_bg": "#4a6984", + "highlight_fg": "#ffffff", + "border": "#555555", + "success": "#28a745", + "error": "#dc3545", + "warning": "#ffc107", + "info": "#17a2b8" + } +} + +class StatusBar(tk.Frame): + """状态栏,显示当前系统状态和进度""" + + def __init__(self, master, **kwargs): + super().__init__(master, **kwargs) + self.configure(height=25, relief=tk.SUNKEN, borderwidth=1) - def write(self, string): - self.buffer += string - # 同时输出到终端 - self.terminal.write(string) - # 在UI线程中更新文本控件 - self.text_widget.after(0, self.update_text_widget) + # 状态标签 + self.status_label = tk.Label(self, text="就绪", anchor=tk.W, padx=5) + self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True) - def update_text_widget(self): - self.text_widget.configure(state=tk.NORMAL) - self.text_widget.insert(tk.END, self.buffer) - # 自动滚动到底部 - self.text_widget.see(tk.END) - self.text_widget.configure(state=tk.DISABLED) - self.buffer = "" + # 进度条 + self.progress = ttk.Progressbar(self, orient=tk.HORIZONTAL, length=200, mode='determinate') + self.progress.pack(side=tk.RIGHT, padx=5, pady=2) - def flush(self): - self.terminal.flush() # 确保终端也被刷新 - -def run_command_with_logging(command, log_widget): + # 隐藏进度条(初始状态) + self.progress.pack_forget() + + def set_status(self, text, progress=None): + """设置状态栏文本和进度""" + self.status_label.config(text=text) + + if progress is not None and 0 <= progress <= 100: + self.progress.pack(side=tk.RIGHT, padx=5, pady=2) + self.progress.config(value=progress) + else: + self.progress.pack_forget() + + def set_running(self, is_running=True): + """设置运行状态""" + if is_running: + self.status_label.config(text="处理中...", foreground=THEMES[THEME_MODE]["info"]) + self.progress.pack(side=tk.RIGHT, padx=5, pady=2) + self.progress.config(mode='indeterminate') + self.progress.start() + else: + self.status_label.config(text="就绪", foreground=THEMES[THEME_MODE]["fg"]) + self.progress.stop() + self.progress.pack_forget() + +def run_command_with_logging(command, log_widget, status_bar=None, on_complete=None): """运行命令并将输出重定向到日志窗口""" + global RUNNING_TASK + + # 如果已有任务在运行,提示用户 + if RUNNING_TASK is not None: + messagebox.showinfo("任务进行中", "请等待当前任务完成后再执行新的操作。") + return + def run_in_thread(): + global RUNNING_TASK + RUNNING_TASK = command + + # 更新状态栏 + if status_bar: + status_bar.set_running(True) + # 记录命令开始执行的时间 start_time = datetime.datetime.now() log_widget.configure(state=tk.NORMAL) log_widget.delete(1.0, tk.END) # 清空之前的日志 - log_widget.insert(tk.END, f"执行命令: {' '.join(command)}\n") - log_widget.insert(tk.END, f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n") - log_widget.insert(tk.END, "=" * 50 + "\n\n") + log_widget.insert(tk.END, f"执行命令: {' '.join(command)}\n", "command") + log_widget.insert(tk.END, f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n", "time") + log_widget.insert(tk.END, "=" * 50 + "\n\n", "separator") log_widget.configure(state=tk.DISABLED) # 获取原始的stdout和stderr @@ -94,10 +157,18 @@ def run_command_with_logging(command, log_widget): env=env ) + output_data = [] # 读取并显示输出 for line in process.stdout: + output_data.append(line) print(line.rstrip()) # 直接打印到已重定向的stdout + # 尝试从输出中提取进度信息 + if status_bar: + progress = extract_progress_from_log(line) + if progress is not None: + log_widget.after(0, lambda p=progress: status_bar.set_status(f"处理中: {p}%完成", p)) + # 等待进程结束 process.wait() @@ -110,352 +181,494 @@ def run_command_with_logging(command, log_widget): print(f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}") print(f"耗时: {duration.total_seconds():.2f} 秒") - # 如果处理成功,显示成功信息 - if process.returncode == 0: - log_widget.after(0, lambda: messagebox.showinfo("操作成功", "处理完成!\n请在data/output目录查看结果。")) + # 获取输出内容 + output_text = ''.join(output_data) + + # 检查是否是完整流程命令且遇到了"未找到可合并的文件"的情况 + is_pipeline = "pipeline" in command + no_merge_files = "未找到可合并的文件" in output_text + + # 如果是完整流程且只是没有找到可合并文件,则视为成功 + if is_pipeline and no_merge_files: + print("完整流程中未找到可合并文件,但其他步骤执行成功,视为成功完成") + if status_bar: + log_widget.after(0, lambda: status_bar.set_status("处理完成", 100)) + log_widget.after(0, lambda: show_result_preview(command, output_text)) else: - log_widget.after(0, lambda: messagebox.showerror("操作失败", f"处理失败,返回码:{process.returncode}")) + # 执行完成后处理结果 + if on_complete: + log_widget.after(0, lambda: on_complete(process.returncode, output_text)) + + # 如果处理成功,显示成功信息 + if process.returncode == 0: + if status_bar: + log_widget.after(0, lambda: status_bar.set_status("处理完成", 100)) + log_widget.after(0, lambda: show_result_preview(command, output_text)) + else: + if status_bar: + log_widget.after(0, lambda: status_bar.set_status(f"处理失败 (返回码: {process.returncode})", 0)) + log_widget.after(0, lambda: messagebox.showerror("操作失败", f"处理失败,返回码:{process.returncode}")) except Exception as e: print(f"\n执行出错: {str(e)}") + if status_bar: + log_widget.after(0, lambda: status_bar.set_status(f"执行出错: {str(e)}", 0)) log_widget.after(0, lambda: messagebox.showerror("执行错误", f"执行命令时出错: {str(e)}")) finally: # 恢复原始stdout和stderr sys.stdout = old_stdout sys.stderr = old_stderr + + # 任务完成,重置状态 + RUNNING_TASK = None + if status_bar: + log_widget.after(0, lambda: status_bar.set_running(False)) # 在新线程中运行,避免UI阻塞 Thread(target=run_in_thread).start() -def add_to_log(log_widget, text): - """向日志窗口添加文本""" - log_widget.configure(state=tk.NORMAL) - log_widget.insert(tk.END, text) - log_widget.see(tk.END) # 自动滚动到底部 - log_widget.configure(state=tk.DISABLED) +def extract_progress_from_log(log_line): + """从日志行中提取进度信息""" + # 尝试匹配"处理批次 x/y"格式的进度信息 + batch_match = re.search(r'处理批次 (\d+)/(\d+)', log_line) + if batch_match: + current = int(batch_match.group(1)) + total = int(batch_match.group(2)) + return int(current / total * 100) + + # 尝试匹配百分比格式 + percent_match = re.search(r'(\d+)%', log_line) + if percent_match: + return int(percent_match.group(1)) + + return None -def select_file(log_widget): - """选择图片文件并复制到data/input目录""" - # 确保目录存在 - ensure_directories() - - # 获取输入目录的绝对路径 - input_dir = os.path.abspath("data/input") - - file_path = filedialog.askopenfilename( - title="选择要处理的图片文件", - initialdir=input_dir, # 默认打开data/input目录 - filetypes=[("图片文件", "*.jpg *.jpeg *.png *.bmp")] - ) - - if not file_path: - return None +def show_result_preview(command, output): + """显示处理结果预览""" + # 根据命令类型提取不同的结果信息 + if "ocr" in command: + show_ocr_result_preview(output) + elif "excel" in command: + show_excel_result_preview(output) + elif "merge" in command: + show_merge_result_preview(output) + elif "pipeline" in command: + show_pipeline_result_preview(output) + else: + messagebox.showinfo("处理完成", "操作已成功完成!\n请在data/output目录查看结果。") + +def show_ocr_result_preview(output): + """显示OCR处理结果预览""" + # 提取处理的文件数量 + files_match = re.search(r'找到 (\d+) 个图片文件,其中 (\d+) 个未处理', output) + processed_match = re.search(r'所有图片处理完成, 总计: (\d+), 成功: (\d+)', output) + + if processed_match: + total = int(processed_match.group(1)) + success = int(processed_match.group(2)) - # 记录选择文件的信息 - add_to_log(log_widget, f"已选择文件: {file_path}\n") - - # 计算目标路径,始终放在data/input中 - output_path = os.path.join("data/input", os.path.basename(file_path)) - abs_output_path = os.path.abspath(output_path) - - # 检查是否是同一个文件 - if os.path.normpath(os.path.abspath(file_path)) != os.path.normpath(abs_output_path): - # 如果是不同的文件,则复制 - try: - shutil.copy2(file_path, output_path) - add_to_log(log_widget, f"已复制文件到处理目录: {output_path}\n") - except Exception as e: - add_to_log(log_widget, f"复制文件失败: {e}\n") - messagebox.showerror("错误", f"复制文件失败: {e}") - return None - - # 返回绝对路径,确保命令行处理正确 - return abs_output_path + # 创建结果预览对话框 + preview = tk.Toplevel() + preview.title("OCR处理结果") + preview.geometry("400x300") + preview.resizable(False, False) + + # 居中显示 + center_window(preview) + + # 添加内容 + tk.Label(preview, text="OCR处理完成", font=("Arial", 16, "bold")).pack(pady=10) + + result_frame = tk.Frame(preview) + result_frame.pack(pady=10, fill=tk.BOTH, expand=True) + + tk.Label(result_frame, text=f"总共处理: {total} 个文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5) + tk.Label(result_frame, text=f"成功处理: {success} 个文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5) + tk.Label(result_frame, text=f"失败数量: {total - success} 个文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5) + + # 处理结果评估 + if success == total: + result_text = "全部处理成功!" + result_color = "#28a745" + elif success > total * 0.8: + result_text = "大部分处理成功。" + result_color = "#ffc107" + else: + result_text = "处理失败较多,请检查日志。" + result_color = "#dc3545" + + tk.Label(result_frame, text=result_text, font=("Arial", 12, "bold"), fg=result_color).pack(pady=10) + + # 添加按钮 + button_frame = tk.Frame(preview) + button_frame.pack(pady=10) + + tk.Button(button_frame, text="查看输出文件", command=lambda: os.startfile(os.path.abspath("data/output"))).pack(side=tk.LEFT, padx=10) + tk.Button(button_frame, text="关闭", command=preview.destroy).pack(side=tk.LEFT, padx=10) + else: + messagebox.showinfo("OCR处理完成", "OCR处理已完成,请在data/output目录查看结果。") -def select_excel_file(log_widget): - """选择Excel文件并复制到data/output目录""" - # 确保目录存在 - ensure_directories() - - # 获取输出目录的绝对路径 - output_dir = os.path.abspath("data/output") - - file_path = filedialog.askopenfilename( - title="选择要处理的Excel文件", - initialdir=output_dir, # 默认打开data/output目录 - filetypes=[("Excel文件", "*.xlsx *.xls")] - ) - - if not file_path: - return None +def show_excel_result_preview(output): + """显示Excel处理结果预览""" + # 提取处理的Excel信息 + extract_match = re.search(r'提取到 (\d+) 个商品信息', output) + file_match = re.search(r'采购单已保存到: (.+?)(?:\n|$)', output) + + if extract_match and file_match: + products_count = int(extract_match.group(1)) + output_file = file_match.group(1) - # 记录选择文件的信息 - add_to_log(log_widget, f"已选择文件: {file_path}\n") - - # 计算目标路径,始终放在data/output中 - output_path = os.path.join("data/output", os.path.basename(file_path)) - abs_output_path = os.path.abspath(output_path) - - # 检查是否是同一个文件 - if os.path.normpath(os.path.abspath(file_path)) != os.path.normpath(abs_output_path): - # 如果是不同的文件,则复制 + # 创建结果预览对话框 + preview = tk.Toplevel() + preview.title("Excel处理结果") + preview.geometry("450x320") + preview.resizable(False, False) + + # 使弹窗居中显示 + center_window(preview) + + # 添加内容 + tk.Label(preview, text="Excel处理完成", font=("Arial", 16, "bold")).pack(pady=10) + + result_frame = tk.Frame(preview) + result_frame.pack(pady=10, fill=tk.BOTH, expand=True) + + tk.Label(result_frame, text=f"提取商品数量: {products_count} 个", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5) + tk.Label(result_frame, text=f"输出文件: {os.path.basename(output_file)}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5) + + # 处理成功提示 + tk.Label(result_frame, text="采购单已成功生成!", font=("Arial", 12, "bold"), fg="#28a745").pack(pady=10) + + # 文件信息框 + file_frame = tk.Frame(result_frame, relief=tk.GROOVE, borderwidth=1) + file_frame.pack(fill=tk.X, padx=15, pady=5) + + tk.Label(file_frame, text="文件信息", font=("Arial", 10, "bold")).pack(anchor=tk.W, padx=10, pady=5) + + # 获取文件大小和时间 try: - shutil.copy2(file_path, output_path) - add_to_log(log_widget, f"已复制文件到处理目录: {output_path}\n") - except Exception as e: - add_to_log(log_widget, f"复制文件失败: {e}\n") - messagebox.showerror("错误", f"复制文件失败: {e}") - return None - - # 返回绝对路径,确保命令行处理正确 - return abs_output_path + file_size = os.path.getsize(output_file) + file_time = datetime.datetime.fromtimestamp(os.path.getmtime(output_file)) + + size_text = f"{file_size / 1024:.1f} KB" if file_size < 1024*1024 else f"{file_size / (1024*1024):.1f} MB" + + tk.Label(file_frame, text=f"文件大小: {size_text}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2) + tk.Label(file_frame, text=f"创建时间: {file_time.strftime('%Y-%m-%d %H:%M:%S')}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2) + except: + tk.Label(file_frame, text="无法获取文件信息", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2) + + # 添加按钮 + button_frame = tk.Frame(preview) + button_frame.pack(pady=10) + + tk.Button(button_frame, text="打开文件", command=lambda: os.startfile(output_file)).pack(side=tk.LEFT, padx=5) + tk.Button(button_frame, text="打开所在文件夹", command=lambda: os.startfile(os.path.dirname(output_file))).pack(side=tk.LEFT, padx=5) + tk.Button(button_frame, text="关闭", command=preview.destroy).pack(side=tk.LEFT, padx=5) + else: + messagebox.showinfo("Excel处理完成", "Excel处理已完成,请在data/output目录查看结果。") -def process_single_image(log_widget): - """处理单个图片""" - file_path = select_file(log_widget) - if file_path: - # 确保文件存在 - if os.path.exists(file_path): - add_to_log(log_widget, f"正在处理图片: {os.path.basename(file_path)}\n") - # 使用绝对路径,并指定直接输出到data/output - run_command_with_logging(["python", "run.py", "ocr", "--input", file_path], log_widget) - else: - add_to_log(log_widget, f"文件不存在: {file_path}\n") - messagebox.showerror("错误", f"文件不存在: {file_path}") +def show_merge_result_preview(output): + """显示合并结果预览""" + # 提取合并信息 + merged_match = re.search(r'合并了 (\d+) 个采购单', output) + product_match = re.search(r'共处理 (\d+) 个商品', output) + output_match = re.search(r'已保存到: (.+?)(?:\n|$)', output) + + if merged_match and output_match: + merged_count = int(merged_match.group(1)) + product_count = int(product_match.group(1)) if product_match else 0 + output_file = output_match.group(1) + + # 创建结果预览对话框 + preview = tk.Toplevel() + preview.title("采购单合并结果") + preview.geometry("450x300") + preview.resizable(False, False) + + # 设置主题 + apply_theme(preview) + + # 添加内容 + tk.Label(preview, text="采购单合并完成", font=("Arial", 16, "bold")).pack(pady=10) + + result_frame = tk.Frame(preview) + result_frame.pack(pady=10, fill=tk.BOTH, expand=True) + + tk.Label(result_frame, text=f"合并采购单数量: {merged_count} 个", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5) + tk.Label(result_frame, text=f"处理商品数量: {product_count} 个", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5) + tk.Label(result_frame, text=f"输出文件: {os.path.basename(output_file)}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5) + + # 处理成功提示 + tk.Label(result_frame, text="采购单已成功合并!", font=("Arial", 12, "bold"), fg=THEMES[THEME_MODE]["success"]).pack(pady=10) + + # 添加按钮 + button_frame = tk.Frame(preview) + button_frame.pack(pady=10) + + tk.Button(button_frame, text="打开文件", command=lambda: os.startfile(output_file)).pack(side=tk.LEFT, padx=10) + tk.Button(button_frame, text="打开所在文件夹", command=lambda: os.startfile(os.path.dirname(output_file))).pack(side=tk.LEFT, padx=10) + tk.Button(button_frame, text="关闭", command=preview.destroy).pack(side=tk.LEFT, padx=10) else: - add_to_log(log_widget, "未选择文件,操作已取消\n") + messagebox.showinfo("采购单合并完成", "采购单合并已完成,请在data/output目录查看结果。") -def process_excel_file(log_widget): - """处理Excel文件""" - file_path = select_excel_file(log_widget) - if file_path: - # 确保文件存在 - if os.path.exists(file_path): - add_to_log(log_widget, f"正在处理Excel文件: {os.path.basename(file_path)}\n") - # 使用绝对路径 - run_command_with_logging(["python", "run.py", "excel", "--input", file_path], log_widget) +def show_pipeline_result_preview(output): + """显示完整流程结果预览""" + # 提取关键信息 + ocr_match = re.search(r'所有图片处理完成, 总计: (\d+), 成功: (\d+)', output) + excel_match = re.search(r'提取到 (\d+) 个商品信息', output) + output_file_match = re.search(r'采购单已保存到: (.+?)(?:\n|$)', output) + + # 创建结果预览对话框 + preview = tk.Toplevel() + preview.title("完整流程处理结果") + preview.geometry("500x400") + preview.resizable(False, False) + + # 居中显示 + center_window(preview) + + # 添加内容 + tk.Label(preview, text="完整处理流程已完成", font=("Arial", 16, "bold")).pack(pady=10) + + # 添加处理结果提示(即使没有可合并文件也显示成功) + no_files_match = re.search(r'未找到可合并的文件', output) + if no_files_match: + tk.Label(preview, text="未找到可合并文件,但其他步骤已成功执行", font=("Arial", 12)).pack(pady=0) + + result_frame = tk.Frame(preview) + result_frame.pack(pady=10, fill=tk.BOTH, expand=True) + + # 创建多行结果区域 + result_text = scrolledtext.ScrolledText(result_frame, wrap=tk.WORD, height=15, width=60) + result_text.pack(fill=tk.BOTH, expand=True, padx=15, pady=5) + result_text.configure(state=tk.NORMAL) + + # 填充结果文本 + result_text.insert(tk.END, "===== 流程执行结果 =====\n\n", "title") + + # OCR处理结果 + result_text.insert(tk.END, "步骤1: OCR识别\n", "step") + if ocr_match: + total = int(ocr_match.group(1)) + success = int(ocr_match.group(2)) + result_text.insert(tk.END, f" 处理图片: {total} 个\n", "info") + result_text.insert(tk.END, f" 成功识别: {success} 个\n", "info") + if success == total: + result_text.insert(tk.END, " 结果: 全部识别成功\n", "success") else: - add_to_log(log_widget, f"文件不存在: {file_path}\n") - messagebox.showerror("错误", f"文件不存在: {file_path}") + result_text.insert(tk.END, f" 结果: 部分识别成功 ({success}/{total})\n", "warning") else: - # 如果未选择文件,尝试处理最新的Excel - add_to_log(log_widget, "未选择文件,尝试处理最新的Excel文件\n") - run_command_with_logging(["python", "run.py", "excel"], log_widget) - -def organize_project_files(log_widget): - """整理项目中的文件到正确位置""" - # 确保目录存在 - ensure_directories() - - add_to_log(log_widget, "开始整理项目文件...\n") + result_text.insert(tk.END, " 结果: 无OCR处理或处理信息不完整\n", "warning") + + # Excel处理结果 + result_text.insert(tk.END, "\n步骤2: Excel处理\n", "step") + if excel_match: + products = int(excel_match.group(1)) + result_text.insert(tk.END, f" 提取商品: {products} 个\n", "info") + result_text.insert(tk.END, " 结果: 成功生成采购单\n", "success") + if output_file_match: + output_file = output_file_match.group(1) + result_text.insert(tk.END, f" 输出文件: {os.path.basename(output_file)}\n", "info") + else: + result_text.insert(tk.END, " 结果: 无Excel处理或处理信息不完整\n", "warning") - # 转移根目录文件 - files_moved = 0 + # 总体评估 + result_text.insert(tk.END, "\n===== 整体评估 =====\n", "title") - # 处理日志文件 - log_files = [f for f in os.listdir('.') if f.endswith('.log')] - for log_file in log_files: - try: - src_path = os.path.join('.', log_file) - dst_path = os.path.join('logs', log_file) - if not os.path.exists(dst_path) or os.path.getmtime(src_path) > os.path.getmtime(dst_path): - shutil.copy2(src_path, dst_path) - add_to_log(log_widget, f"已移动日志文件: {src_path} -> {dst_path}\n") - files_moved += 1 - except Exception as e: - add_to_log(log_widget, f"移动日志文件出错: {e}\n") + has_errors = "错误" in output or "失败" in output - # 处理JSON文件 - json_files = [f for f in os.listdir('.') if f.endswith('.json')] - for json_file in json_files: - try: - src_path = os.path.join('.', json_file) - dst_path = os.path.join('data', json_file) - if not os.path.exists(dst_path) or os.path.getmtime(src_path) > os.path.getmtime(dst_path): - shutil.copy2(src_path, dst_path) - add_to_log(log_widget, f"已移动记录文件: {src_path} -> {dst_path}\n") - files_moved += 1 - except Exception as e: - add_to_log(log_widget, f"移动记录文件出错: {e}\n") - - # 处理input和output目录 - for old_dir, new_dir in {"input": "data/input", "output": "data/output"}.items(): - if os.path.exists(old_dir) and os.path.isdir(old_dir): - for file in os.listdir(old_dir): - src_path = os.path.join(old_dir, file) - dst_path = os.path.join(new_dir, file) - try: - if os.path.isfile(src_path): - if not os.path.exists(dst_path) or os.path.getmtime(src_path) > os.path.getmtime(dst_path): - shutil.copy2(src_path, dst_path) - add_to_log(log_widget, f"已转移文件: {src_path} -> {dst_path}\n") - files_moved += 1 - except Exception as e: - add_to_log(log_widget, f"移动文件出错: {e}\n") - - # 显示结果 - if files_moved > 0: - add_to_log(log_widget, f"整理完成,共整理 {files_moved} 个文件\n") - messagebox.showinfo("整理完成", f"已整理 {files_moved} 个文件到正确位置。\n" - "原始文件保留在原位置,以确保数据安全。") + if no_files_match: + result_text.insert(tk.END, "没有找到可合并的文件,但处理流程已成功完成。\n", "warning") + result_text.insert(tk.END, "可以选择打开Excel文件或查看输出文件夹。\n", "info") + elif ocr_match and excel_match and not has_errors: + result_text.insert(tk.END, "流程完整执行成功!\n", "success") + elif ocr_match or excel_match: + result_text.insert(tk.END, "流程部分执行成功,请检查日志获取详情。\n", "warning") else: - add_to_log(log_widget, "没有需要整理的文件\n") - messagebox.showinfo("整理完成", "没有需要整理的文件。") - -def clean_data_files(log_widget): - """清理data目录中的文件""" - # 确保目录存在 - ensure_directories() + result_text.insert(tk.END, "流程执行可能存在问题,请查看详细日志。\n", "error") - add_to_log(log_widget, "开始清理文件...\n") + # 设置标签样式 + result_text.tag_configure("title", font=("Arial", 12, "bold")) + result_text.tag_configure("step", font=("Arial", 11, "bold")) + result_text.tag_configure("info", font=("Arial", 10)) + result_text.tag_configure("success", font=("Arial", 10, "bold"), foreground="#28a745") + result_text.tag_configure("warning", font=("Arial", 10, "bold"), foreground="#ffc107") + result_text.tag_configure("error", font=("Arial", 10, "bold"), foreground="#dc3545") - # 获取需要清理的目录 - input_dir = os.path.abspath("data/input") - output_dir = os.path.abspath("data/output") + result_text.configure(state=tk.DISABLED) - # 统计文件信息 - input_files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] - output_files = [f for f in os.listdir(output_dir) if os.path.isfile(os.path.join(output_dir, f))] - - # 显示统计信息 - add_to_log(log_widget, f"输入目录 ({input_dir}) 共有 {len(input_files)} 个文件\n") - add_to_log(log_widget, f"输出目录 ({output_dir}) 共有 {len(output_files)} 个文件\n") + # 添加按钮 + button_frame = tk.Frame(preview) + button_frame.pack(pady=10) - # 显示确认对话框 - if not input_files and not output_files: - messagebox.showinfo("清理文件", "没有需要清理的文件") - return + if output_file_match: + output_file = output_file_match.group(1) + tk.Button(button_frame, text="打开Excel文件", command=lambda: os.startfile(output_file)).pack(side=tk.LEFT, padx=10) + else: + # 如果没有找到合并后的文件,但Excel处理成功,提供打开最新Excel文件的选项 + if excel_match: + # 找到输出目录中最新的采购单Excel文件 + output_dir = os.path.abspath("data/output") + excel_files = [f for f in os.listdir(output_dir) if f.startswith('采购单_') and (f.endswith('.xls') or f.endswith('.xlsx'))] + if excel_files: + # 按修改时间排序,获取最新的文件 + excel_files.sort(key=lambda x: os.path.getmtime(os.path.join(output_dir, x)), reverse=True) + latest_file = os.path.join(output_dir, excel_files[0]) + tk.Button(button_frame, text="打开最新Excel文件", + command=lambda: os.startfile(latest_file)).pack(side=tk.LEFT, padx=10) + + tk.Button(button_frame, text="查看输出文件夹", command=lambda: os.startfile(os.path.abspath("data/output"))).pack(side=tk.LEFT, padx=10) + tk.Button(button_frame, text="关闭", command=preview.destroy).pack(side=tk.LEFT, padx=10) + +def apply_theme(widget, theme_mode=None): + """应用主题到小部件""" + global THEME_MODE - confirm_message = "确定要清理以下文件吗?\n\n" - confirm_message += f"- 输入目录: {len(input_files)} 个文件\n" - confirm_message += f"- 输出目录: {len(output_files)} 个文件\n" - confirm_message += "\n此操作不可撤销!" + if theme_mode is None: + theme_mode = THEME_MODE - if not messagebox.askyesno("确认清理", confirm_message): - add_to_log(log_widget, "清理操作已取消\n") - return + theme = THEMES[theme_mode] - # 清理输入目录的文件 - files_deleted = 0 - - # 先提示用户选择要清理的目录 - options = [] - if input_files: - options.append(("输入目录(data/input)", input_dir)) - if output_files: - options.append(("输出目录(data/output)", output_dir)) - - # 创建临时的选择对话框 - dialog = tk.Toplevel() - dialog.title("选择要清理的目录") - dialog.geometry("300x200") - dialog.transient(log_widget.winfo_toplevel()) # 设置为主窗口的子窗口 - dialog.grab_set() # 模态对话框 - - tk.Label(dialog, text="请选择要清理的目录:", font=("Arial", 12)).pack(pady=10) - - # 选择变量 - choices = {} - for name, path in options: - var = tk.BooleanVar(value=True) # 默认选中 - choices[path] = var - tk.Checkbutton(dialog, text=name, variable=var, font=("Arial", 10)).pack(anchor=tk.W, padx=20, pady=5) - - # 删除前备份选项 - backup_var = tk.BooleanVar(value=False) - tk.Checkbutton(dialog, text="删除前备份文件", variable=backup_var, font=("Arial", 10)).pack(anchor=tk.W, padx=20, pady=5) - - result = {"confirmed": False, "choices": {}, "backup": False} - - def on_confirm(): - result["confirmed"] = True - result["choices"] = {path: var.get() for path, var in choices.items()} - result["backup"] = backup_var.get() - dialog.destroy() - - def on_cancel(): - dialog.destroy() - - # 按钮 - button_frame = tk.Frame(dialog) - button_frame.pack(pady=10) - tk.Button(button_frame, text="确认", command=on_confirm, width=10).pack(side=tk.LEFT, padx=10) - tk.Button(button_frame, text="取消", command=on_cancel, width=10).pack(side=tk.LEFT, padx=10) + try: + widget.configure(bg=theme["bg"], fg=theme["fg"]) + except: + pass + + # 递归应用到所有子部件 + for child in widget.winfo_children(): + if isinstance(child, tk.Button) and not isinstance(child, ttk.Button): + child.configure(bg=theme["button_bg"], fg=theme["button_fg"]) + elif isinstance(child, scrolledtext.ScrolledText): + child.configure(bg=theme["log_bg"], fg=theme["log_fg"]) + else: + try: + child.configure(bg=theme["bg"], fg=theme["fg"]) + except: + pass + + # 递归处理子部件的子部件 + apply_theme(child, theme_mode) + +def toggle_theme(root, log_widget, status_bar=None): + """切换主题模式""" + global THEME_MODE - # 等待对话框关闭 - dialog.wait_window() + # 切换主题模式 + THEME_MODE = "dark" if THEME_MODE == "light" else "light" - if not result["confirmed"]: - add_to_log(log_widget, "清理操作已取消\n") - return + # 应用主题到整个界面 + apply_theme(root) - # 备份文件 - if result["backup"]: - backup_dir = os.path.join("data", "backup", datetime.datetime.now().strftime("%Y%m%d%H%M%S")) - os.makedirs(backup_dir, exist_ok=True) - add_to_log(log_widget, f"创建备份目录: {backup_dir}\n") - - for dir_path, selected in result["choices"].items(): - if selected: - dir_name = os.path.basename(dir_path) - backup_subdir = os.path.join(backup_dir, dir_name) - os.makedirs(backup_subdir, exist_ok=True) - - files = [f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f))] - for file in files: - src = os.path.join(dir_path, file) - dst = os.path.join(backup_subdir, file) - try: - shutil.copy2(src, dst) - add_to_log(log_widget, f"已备份: {src} -> {dst}\n") - except Exception as e: - add_to_log(log_widget, f"备份失败: {src}, 错误: {e}\n") - - # 删除所选目录的文件 - for dir_path, selected in result["choices"].items(): - if selected: - files = [f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f))] - for file in files: - file_path = os.path.join(dir_path, file) - try: - os.remove(file_path) - add_to_log(log_widget, f"已删除: {file_path}\n") - files_deleted += 1 - except Exception as e: - add_to_log(log_widget, f"删除失败: {file_path}, 错误: {e}\n") + # 配置日志样式 + log_widget.configure(bg=THEMES[THEME_MODE]["log_bg"], fg=THEMES[THEME_MODE]["log_fg"]) - # 显示结果 - add_to_log(log_widget, f"清理完成,共删除 {files_deleted} 个文件\n") - messagebox.showinfo("清理完成", f"共删除 {files_deleted} 个文件") + # 设置状态栏 + if status_bar: + apply_theme(status_bar) + + # 保存主题设置 + try: + with open("data/user_settings.json", "w") as f: + json.dump({"theme": THEME_MODE}, f) + except: + pass + + return THEME_MODE -def clean_cache(log_widget): - """清除缓存,重置处理记录""" - add_to_log(log_widget, "开始清除缓存...\n") +def ensure_directories(): + """确保必要的目录结构存在""" + directories = ["data/input", "data/output", "data/temp", "logs"] + for directory in directories: + if not os.path.exists(directory): + os.makedirs(directory, exist_ok=True) + print(f"创建目录: {directory}") + +class LogRedirector: + """日志重定向器,用于捕获命令输出并显示到界面""" + def __init__(self, text_widget): + self.text_widget = text_widget + self.buffer = "" + self.terminal = sys.__stdout__ # 保存原始的stdout引用 + + def write(self, string): + self.buffer += string + # 同时输出到终端 + self.terminal.write(string) + # 在UI线程中更新文本控件 + self.text_widget.after(0, self.update_text_widget) + + def update_text_widget(self): + self.text_widget.configure(state=tk.NORMAL) + + # 根据内容使用不同的标签 + if self.buffer.strip(): + # 检测不同类型的消息并应用相应样式 + if any(marker in self.buffer.lower() for marker in ["错误", "error", "失败", "异常", "exception"]): + self.text_widget.insert(tk.END, self.buffer, "error") + elif any(marker in self.buffer.lower() for marker in ["警告", "warning"]): + self.text_widget.insert(tk.END, self.buffer, "warning") + elif any(marker in self.buffer.lower() for marker in ["成功", "success", "完成", "成功处理"]): + self.text_widget.insert(tk.END, self.buffer, "success") + elif any(marker in self.buffer.lower() for marker in ["info", "信息", "开始", "处理中"]): + self.text_widget.insert(tk.END, self.buffer, "info") + else: + self.text_widget.insert(tk.END, self.buffer, "normal") + else: + self.text_widget.insert(tk.END, self.buffer) + + # 自动滚动到底部 + self.text_widget.see(tk.END) + self.text_widget.configure(state=tk.DISABLED) + self.buffer = "" + + def flush(self): + self.terminal.flush() # 确保终端也被刷新 + +def create_collapsible_frame(parent, title, initial_state=True): + """创建可折叠的面板""" + frame = tk.Frame(parent) + frame.pack(fill=tk.X, pady=5) - cache_files = [ - "data/processed_files.json", # OCR处理记录 - "data/output/processed_files.json" # Excel处理记录 - ] + # 标题栏 + title_frame = tk.Frame(frame) + title_frame.pack(fill=tk.X) - for cache_file in cache_files: - try: - if os.path.exists(cache_file): - # 创建备份 - backup_file = f"{cache_file}.bak" - shutil.copy2(cache_file, backup_file) - - # 清空或删除缓存文件 - with open(cache_file, 'w') as f: - f.write('{}') # 写入空的JSON对象 - - add_to_log(log_widget, f"已清除缓存文件: {cache_file},并创建备份: {backup_file}\n") - else: - add_to_log(log_widget, f"缓存文件不存在: {cache_file}\n") - except Exception as e: - add_to_log(log_widget, f"清除缓存文件时出错: {cache_file}, 错误: {e}\n") + # 折叠指示器 + state_var = tk.BooleanVar(value=initial_state) + indicator = "▼" if initial_state else "►" + state_label = tk.Label(title_frame, text=indicator, font=("Arial", 10, "bold")) + state_label.pack(side=tk.LEFT, padx=5) + + # 标题 + title_label = tk.Label(title_frame, text=title, font=("Arial", 11, "bold")) + title_label.pack(side=tk.LEFT, padx=5) + + # 内容区域 + content_frame = tk.Frame(frame) + if initial_state: + content_frame.pack(fill=tk.X, padx=20, pady=5) + + # 点击事件处理函数 + def toggle_collapse(event=None): + current_state = state_var.get() + new_state = not current_state + state_var.set(new_state) + + # 更新指示器 + state_label.config(text="▼" if new_state else "►") + + # 显示或隐藏内容 + if new_state: + content_frame.pack(fill=tk.X, padx=20, pady=5) + else: + content_frame.pack_forget() - add_to_log(log_widget, "缓存清除完成,系统将重新处理所有文件\n") - messagebox.showinfo("缓存清除", "缓存已清除,系统将重新处理所有文件。") + # 绑定点击事件 + title_frame.bind("", toggle_collapse) + state_label.bind("", toggle_collapse) + title_label.bind("", toggle_collapse) + + return content_frame, state_var def main(): """主函数""" @@ -465,7 +678,7 @@ def main(): # 创建窗口 root = tk.Tk() root.title("益选-OCR订单处理系统 v1.0") - root.geometry("1200x800") # 增加窗口宽度以容纳日志 + root.geometry("1200x650") # 增加窗口高度以容纳更多元素 # 创建主区域分割 main_pane = tk.PanedWindow(root, orient=tk.HORIZONTAL) @@ -476,130 +689,331 @@ def main(): main_pane.add(left_frame) # 标题 - tk.Label(left_frame, text="益选-OCR订单处理系统", font=("Arial", 16)).pack(pady=10) + title_frame = tk.Frame(left_frame) + title_frame.pack(fill=tk.X, pady=10) + + # 主标题 + tk.Label(title_frame, text="益选-OCR订单处理系统", font=("Arial", 16, "bold")).pack(side=tk.LEFT, padx=10) - # 功能按钮区域 - buttons_frame = tk.Frame(left_frame) - buttons_frame.pack(pady=10, fill=tk.Y) + # 添加作者信息 + author_frame = tk.Frame(left_frame) + author_frame.pack(fill=tk.X, pady=0) + tk.Label(author_frame, text="作者:欢欢欢", font=("Arial", 10)).pack(side=tk.LEFT, padx=15) # 创建日志显示区域 log_frame = tk.Frame(main_pane) main_pane.add(log_frame) # 日志标题 - tk.Label(log_frame, text="处理日志", font=("Arial", 12)).pack(pady=5) + tk.Label(log_frame, text="处理日志", font=("Arial", 12, "bold")).pack(pady=5) # 日志文本区域 log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=30, width=60) log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) log_text.configure(state=tk.DISABLED) # 设置为只读 + # 为日志文本添加标签样式 + log_text.tag_configure("normal", foreground="#000000") + log_text.tag_configure("command", foreground="#17a2b8", font=("Arial", 10, "bold")) + log_text.tag_configure("time", foreground="#17a2b8", font=("Arial", 9)) + log_text.tag_configure("separator", foreground="#cccccc") + log_text.tag_configure("error", foreground="#dc3545") + log_text.tag_configure("warning", foreground="#ffc107") + log_text.tag_configure("success", foreground="#28a745") + log_text.tag_configure("info", foreground="#17a2b8") + + # 创建状态栏 + status_bar = StatusBar(root) + status_bar.pack(side=tk.BOTTOM, fill=tk.X) + # 日志初始内容 - add_to_log(log_text, "益选-OCR订单处理系统启动器 v1.0\n") - add_to_log(log_text, f"当前工作目录: {os.getcwd()}\n") - add_to_log(log_text, "系统已准备就绪,请选择要执行的操作。\n") + add_to_log(log_text, "益选-OCR订单处理系统启动器 v1.0\n", "command") + add_to_log(log_text, f"当前工作目录: {os.getcwd()}\n", "info") + add_to_log(log_text, "系统已准备就绪,请选择要执行的操作。\n", "normal") - # OCR识别按钮 - tk.Button( - buttons_frame, - text="OCR图像识别 (批量)", - width=20, - height=2, - command=lambda: run_command_with_logging(["python", "run.py", "ocr", "--batch"], log_text) - ).pack(pady=5) - - # 单个图片处理 - tk.Button( - buttons_frame, - text="处理单个图片", - width=20, - height=2, - command=lambda: process_single_image(log_text) - ).pack(pady=5) + # 创建按钮区域(使用两列布局) + button_area = tk.Frame(left_frame) + button_area.pack(fill=tk.BOTH, expand=True, pady=10) + + # 按钮尺寸和间距 + button_width = 15 + button_height = 2 + button_padx = 5 + button_pady = 5 - # Excel处理按钮 + # 第一行 + row1 = tk.Frame(button_area) + row1.pack(fill=tk.X, pady=button_pady) + + # 处理Excel文件 tk.Button( - buttons_frame, + row1, text="处理Excel文件", - width=20, - height=2, - command=lambda: process_excel_file(log_text) - ).pack(pady=5) + width=button_width, + height=button_height, + command=lambda: process_excel_file(log_text, status_bar) + ).pack(side=tk.LEFT, padx=button_padx) - # 订单合并按钮 + # OCR批量识别 tk.Button( - buttons_frame, - text="合并采购单", - width=20, - height=2, - command=lambda: run_command_with_logging(["python", "run.py", "merge"], log_text) - ).pack(pady=5) - - # 完整流程按钮 + row1, + text="OCR批量识别", + width=button_width, + height=button_height, + command=lambda: run_command_with_logging(["python", "run.py", "ocr", "--batch"], log_text, status_bar) + ).pack(side=tk.LEFT, padx=button_padx) + + # 第二行 + row2 = tk.Frame(button_area) + row2.pack(fill=tk.X, pady=button_pady) + + # 完整处理流程 tk.Button( - buttons_frame, + row2, text="完整处理流程", - width=20, - height=2, - command=lambda: run_command_with_logging(["python", "run.py", "pipeline"], log_text) - ).pack(pady=5) + width=button_width, + height=button_height, + command=lambda: run_command_with_logging(["python", "run.py", "pipeline"], log_text, status_bar) + ).pack(side=tk.LEFT, padx=button_padx) - # 清除缓存按钮 + # 处理单个图片 tk.Button( - buttons_frame, - text="清除处理缓存", - width=20, - height=2, - command=lambda: clean_cache(log_text) - ).pack(pady=5) + row2, + text="处理单个图片", + width=button_width, + height=button_height, + command=lambda: process_single_image(log_text, status_bar) + ).pack(side=tk.LEFT, padx=button_padx) - # 整理文件按钮 + # 第三行 + row3 = tk.Frame(button_area) + row3.pack(fill=tk.X, pady=button_pady) + + # 合并采购单按钮 tk.Button( - buttons_frame, + row3, + text="合并采购单", + width=button_width, + height=button_height, + command=lambda: run_command_with_logging(["python", "run.py", "merge"], log_text, status_bar) + ).pack(side=tk.LEFT, padx=button_padx) + + # 整理项目文件 + tk.Button( + row3, text="整理项目文件", - width=20, - height=2, + width=button_width, + height=button_height, command=lambda: organize_project_files(log_text) - ).pack(pady=5) + ).pack(side=tk.LEFT, padx=button_padx) + + # 第四行 + row4 = tk.Frame(button_area) + row4.pack(fill=tk.X, pady=button_pady) + + # 清除处理缓存按钮 + tk.Button( + row4, + text="清除处理缓存", + width=button_width, + height=button_height, + command=lambda: clean_cache(log_text) + ).pack(side=tk.LEFT, padx=button_padx) # 清理文件按钮 tk.Button( - buttons_frame, + row4, text="清理文件", - width=20, - height=2, + width=button_width, + height=button_height, command=lambda: clean_data_files(log_text) - ).pack(pady=5) + ).pack(side=tk.LEFT, padx=button_padx) + + # 第五行 + row5 = tk.Frame(button_area) + row5.pack(fill=tk.X, pady=button_pady) # 打开输入目录 tk.Button( - buttons_frame, + row5, text="打开输入目录", - width=20, + width=button_width, + height=button_height, command=lambda: os.startfile(os.path.abspath("data/input")) - ).pack(pady=5) + ).pack(side=tk.LEFT, padx=button_padx) # 打开输出目录 tk.Button( - buttons_frame, + row5, text="打开输出目录", - width=20, + width=button_width, + height=button_height, command=lambda: os.startfile(os.path.abspath("data/output")) - ).pack(pady=5) - - # 清空日志按钮 - tk.Button( - buttons_frame, - text="清空日志", - width=20, - command=lambda: log_text.delete(1.0, tk.END) - ).pack(pady=5) + ).pack(side=tk.LEFT, padx=button_padx) # 底部说明 - tk.Label(left_frame, text="© 2025 益选-OCR订单处理系统 v1.0", font=("Arial", 10)).pack(side=tk.BOTTOM, pady=10) + tk.Label(left_frame, text="© 2025 益选-OCR订单处理系统 v1.0 by 欢欢欢", font=("Arial", 9)).pack(side=tk.BOTTOM, pady=10) + + # 修改单个图片和Excel处理函数以使用状态栏 + def process_single_image_with_status(log_widget, status_bar): + status_bar.set_status("选择图片中...") + file_path = select_file(log_widget) + if file_path: + status_bar.set_status("开始处理图片...") + run_command_with_logging(["python", "run.py", "ocr", "--input", file_path], log_widget, status_bar) + else: + status_bar.set_status("操作已取消") + add_to_log(log_widget, "未选择文件,操作已取消\n", "warning") + + def process_excel_file_with_status(log_widget, status_bar): + status_bar.set_status("选择Excel文件中...") + file_path = select_excel_file(log_widget) + if file_path: + status_bar.set_status("开始处理Excel文件...") + run_command_with_logging(["python", "run.py", "excel", "--input", file_path], log_widget, status_bar) + else: + status_bar.set_status("开始处理最新Excel文件...") + add_to_log(log_widget, "未选择文件,尝试处理最新的Excel文件\n", "info") + run_command_with_logging(["python", "run.py", "excel"], log_widget, status_bar) + + # 替换原始函数引用 + global process_single_image, process_excel_file + process_single_image = process_single_image_with_status + process_excel_file = process_excel_file_with_status # 启动主循环 root.mainloop() +def add_to_log(log_widget, text, tag="normal"): + """向日志窗口添加文本,支持样式标签""" + log_widget.configure(state=tk.NORMAL) + log_widget.insert(tk.END, text, tag) + log_widget.see(tk.END) # 自动滚动到底部 + log_widget.configure(state=tk.DISABLED) + +def select_file(log_widget, file_types=[("所有文件", "*.*")], title="选择文件"): + """通用文件选择对话框""" + file_path = filedialog.askopenfilename(title=title, filetypes=file_types) + if file_path: + add_to_log(log_widget, f"已选择文件: {file_path}\n", "info") + return file_path + +def select_excel_file(log_widget): + """选择Excel文件""" + return select_file( + log_widget, + [("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")], + "选择Excel文件" + ) + +def clean_cache(log_widget): + """清除处理缓存""" + try: + # 清除OCR缓存 + ocr_cache_file = os.path.join("data/output", "processed_files.json") + if os.path.exists(ocr_cache_file): + os.remove(ocr_cache_file) + add_to_log(log_widget, f"已清除OCR处理缓存: {ocr_cache_file}\n", "success") + + # 清除临时文件夹 + temp_dir = os.path.join("data/temp") + if os.path.exists(temp_dir): + for file in os.listdir(temp_dir): + file_path = os.path.join(temp_dir, file) + try: + if os.path.isfile(file_path): + os.remove(file_path) + add_to_log(log_widget, f"已清除临时文件: {file_path}\n", "info") + except Exception as e: + add_to_log(log_widget, f"清除文件时出错: {file_path}, 错误: {str(e)}\n", "error") + + add_to_log(log_widget, "缓存清除完成\n", "success") + except Exception as e: + add_to_log(log_widget, f"清除缓存时出错: {str(e)}\n", "error") + +def organize_project_files(log_widget): + """整理项目文件结构""" + try: + # 创建必要的目录 + directories = ["data/input", "data/output", "data/temp", "logs"] + for directory in directories: + if not os.path.exists(directory): + os.makedirs(directory, exist_ok=True) + add_to_log(log_widget, f"创建目录: {directory}\n", "info") + + # 移动日志文件到logs目录 + for file in os.listdir("."): + if file.endswith(".log") and os.path.isfile(file): + dest_path = os.path.join("logs", file) + try: + shutil.move(file, dest_path) + add_to_log(log_widget, f"移动日志文件: {file} -> {dest_path}\n", "info") + except Exception as e: + add_to_log(log_widget, f"移动文件时出错: {file}, 错误: {str(e)}\n", "error") + + # 移动配置文件到config目录 + if not os.path.exists("config"): + os.makedirs("config", exist_ok=True) + + for file in os.listdir("."): + if file.endswith(".ini") or file.endswith(".cfg") or file.endswith(".json"): + if os.path.isfile(file) and file != "data/user_settings.json": + dest_path = os.path.join("config", file) + try: + shutil.move(file, dest_path) + add_to_log(log_widget, f"移动配置文件: {file} -> {dest_path}\n", "info") + except Exception as e: + add_to_log(log_widget, f"移动文件时出错: {file}, 错误: {str(e)}\n", "error") + + add_to_log(log_widget, "项目文件整理完成\n", "success") + except Exception as e: + add_to_log(log_widget, f"整理项目文件时出错: {str(e)}\n", "error") + +def clean_data_files(log_widget): + """清理数据文件""" + try: + # 确认清理 + if not messagebox.askyesno("确认清理", "确定要清理所有数据文件吗?这将删除所有输入和输出数据。"): + add_to_log(log_widget, "操作已取消\n", "info") + return + + # 清理输入目录 + input_dir = "data/input" + files_cleaned = 0 + for file in os.listdir(input_dir): + file_path = os.path.join(input_dir, file) + if os.path.isfile(file_path): + os.remove(file_path) + files_cleaned += 1 + + # 清理输出目录 + output_dir = "data/output" + for file in os.listdir(output_dir): + file_path = os.path.join(output_dir, file) + if os.path.isfile(file_path): + os.remove(file_path) + files_cleaned += 1 + + # 清理临时目录 + temp_dir = "data/temp" + for file in os.listdir(temp_dir): + file_path = os.path.join(temp_dir, file) + if os.path.isfile(file_path): + os.remove(file_path) + files_cleaned += 1 + + add_to_log(log_widget, f"已清理 {files_cleaned} 个数据文件\n", "success") + except Exception as e: + add_to_log(log_widget, f"清理数据文件时出错: {str(e)}\n", "error") + +def center_window(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('{}x{}+{}+{}'.format(width, height, x, y)) + if __name__ == "__main__": main() \ No newline at end of file