Files
playwrite/gui/login_dialog.py
Misaka Server f715cb97e3 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>
2026-02-26 18:40:26 +08:00

188 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Login Dialog - Modal dialog for user authentication
"""
import socket
import tkinter as tk
from tkinter import ttk, messagebox
from typing import Optional, Tuple
from gui.widgets.base_dialog import BaseDialog
class LoginDialog:
"""
Modal login dialog that captures user credentials
Usage:
login_dialog = LoginDialog(parent)
credentials = login_dialog.get_credentials()
if credentials:
username, password = credentials
# Proceed with authentication
"""
def __init__(self, parent):
"""
Initialize the login dialog
Args:
parent: The parent Tkinter window
"""
self.parent = parent
self.result = None # Will hold (username, password) or None
self.dialog = None
self.username_entry = None
self.password_entry = None
# Create dialog as modal
self._create_dialog()
self.parent.wait_window(self.dialog)
def _create_dialog(self):
"""Create the login dialog UI"""
# 使用普通 Toplevel因为 LoginDialog 需要特殊的居中逻辑
self.dialog = tk.Toplevel(self.parent)
self.dialog.title("ERP 自动化工具 - 登录")
self.dialog.resizable(False, False)
# 设置固定大小
dialog_width = 400
dialog_height = 280
self.dialog.geometry(f"{dialog_width}x{dialog_height}")
# 设置为模态对话框
self.dialog.transient(self.parent)
self.dialog.grab_set()
# 居中显示到父窗口
self._center_on_parent()
# Create UI elements
self._create_widgets()
# Bind Enter key to login button
self.dialog.bind('<Return>', lambda e: self._on_login())
# Focus on username entry
self.username_entry.focus_set()
def _center_on_parent(self):
"""将对话框居中到父窗口"""
self.dialog.update_idletasks()
self.parent.update_idletasks()
dialog_width = 400
dialog_height = 280
parent_x = self.parent.winfo_x()
parent_y = self.parent.winfo_y()
parent_width = self.parent.winfo_width()
parent_height = self.parent.winfo_height()
x = parent_x + (parent_width - dialog_width) // 2
y = parent_y + (parent_height - dialog_height) // 2
self.dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}")
def _create_widgets(self):
"""Create the dialog widgets"""
# Main frame with padding
main_frame = ttk.Frame(self.dialog, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)
# Title
title_label = ttk.Label(
main_frame,
text="请登录",
font=('', 16, 'bold')
)
title_label.pack(pady=(0, 10))
# Computer name display
computer_name_label = ttk.Label(
main_frame,
text=f"当前计算机: {socket.gethostname()}",
font=('', 9),
foreground='gray'
)
computer_name_label.pack(pady=(0, 15))
# Username field
username_frame = ttk.Frame(main_frame)
username_frame.pack(fill=tk.X, pady=5)
ttk.Label(username_frame, text="用户名:", width=10).pack(side=tk.LEFT)
self.username_entry = ttk.Entry(username_frame)
self.username_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Password field
password_frame = ttk.Frame(main_frame)
password_frame.pack(fill=tk.X, pady=5)
ttk.Label(password_frame, text="密码:", width=10).pack(side=tk.LEFT)
self.password_entry = ttk.Entry(password_frame, show="*")
self.password_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Buttons frame
button_frame = ttk.Frame(main_frame)
button_frame.pack(pady=(20, 0))
# Login button
login_btn = ttk.Button(
button_frame,
text="登录",
command=self._on_login,
width=10
)
login_btn.pack(side=tk.LEFT, padx=5)
# Cancel button
cancel_btn = ttk.Button(
button_frame,
text="取消",
command=self._on_cancel,
width=10
)
cancel_btn.pack(side=tk.LEFT, padx=5)
# Version info
version_label = ttk.Label(
main_frame,
text="v1.0",
font=('', 8),
foreground='gray'
)
version_label.pack(side=tk.BOTTOM, pady=10)
def _on_login(self):
"""Handle login button click"""
username = self.username_entry.get().strip()
password = self.password_entry.get().strip()
if not username:
messagebox.showwarning("输入错误", "请输入用户名")
self.username_entry.focus_set()
return
if not password:
messagebox.showwarning("输入错误", "请输入密码")
self.password_entry.focus_set()
return
# Return credentials for validation
self.result = (username, password)
self.dialog.destroy()
def _on_cancel(self):
"""Handle cancel button click"""
self.result = None
self.dialog.destroy()
def get_credentials(self) -> Optional[Tuple[str, str]]:
"""
Get the entered credentials
Returns:
Tuple of (username, password) if user clicked Login,
None if user clicked Cancel or closed the dialog
"""
return self.result