refactor: extract common tab functionality to BaseTab and add decorators

- Add BaseTab base class for common tab initialization and logging
- Add constants module for shared GUI constants
- Move CheckboxTreeview to separate widget file
- Add admin_only and require_session decorators to utils.py
- Refactor DataExtractionTab and MaterialValidationTab to inherit BaseTab
- Remove duplicate _update_log method from tab classes

Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-02-26 21:17:37 +08:00
parent 3a9c6f0978
commit 18f784f067
8 changed files with 648 additions and 294 deletions

View File

@@ -1,9 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据提取标签页 - 稳定性修复版
数据提取标签页
修复了 LogText.info 不支持 add_timestamp 参数导致的 TypeError
从 ERP 系统提取生产订单数据的标签页
继承自 BaseTab使用统一的日志系统。
"""
import os
@@ -14,6 +15,7 @@ import queue
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from pathlib import Path
from gui.base_tab import BaseTab
from gui.widgets import FileSelector, LogText, ProductionIdInput, GuiTextHandler
from gui.config_manager import ConfigManager
from gui.log_config import setup_gui_logging, get_logger
@@ -21,13 +23,11 @@ from gui.progress import ProgressInfo, ProgressCalculator
from gui.utils import RealtimeOutput
class DataExtractionTab(ttk.Frame):
class DataExtractionTab(BaseTab):
"""数据提取标签页"""
def __init__(self, parent, config: ConfigManager, main_window=None):
super().__init__(parent)
self.config = config
self.main_window = main_window
super().__init__(parent, config, main_window)
self.extracting = False
self.extractor = None
self.extraction_thread = None
@@ -42,10 +42,11 @@ class DataExtractionTab(ttk.Frame):
self.create_widgets()
self._apply_ui_config()
# 初始化日志消息
try:
self.log_text.info("数据提取标签页已就绪")
except:
pass
except Exception as e:
self.logger.debug(f"初始化日志消息失败: {e}")
def create_widgets(self):
horizontal_paned = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
@@ -137,11 +138,14 @@ class DataExtractionTab(ttk.Frame):
self.production_id_input.apply_font(font_family, font_size)
if hasattr(self.log_text, 'apply_font'):
self.log_text.apply_font(font_family, font_size)
except: pass
except Exception as e:
self.logger.debug(f"应用 UI 配置失败: {e}")
def _set_pane_width(self, width: int):
try: self.horizontal_paned.sashpos(0, width)
except: pass
try:
self.horizontal_paned.sashpos(0, width)
except tk.TclError as e:
self.logger.debug(f"设置窗格宽度失败: {e}")
def start_extraction(self):
production_ids = self.production_id_input.get()
@@ -208,8 +212,10 @@ class DataExtractionTab(ttk.Frame):
self._update_log(f"运行时错误: {str(e)}", "ERROR")
finally:
if temp_file and os.path.exists(temp_file):
try: os.unlink(temp_file)
except: pass
try:
os.unlink(temp_file)
except OSError as e:
self.logger.debug(f"清理临时文件失败: {e}")
self.after(0, self._extraction_complete)
def _extraction_complete(self):
@@ -225,32 +231,18 @@ class DataExtractionTab(ttk.Frame):
value, message = self.progress_queue.get_nowait()
self.progress_bar["value"] = value
self.status_label.config(text=message)
except queue.Empty: break
finally: self.after(50, self._poll_progress_queue)
except queue.Empty:
break
except Exception as e:
self.logger.debug(f"轮询进度队列失败: {e}")
finally:
self.after(50, self._poll_progress_queue)
def _update_progress(self, value: int, message: str):
try: self.progress_queue.put_nowait((value, message))
except: pass
def _update_log(self, message: str, level: str = "INFO"):
"""
标准的日志更新方法(兼容接口)
通过统一的 logging 系统输出日志,自动同时输出到控制台和 GUI。
Args:
message: 日志消息
level: 日志级别 (INFO, SUCCESS, WARNING, ERROR, DEBUG)
"""
# 将自定义级别映射到 logging 级别
level_upper = level.upper()
if level_upper == "SUCCESS":
# SUCCESS 映射到 INFO但在 UI 中仍显示为 SUCCESS
self.logger.info(message)
else:
# 其他级别直接映射
log_level = getattr(logging, level_upper, logging.INFO)
self.logger.log(log_level, message)
try:
self.progress_queue.put_nowait((value, message))
except queue.Full as e:
self.logger.debug(f"进度队列已满: {e}")
def _on_production_ids_changed(self, event=None):
if self.main_window: