This commit implements the comprehensive UI code refactoring plan to improve code quality, reduce duplication, and enhance maintainability. Phase 1: High Priority Improvements - Create BaseDialog class to eliminate window centering code duplication across ProgressDialog, DeleteProgressWindow, LoginDialog, and UserSelectionDialog (~30 lines of duplicate code removed) - Unify log color configuration with LogTheme class in gui/log_config.py - Create permission check decorators (@require_admin, @require_permission, @require_user_type, @handle_errors) - Update all dialog classes to use unified patterns Phase 2: Architecture Improvements - Create input validation framework (ValidationResult, Validator, ValidatedWidget classes) - Implement StateManager with observer pattern for component state sharing - Create unified ErrorHandler for consistent error handling - Extract CheckboxTreeview into reusable component New Modules: - gui/widgets/base_dialog.py - Base dialog class with modal setup and centering - gui/utils/decorators.py - Permission and error handling decorators - gui/utils/error_handler.py - Unified error handling with user-friendly messages - gui/utils/state_manager.py - State management with observer pattern - gui/utils/validators.py - Input validation framework - gui/material_validation/checkbox_treeview.py - Reusable checkbox table Modified Files: - gui/log_config.py - Added LogTheme class for centralized styling - gui/widgets/log_text.py - Use LogTheme.COLORS - gui/widgets/progress_dialog.py - Inherit from BaseDialog - gui/widgets/delete_progress_window.py - Inherit from BaseDialog, use LogTheme - gui/widgets/__init__.py - Add new exports, optional imports - gui/login_dialog.py - Use unified centering pattern - gui/user_selection_dialog.py - Use unified centering pattern - gui/material_validation_tab.py - Import CheckboxTreeview from new module Benefits: - Reduced code duplication by ~200 lines - Improved maintainability through centralized configuration - Better abstraction with 5 new reusable base classes - Enhanced type safety with type hints - Future-proof theming support via LogTheme Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
203 lines
5.7 KiB
Python
203 lines
5.7 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
Error Handler - 统一的错误处理器
|
||
|
||
提供统一的错误处理机制,将技术错误转换为用户友好的消息
|
||
"""
|
||
import logging
|
||
import traceback
|
||
from tkinter import messagebox
|
||
from typing import Optional, Type
|
||
import sys
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class ErrorHandler:
|
||
"""
|
||
统一的错误处理器
|
||
|
||
将技术错误转换为用户友好的消息,并记录详细日志
|
||
"""
|
||
|
||
# 错误类型到用户消息的映射
|
||
ERROR_MESSAGE_MAP = {
|
||
ConnectionError: "无法连接到服务器,请检查网络连接",
|
||
PermissionError: "权限不足,请联系管理员",
|
||
FileNotFoundError: "找不到指定的文件",
|
||
ValueError: "输入数据格式不正确",
|
||
TypeError: "数据类型错误,请检查输入",
|
||
KeyError: "数据缺失,请检查输入完整性",
|
||
TimeoutError: "操作超时,请重试",
|
||
RuntimeError: "运行时错误,请查看日志了解详情",
|
||
}
|
||
|
||
@staticmethod
|
||
def handle(
|
||
error: Exception,
|
||
context: str = "",
|
||
show_user: bool = True,
|
||
parent=None
|
||
):
|
||
"""
|
||
处理错误
|
||
|
||
记录详细日志,并可选择向用户显示友好的错误消息
|
||
|
||
Args:
|
||
error: 异常对象
|
||
context: 错误发生的上下文信息
|
||
show_user: 是否向用户显示错误消息
|
||
parent: 父窗口(用于显示消息框)
|
||
"""
|
||
# 记录详细日志
|
||
error_message = str(error)
|
||
if context:
|
||
logger.error(f"{context}: {error_message}", exc_info=error)
|
||
else:
|
||
logger.error(error_message, exc_info=error)
|
||
|
||
# 显示用户友好的错误信息
|
||
if show_user:
|
||
user_message = ErrorHandler._get_user_message(error)
|
||
if context:
|
||
user_message = f"[{context}]\n{user_message}"
|
||
|
||
if parent:
|
||
messagebox.showerror("操作失败", user_message, parent=parent)
|
||
else:
|
||
messagebox.showerror("操作失败", user_message)
|
||
|
||
@staticmethod
|
||
def _get_user_message(error: Exception) -> str:
|
||
"""
|
||
将技术错误转换为用户友好的消息
|
||
|
||
Args:
|
||
error: 异常对象
|
||
|
||
Returns:
|
||
用户友好的错误消息
|
||
"""
|
||
# 检查是否为已知错误类型
|
||
for error_type, message in ErrorHandler.ERROR_MESSAGE_MAP.items():
|
||
if isinstance(error, error_type):
|
||
return message
|
||
|
||
# 未知错误类型
|
||
error_name = type(error).__name__
|
||
error_msg = str(error)
|
||
|
||
# 如果错误消息为空或只包含类名,返回通用消息
|
||
if not error_msg or error_msg == error_name:
|
||
return "操作失败,请查看日志了解详情"
|
||
|
||
# 返回简化的错误消息(不包含技术细节)
|
||
# 限制长度以避免消息过长
|
||
if len(error_msg) > 200:
|
||
return f"{error_msg[:200]}..."
|
||
|
||
return error_msg
|
||
|
||
@staticmethod
|
||
def handle_with_retry(
|
||
error: Exception,
|
||
context: str = "",
|
||
retry_callback: Optional[callable] = None,
|
||
parent=None
|
||
) -> bool:
|
||
"""
|
||
处理错误并提供重试选项
|
||
|
||
Args:
|
||
error: 异常对象
|
||
context: 错误发生的上下文信息
|
||
retry_callback: 重试回调函数
|
||
parent: 父窗口
|
||
|
||
Returns:
|
||
True 如果用户选择重试,False 否则
|
||
"""
|
||
user_message = ErrorHandler._get_user_message(error)
|
||
if context:
|
||
user_message = f"[{context}]\n{user_message}"
|
||
|
||
user_message += "\n\n是否重试?"
|
||
|
||
if parent:
|
||
result = messagebox.askyesno("操作失败", user_message, parent=parent)
|
||
else:
|
||
result = messagebox.askyesno("操作失败", user_message)
|
||
|
||
if result and retry_callback:
|
||
try:
|
||
retry_callback()
|
||
return True
|
||
except Exception as e:
|
||
ErrorHandler.handle(e, f"{context} (重试)", True, parent)
|
||
return False
|
||
|
||
return result
|
||
|
||
@staticmethod
|
||
def log_exception(error: Exception, context: str = ""):
|
||
"""
|
||
仅记录异常到日志,不显示用户消息
|
||
|
||
Args:
|
||
error: 异常对象
|
||
context: 错误发生的上下文信息
|
||
"""
|
||
error_message = str(error)
|
||
if context:
|
||
logger.error(f"{context}: {error_message}", exc_info=error)
|
||
else:
|
||
logger.error(error_message, exc_info=error)
|
||
|
||
@staticmethod
|
||
def show_warning(message: str, parent=None):
|
||
"""
|
||
显示警告消息
|
||
|
||
Args:
|
||
message: 警告消息
|
||
parent: 父窗口
|
||
"""
|
||
if parent:
|
||
messagebox.showwarning("警告", message, parent=parent)
|
||
else:
|
||
messagebox.showwarning("警告", message)
|
||
|
||
@staticmethod
|
||
def show_info(message: str, parent=None):
|
||
"""
|
||
显示信息消息
|
||
|
||
Args:
|
||
message: 信息消息
|
||
parent: 父窗口
|
||
"""
|
||
if parent:
|
||
messagebox.showinfo("信息", message, parent=parent)
|
||
else:
|
||
messagebox.showinfo("信息", message)
|
||
|
||
@staticmethod
|
||
def ask_confirmation(message: str, parent=None) -> bool:
|
||
"""
|
||
询问用户确认
|
||
|
||
Args:
|
||
message: 确认消息
|
||
parent: 父窗口
|
||
|
||
Returns:
|
||
True 如果用户确认,False 否则
|
||
"""
|
||
if parent:
|
||
return messagebox.askyesno("确认", message, parent=parent)
|
||
else:
|
||
return messagebox.askyesno("确认", message)
|