refactor: implement UI code optimization and refactoring

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>
This commit is contained in:
Misaka Server
2026-02-26 18:40:26 +08:00
parent 3a9c6f0978
commit f715cb97e3
16 changed files with 1735 additions and 313 deletions

202
gui/utils/error_handler.py Normal file
View File

@@ -0,0 +1,202 @@
#!/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)