e4d62df7e3
- 智能供应商识别(蓉城易购/烟草/杨碧月/通用) - 百度 OCR 表格识别集成 - 规则引擎(列映射/数据清洗/单位转换/规格推断) - 条码映射管理与云端同步(Gitea REST API) - 云端同步支持:条码映射、供应商配置、商品资料、采购模板 - 拖拽一键处理(图片→OCR→Excel→合并) - 191 个单元测试 - 移除无用的模板管理功能 - 清理 IDE 产物目录 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
159 lines
5.8 KiB
Python
159 lines
5.8 KiB
Python
#!/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
|