e4d62df7e3
- 智能供应商识别(蓉城易购/烟草/杨碧月/通用) - 百度 OCR 表格识别集成 - 规则引擎(列映射/数据清洗/单位转换/规格推断) - 条码映射管理与云端同步(Gitea REST API) - 云端同步支持:条码映射、供应商配置、商品资料、采购模板 - 拖拽一键处理(图片→OCR→Excel→合并) - 191 个单元测试 - 移除无用的模板管理功能 - 清理 IDE 产物目录 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
127 lines
4.1 KiB
Python
127 lines
4.1 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""GUI日志处理模块"""
|
|
|
|
import logging
|
|
import queue
|
|
import sys
|
|
import tkinter as tk
|
|
|
|
# 全局日志队列,用于异步更新UI
|
|
LOG_QUEUE = queue.Queue()
|
|
|
|
|
|
class LogRedirector:
|
|
"""日志重定向器,用于捕获命令输出并显示到界面"""
|
|
def __init__(self, text_widget):
|
|
self.text_widget = text_widget
|
|
self.buffer = ""
|
|
self.terminal = sys.__stdout__
|
|
|
|
def write(self, string):
|
|
self.buffer += string
|
|
self.terminal.write(string)
|
|
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()
|
|
|
|
|
|
class GUILogHandler(logging.Handler):
|
|
"""自定义日志处理器,将日志放入队列,由GUI主线程定时消费"""
|
|
def __init__(self, text_widget):
|
|
super().__init__()
|
|
self.text_widget = text_widget
|
|
|
|
def emit(self, record):
|
|
try:
|
|
msg = self.format(record)
|
|
if record.levelno >= logging.ERROR:
|
|
tag = "error"
|
|
elif record.levelno >= logging.WARNING:
|
|
tag = "warning"
|
|
elif record.levelno >= logging.INFO:
|
|
tag = "info"
|
|
else:
|
|
tag = "normal"
|
|
|
|
LOG_QUEUE.put((msg + "\n", tag))
|
|
except Exception:
|
|
self.handleError(record)
|
|
|
|
|
|
def poll_log_queue(text_widget):
|
|
"""定期从队列中读取日志并更新UI"""
|
|
try:
|
|
updated = False
|
|
while not LOG_QUEUE.empty():
|
|
msg, tag = LOG_QUEUE.get_nowait()
|
|
text_widget.configure(state=tk.NORMAL)
|
|
text_widget.insert(tk.END, msg, tag)
|
|
updated = True
|
|
|
|
if updated:
|
|
text_widget.see(tk.END)
|
|
text_widget.configure(state=tk.DISABLED)
|
|
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
text_widget.after(100, lambda: poll_log_queue(text_widget))
|
|
|
|
|
|
def init_gui_logger(text_widget, level=logging.INFO):
|
|
handler = GUILogHandler(text_widget)
|
|
handler.setLevel(level)
|
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
handler.setFormatter(formatter)
|
|
root_logger = logging.getLogger()
|
|
for h in root_logger.handlers[:]:
|
|
if isinstance(h, logging.StreamHandler):
|
|
root_logger.removeHandler(h)
|
|
if not any(isinstance(h, GUILogHandler) for h in root_logger.handlers):
|
|
root_logger.addHandler(handler)
|
|
root_logger.setLevel(level)
|
|
return handler
|
|
|
|
|
|
def dispose_gui_logger():
|
|
root_logger = logging.getLogger()
|
|
for handler in root_logger.handlers[:]:
|
|
if isinstance(handler, GUILogHandler):
|
|
root_logger.removeHandler(handler)
|
|
try:
|
|
handler.close()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def add_to_log(log_widget, text, tag="normal"):
|
|
"""向日志队列添加文本,由 poll_log_queue 消费并更新 UI"""
|
|
if log_widget is None:
|
|
print(f"[{tag}] {text}", end="")
|
|
return
|
|
|
|
LOG_QUEUE.put((text, tag))
|