From db461e39f9683072b738808e81b87b52ef85ef41 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Tue, 10 Feb 2026 17:05:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20add=20Admin=20user=20selection=20dialog?= =?UTF-8?q?=20(=E7=AE=A1=E7=90=86=E5=91=98=E7=94=A8=E6=88=B7=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=AF=B9=E8=AF=9D=E6=A1=86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an Admin user logs in, display a centered dialog allowing them to select which user identity to use for the session. The Admin adopts the selected user's permissions and operates as that user throughout the session. Changes: - Add UserSelectionDialog class in gui/user_selection_dialog.py - Add SessionManager.switch_user() method to switch user identity - Add SessionManager.get_original_admin() method to retrieve original admin - Update MainWindow status bar to show when Admin operates as another user - Display format: "当前用户: {username} ({type}) - 以 {admin_username} 身份登录" Co-Authored-By: Claude Sonnet 4.5 --- auth/session_manager.py | 37 ++++++++- gui/main_window.py | 11 ++- gui/user_selection_dialog.py | 157 +++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 gui/user_selection_dialog.py diff --git a/auth/session_manager.py b/auth/session_manager.py index e4ad86c..bd22a57 100644 --- a/auth/session_manager.py +++ b/auth/session_manager.py @@ -1,7 +1,7 @@ """ Session Manager - Singleton pattern for managing authenticated user session """ -from typing import Optional +from typing import Optional, Dict, Any class SessionManager: @@ -150,3 +150,38 @@ class SessionManager: Dict with username and user_type if authenticated, None otherwise """ return self._current_user + + def switch_user(self, user_info: Dict[str, Any]) -> bool: + """ + Switch to a different user (Admin only feature) + + This allows an Admin user to operate as a different user + with that user's permissions. + + Args: + user_info: Dict with {id, username, user_type} + + Returns: + True if switch successful + """ + if not self._current_user: + return False + + # Store original admin user for reference + if not hasattr(self, '_original_admin_user'): + self._original_admin_user = self._current_user.copy() + + self._current_user = { + 'username': user_info['username'], + 'user_type': user_info['user_type'] + } + return True + + def get_original_admin(self) -> Optional[Dict[str, Any]]: + """ + Get the original Admin user before any user switch + + Returns: + Original admin user dict if a switch occurred, None otherwise + """ + return getattr(self, '_original_admin_user', None) diff --git a/gui/main_window.py b/gui/main_window.py index d03da2b..f329bf4 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -102,8 +102,17 @@ class MainWindow: # 用户信息显示 user_type_display = "管理员" if self.session_manager.is_admin() else "用户" + original_admin = self.session_manager.get_original_admin() + + if original_admin: + # Admin以其他用户身份操作 + user_info_text = f"当前用户: {self.session_manager.get_username()} ({user_type_display}) - 以 {original_admin['username']} 身份登录" + else: + # 正常登录 + user_info_text = f"当前用户: {self.session_manager.get_username()} ({user_type_display})" + self.user_info_var = tk.StringVar() - self.user_info_var.set(f"当前用户: {self.session_manager.get_username()} ({user_type_display})") + self.user_info_var.set(user_info_text) user_label = ttk.Label( self.status_bar, textvariable=self.user_info_var, anchor=tk.E ) diff --git a/gui/user_selection_dialog.py b/gui/user_selection_dialog.py new file mode 100644 index 0000000..65eb608 --- /dev/null +++ b/gui/user_selection_dialog.py @@ -0,0 +1,157 @@ +""" +User Selection Dialog - Allows Admin to choose which user identity to use +""" +import tkinter as tk +from tkinter import ttk, messagebox +from typing import Optional, Dict, Any, List + + +class UserSelectionDialog: + """ + Modal dialog for Admin users to select which user identity to use + + Usage: + dialog = UserSelectionDialog(parent, users_list, current_username) + selected_user = dialog.get_selected_user() + if selected_user: + # Use the selected user info + """ + + def __init__(self, parent, users: List[Dict[str, Any]], current_username: str): + """ + Initialize the user selection dialog + + Args: + parent: The parent Tkinter window + users: List of user dictionaries [{id, username, user_type}] + current_username: The currently logged-in Admin's username + """ + self.parent = parent + self.users = users + self.current_username = current_username + self.selected_user = None + self.dialog = None + self.selected_var = None + + self._create_dialog() + self.parent.wait_window(self.dialog) + + def _create_dialog(self): + """Create the user selection dialog UI""" + self.dialog = tk.Toplevel(self.parent) + self.dialog.title("选择用户身份") + self.dialog.geometry("450x400") + self.dialog.resizable(False, False) + + # Center on screen (not parent) + self.dialog.transient(self.parent) + self.dialog.grab_set() + + screen_width = self.dialog.winfo_screenwidth() + screen_height = self.dialog.winfo_screenheight() + dialog_width = 450 + dialog_height = 400 + x = (screen_width - dialog_width) // 2 + y = (screen_height - dialog_height) // 2 + self.dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}") + + self._create_widgets() + + def _create_widgets(self): + """Create dialog widgets""" + 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=('', 14, 'bold') + ) + title_label.pack(pady=(0, 20)) + + # User list with scrollbar + list_frame = ttk.Frame(main_frame) + list_frame.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar(list_frame) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + self.selected_var = tk.StringVar() + + # Sort users: current user first, then by username + sorted_users = sorted( + self.users, + key=lambda u: (0 if u['username'] == self.current_username else 1, u['username']) + ) + + for user in sorted_users: + username = user['username'] + user_type = user['user_type'] + is_current = username == self.current_username + + # Mark current user + display_text = f"{username} ({user_type})" + if is_current: + display_text += " [当前]" + + rb = ttk.Radiobutton( + list_frame, + text=display_text, + variable=self.selected_var, + value=username + ) + rb.pack(anchor=tk.W, pady=3, padx=5) + + # Select current user by default + self.selected_var.set(self.current_username) + + # Buttons + button_frame = ttk.Frame(main_frame) + button_frame.pack(pady=(20, 0)) + + confirm_btn = ttk.Button( + button_frame, + text="确认", + command=self._on_confirm, + width=10 + ) + confirm_btn.pack(side=tk.LEFT, padx=5) + + cancel_btn = ttk.Button( + button_frame, + text="取消", + command=self._on_cancel, + width=10 + ) + cancel_btn.pack(side=tk.LEFT, padx=5) + + def _on_confirm(self): + """Handle confirm button click""" + selected_username = self.selected_var.get() + + if not selected_username: + messagebox.showwarning("未选择", "请选择一个用户") + return + + # Find the selected user + for user in self.users: + if user['username'] == selected_username: + self.selected_user = user + break + + self.dialog.destroy() + + def _on_cancel(self): + """Handle cancel button click""" + self.selected_user = None + self.dialog.destroy() + + def get_selected_user(self) -> Optional[Dict[str, Any]]: + """ + Get the selected user info + + Returns: + User dict {id, username, user_type} if confirmed, None if cancelled + """ + return self.selected_user