#!/usr/bin/env python # -*- coding: utf-8 -*- """ OCR订单处理系统启动器 ----------------- 提供简单的图形界面,方便用户选择功能 """ import os import sys import time import subprocess import shutil import tkinter as tk from tkinter import messagebox, filedialog, scrolledtext from threading import Thread import datetime 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 = "" def write(self, string): self.buffer += string # 在UI线程中更新文本控件 self.text_widget.after(0, self.update_text_widget) 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 = "" def flush(self): pass def run_command_with_logging(command, log_widget): """运行命令并将输出重定向到日志窗口""" def run_in_thread(): # 记录命令开始执行的时间 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.configure(state=tk.DISABLED) # 设置环境变量,强制OCR模块输出到data目录 env = os.environ.copy() env["OCR_OUTPUT_DIR"] = os.path.abspath("data/output") env["OCR_INPUT_DIR"] = os.path.abspath("data/input") env["OCR_LOG_LEVEL"] = "DEBUG" # 设置更详细的日志级别 try: # 运行命令并捕获输出 process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True, env=env ) # 读取并显示输出 for line in process.stdout: log_widget.after(0, lambda l=line: add_to_log(log_widget, l)) # 等待进程结束 process.wait() # 记录命令结束时间 end_time = datetime.datetime.now() duration = end_time - start_time log_widget.after(0, lambda: add_to_log( log_widget, f"\n{'=' * 50}\n执行完毕!返回码: {process.returncode}\n" f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}\n" f"耗时: {duration.total_seconds():.2f} 秒\n" )) # 如果处理成功,显示成功信息 if process.returncode == 0: log_widget.after(0, lambda: messagebox.showinfo("操作成功", "处理完成!\n请在data/output目录查看结果。")) else: log_widget.after(0, lambda: messagebox.showerror("操作失败", f"处理失败,返回码:{process.returncode}")) except Exception as e: log_widget.after(0, lambda: add_to_log(log_widget, f"\n执行出错: {str(e)}\n")) log_widget.after(0, lambda: messagebox.showerror("执行错误", f"执行命令时出错: {str(e)}")) # 在新线程中运行,避免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 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 # 记录选择文件的信息 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 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 # 记录选择文件的信息 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): # 如果是不同的文件,则复制 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 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}") else: add_to_log(log_widget, "未选择文件,操作已取消\n") 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) else: add_to_log(log_widget, f"文件不存在: {file_path}\n") messagebox.showerror("错误", f"文件不存在: {file_path}") 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") # 转移根目录文件 files_moved = 0 # 处理日志文件 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") # 处理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" "原始文件保留在原位置,以确保数据安全。") else: add_to_log(log_widget, "没有需要整理的文件\n") messagebox.showinfo("整理完成", "没有需要整理的文件。") def main(): """主函数""" # 确保必要的目录结构存在并转移旧目录内容 ensure_directories() # 创建窗口 root = tk.Tk() root.title("OCR订单处理系统 v2.0") root.geometry("800x600") # 增加窗口宽度以容纳日志 # 创建主区域分割 main_pane = tk.PanedWindow(root, orient=tk.HORIZONTAL) main_pane.pack(fill=tk.BOTH, expand=1, padx=5, pady=5) # 左侧操作区域 left_frame = tk.Frame(main_pane, width=300) main_pane.add(left_frame) # 标题 tk.Label(left_frame, text="OCR订单处理系统", font=("Arial", 16)).pack(pady=10) # 功能按钮区域 buttons_frame = tk.Frame(left_frame) buttons_frame.pack(pady=10, fill=tk.Y) # 创建日志显示区域 log_frame = tk.Frame(main_pane) main_pane.add(log_frame) # 日志标题 tk.Label(log_frame, text="处理日志", font=("Arial", 12)).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) # 设置为只读 # 日志初始内容 add_to_log(log_text, "OCR订单处理系统启动器 v2.0\n") add_to_log(log_text, f"当前工作目录: {os.getcwd()}\n") add_to_log(log_text, "系统已准备就绪,请选择要执行的操作。\n") # 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) # Excel处理按钮 tk.Button( buttons_frame, text="处理Excel文件", width=20, height=2, command=lambda: process_excel_file(log_text) ).pack(pady=5) # 订单合并按钮 tk.Button( buttons_frame, text="合并采购单", width=20, height=2, command=lambda: run_command_with_logging(["python", "run.py", "merge"], log_text) ).pack(pady=5) # 完整流程按钮 tk.Button( buttons_frame, text="完整处理流程", width=20, height=2, command=lambda: run_command_with_logging(["python", "run.py", "pipeline"], log_text) ).pack(pady=5) # 整理文件按钮 tk.Button( buttons_frame, text="整理项目文件", width=20, height=2, command=lambda: organize_project_files(log_text) ).pack(pady=5) # 打开输入目录 tk.Button( buttons_frame, text="打开输入目录", width=20, command=lambda: os.startfile(os.path.abspath("data/input")) ).pack(pady=5) # 打开输出目录 tk.Button( buttons_frame, text="打开输出目录", width=20, 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) # 底部说明 tk.Label(left_frame, text="© 2025 OCR订单处理系统", font=("Arial", 10)).pack(side=tk.BOTTOM, pady=10) # 启动主循环 root.mainloop() if __name__ == "__main__": main()