orc-order-v2/diff.txt

1401 lines
105 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("<Button-1>", toggle_collapse)
+ state_label.bind("<Button-1>", toggle_collapse)
+ title_label.bind("<Button-1>", 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