feat: 益选 OCR 订单处理系统初始提交
- 智能供应商识别(蓉城易购/烟草/杨碧月/通用) - 百度 OCR 表格识别集成 - 规则引擎(列映射/数据清洗/单位转换/规格推断) - 条码映射管理与云端同步(Gitea REST API) - 云端同步支持:条码映射、供应商配置、商品资料、采购模板 - 拖拽一键处理(图片→OCR→Excel→合并) - 191 个单元测试 - 移除无用的模板管理功能 - 清理 IDE 产物目录 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""命令执行器模块"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
import datetime
|
||||
import re
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
from threading import Thread
|
||||
|
||||
from .logging_ui import LogRedirector
|
||||
from .result_previews import show_result_preview
|
||||
|
||||
# 任务状态跟踪
|
||||
_RUNNING_TASK = None
|
||||
|
||||
|
||||
def get_running_task():
|
||||
return _RUNNING_TASK
|
||||
|
||||
|
||||
def set_running_task(val):
|
||||
global _RUNNING_TASK
|
||||
_RUNNING_TASK = val
|
||||
|
||||
|
||||
def run_command_with_logging(command, log_widget, status_bar=None, on_complete=None):
|
||||
"""运行命令并将输出重定向到日志窗口"""
|
||||
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()
|
||||
start_perf = time.perf_counter()
|
||||
log_widget.configure(state=tk.NORMAL)
|
||||
log_widget.delete(1.0, tk.END)
|
||||
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)
|
||||
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
|
||||
log_redirector = LogRedirector(log_widget)
|
||||
|
||||
env = os.environ.copy()
|
||||
try:
|
||||
from app.config.settings import ConfigManager
|
||||
cfg = ConfigManager()
|
||||
env["OCR_OUTPUT_DIR"] = cfg.get_path('Paths', 'output_folder', fallback='data/output', create=True)
|
||||
env["OCR_INPUT_DIR"] = cfg.get_path('Paths', 'input_folder', fallback='data/input', create=True)
|
||||
env["OCR_TEMP_DIR"] = cfg.get_path('Paths', 'temp_folder', fallback='data/temp', create=True)
|
||||
except Exception:
|
||||
env["OCR_OUTPUT_DIR"] = os.path.abspath("data/output")
|
||||
env["OCR_INPUT_DIR"] = os.path.abspath("data/input")
|
||||
env["OCR_TEMP_DIR"] = os.path.abspath("data/temp")
|
||||
env["OCR_LOG_LEVEL"] = "DEBUG"
|
||||
|
||||
try:
|
||||
sys.stdout = log_redirector
|
||||
sys.stderr = log_redirector
|
||||
|
||||
print("日志重定向已启动,现在同时输出到终端和GUI")
|
||||
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
universal_newlines=True,
|
||||
env=env
|
||||
)
|
||||
|
||||
output_data = []
|
||||
for line in process.stdout:
|
||||
output_data.append(line)
|
||||
print(line.rstrip())
|
||||
|
||||
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()
|
||||
|
||||
end_time = datetime.datetime.now()
|
||||
duration_sec = max(0.0, time.perf_counter() - start_perf)
|
||||
|
||||
print(f"\n{'=' * 50}")
|
||||
print(f"执行完毕!返回码: {process.returncode}")
|
||||
print(f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"耗时: {duration_sec:.2f} 秒")
|
||||
|
||||
output_text = ''.join(output_data)
|
||||
|
||||
is_pipeline = "pipeline" in command
|
||||
no_merge_files = "未找到采购单文件" in output_text
|
||||
single_file = "只有1个采购单文件" in output_text
|
||||
|
||||
if is_pipeline and (no_merge_files or single_file):
|
||||
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:
|
||||
if on_complete:
|
||||
log_widget.after(0, lambda: on_complete(process.returncode, output_text))
|
||||
elif 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:
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
|
||||
_RUNNING_TASK = None
|
||||
if status_bar:
|
||||
log_widget.after(0, lambda: status_bar.set_running(False))
|
||||
|
||||
Thread(target=run_in_thread).start()
|
||||
|
||||
|
||||
def extract_progress_from_log(log_line):
|
||||
"""从日志行中提取进度信息"""
|
||||
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
|
||||
Reference in New Issue
Block a user