From 2c9087ccf976be02bb563f0aaff18b2d746b9ee0 Mon Sep 17 00:00:00 2001 From: hujinyang Date: Mon, 19 Jan 2026 08:02:47 +0000 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=20/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- login.py | 240 +++++++++++++++ login_dialog.py | 783 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1023 insertions(+) create mode 100644 login.py create mode 100644 login_dialog.py diff --git a/login.py b/login.py new file mode 100644 index 0000000..24c36d3 --- /dev/null +++ b/login.py @@ -0,0 +1,240 @@ +'''用户认证登录模块''' +import json +import os +import datetime +from pathlib import Path +from typing import Tuple, Optional + +class SimpleAuth: + """用户认证管理器""" + + def __init__(self, config_path: str = None): + """ + 初始化认证管理器 + + Args: + config_path: 用户配置文件路径 + """ + self.config_path = config_path + self.users = {} + + # 初始化时加载用户数据 + if config_path: + self.load_users() + + def load_users(self) -> bool: + """加载用户数据""" + try: + if not self.config_path: + print("未设置配置文件路径") + return False + + # 如果文件不存在,创建空文件 + if not os.path.exists(self.config_path): + print(f"配置文件不存在,创建空文件: {self.config_path}") + return self.create_empty_file() + + # 读取文件内容 + with open(self.config_path, 'r', encoding='utf-8') as f: + content = f.read().strip() + + if not content: # 空文件 + self.users = {} + return True + + # 解析JSON + self.users = json.loads(content) + + print(f"已加载 {len(self.users)} 个用户") + return True + + except json.JSONDecodeError: + print("配置文件格式错误,重置为空文件") + return self.create_empty_file() + except Exception as e: + print(f"加载用户数据失败: {e}") + return False + + def create_empty_file(self) -> bool: + """创建空的配置文件""" + try: + if not self.config_path: + return False + + os.makedirs(os.path.dirname(self.config_path), exist_ok=True) + self.users = {} + + with open(self.config_path, 'w', encoding='utf-8') as f: + json.dump({}, f, indent=2, ensure_ascii=False) + + print(f"已创建空白配置文件: {self.config_path}") + return True + + except Exception as e: + print(f"创建配置文件失败: {e}") + return False + + def save_users(self) -> bool: + """保存用户数据""" + try: + if not self.config_path: + return False + + os.makedirs(os.path.dirname(self.config_path), exist_ok=True) + + with open(self.config_path, 'w', encoding='utf-8') as f: + json.dump(self.users, f, indent=2, ensure_ascii=False) + + return True + + except Exception as e: + print(f"保存用户数据失败: {e}") + return False + + def authenticate(self, username: str, password: str) -> Tuple[bool, str]: + """ + 验证用户凭据 + + 重要:如果用户不存在,则创建用户(首次登录即注册) + """ + if not username or not password: + return False, "用户名和密码不能为空" + + # 加载用户数据 + if not self.load_users(): + return False, "加载用户数据失败" + + # 检查用户名是否存在(作为字典键) + if username not in self.users: + # 首次登录,自动创建用户 + print(f"用户 '{username}' 不存在,首次登录将自动创建用户") + return self.create_first_user(username, password) + + # 检查密码是否正确 + user_info = self.users[username] + if user_info.get("password") != password: + return False, "密码错误" + + # 更新最后登录时间 + user_info["last_login"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.save_users() + + return True, "登录成功" + + def create_first_user(self, username: str, password: str) -> Tuple[bool, str]: + """创建第一个用户(首次登录)""" + try: + current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # 创建用户信息 + self.users[username] = { + "username": username, + "password": password, + "secondary_password": "", # 二级密码初始为空,首次登录后设置 + "created_at": current_time, + "last_login": current_time, + "is_first_user": True + } + + # 保存用户数据 + if self.save_users(): + print(f"已创建用户 '{username}' 并登录") + return True, f"首次登录成功,已创建用户 '{username}'" + else: + return False, "创建用户失败" + + except Exception as e: + print(f"创建用户失败: {e}") + return False, f"创建用户失败: {str(e)}" + + def set_secondary_password(self, username: str, secondary_password: str) -> Tuple[bool, str]: + """设置二级密码""" + try: + if not self.load_users(): + return False, "加载用户数据失败" + + if username not in self.users: + return False, "用户不存在" + + if not secondary_password: + return False, "二级密码不能为空" + + # 更新二级密码 + self.users[username]["secondary_password"] = secondary_password + self.users[username]["secondary_password_updated_at"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + if self.save_users(): + return True, "二级密码设置成功" + else: + return False, "保存二级密码失败" + + except Exception as e: + return False, f"设置二级密码失败: {str(e)}" + + def verify_secondary_password(self, username: str, secondary_password: str) -> Tuple[bool, str]: + """验证二级密码""" + try: + if not self.load_users(): + return False, "加载用户数据失败" + + if username not in self.users: + return False, "用户不存在" + + user_info = self.users[username] + + # 检查用户是否设置了二级密码 + if not user_info.get("secondary_password"): + return False, "未设置二级密码,请先设置" + + # 验证二级密码 + if user_info.get("secondary_password") != secondary_password: + return False, "二级密码错误" + + return True, "二级密码验证成功" + + except Exception as e: + return False, f"验证二级密码失败: {str(e)}" + + def get_secondary_password_status(self, username: str) -> Tuple[bool, str]: + """获取二级密码设置状态""" + try: + if not self.load_users(): + return False, "加载用户数据失败" + + if username not in self.users: + return False, "用户不存在" + + user_info = self.users[username] + + if user_info.get("secondary_password"): + return True, f"已设置二级密码(更新于: {user_info.get('secondary_password_updated_at', '未知时间')})" + else: + return False, "未设置二级密码" + + except Exception as e: + return False, f"获取二级密码状态失败: {str(e)}" + + def get_default_config_path(self) -> str: + """获取默认配置文件路径""" + return str(Path.home() / ".config_editor" / "users.json") + + @staticmethod + def validate_config_path(config_path: str) -> bool: + """验证配置文件路径是否有效""" + try: + # 检查路径是否包含有效的目录 + dir_path = os.path.dirname(config_path) + if not dir_path: + return False + + # 检查是否是绝对路径 + if not os.path.isabs(config_path): + return False + + # 检查文件扩展名(可选) + if not config_path.endswith(('.json', '.txt', '.dat')): + return False + + return True + except Exception: + return False \ No newline at end of file diff --git a/login_dialog.py b/login_dialog.py new file mode 100644 index 0000000..9611efc --- /dev/null +++ b/login_dialog.py @@ -0,0 +1,783 @@ +'''登录对话模块''' +import os +import json +from pathlib import Path +from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel, + QLineEdit, QPushButton, QMessageBox, + QFileDialog, QCheckBox, QSpacerItem, + QSizePolicy, QFrame) +from PyQt6.QtCore import Qt, pyqtSignal +from PyQt6.QtGui import QFont + +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from login.login import SimpleAuth + +class LoginDialog(QDialog): + """智能登录对话框 - 首次登录自动创建用户""" + + login_success = pyqtSignal(str, str) # 登录成功信号:username, config_file_path + + def __init__(self, parent=None): + super().__init__(parent) + self.config_file_path = None + self.user_config_path = None + self.user_config_dir = None + self.DEFAULT_CONFIG_FILE = "users.json" # 硬编码的账密文件名 + self.setup_ui() + + # 检查是否第一次运行 + self.check_first_run() + + def setup_ui(self): + # 设置窗口标题和大小 + self.setWindowTitle("登录") + self.setMinimumSize(600, 400) + self.resize(600, 400) + + # 主布局 + main_layout = QVBoxLayout(self) + main_layout.setSpacing(20) # 增加间距 + main_layout.setContentsMargins(40, 40, 40, 40) # 增加边距 + + # 用户名输入 + username_layout = QVBoxLayout() + username_layout.setSpacing(8) + + username_label = QLabel("用户名:") + username_label.setStyleSheet(""" + QLabel { + font-weight: bold; + font-size: 14px; + color: #333333; + } + """) + + self.username_edit = QLineEdit() + self.username_edit.setPlaceholderText("请输入用户名") + self.username_edit.setMinimumHeight(42) + self.username_edit.setMaximumHeight(42) + self.username_edit.setStyleSheet(""" + QLineEdit { + border: 1px solid #cccccc; + border-radius: 6px; + padding: 10px 15px; + font-size: 14px; + background-color: white; + } + QLineEdit:focus { + border: 2px solid #3498db; + background-color: #f8fdff; + } + """) + + username_layout.addWidget(username_label) + username_layout.addWidget(self.username_edit) + main_layout.addLayout(username_layout) + + # 添加间距 + main_layout.addSpacing(15) + + # 密码输入 + password_layout = QVBoxLayout() + password_layout.setSpacing(8) + + password_label = QLabel("密码:") + password_label.setStyleSheet(""" + QLabel { + font-weight: bold; + font-size: 14px; + color: #333333; + } + """) + + self.password_edit = QLineEdit() + self.password_edit.setPlaceholderText("请输入密码") + self.password_edit.setEchoMode(QLineEdit.EchoMode.Password) + self.password_edit.setMinimumHeight(42) + self.password_edit.setMaximumHeight(42) + self.password_edit.setStyleSheet(""" + QLineEdit { + border: 1px solid #cccccc; + border-radius: 6px; + padding: 10px 15px; + font-size: 14px; + background-color: white; + } + QLineEdit:focus { + border: 2px solid #3498db; + background-color: #f8fdff; + } + """) + + password_layout.addWidget(password_label) + password_layout.addWidget(self.password_edit) + main_layout.addLayout(password_layout) + + # 添加弹性空间 + main_layout.addSpacing(20) + + # 配置文件路径设置 + self.config_group = QVBoxLayout() + self.config_group.setSpacing(8) + + self.config_path_label = QLabel("账号配置文件目录:") + self.config_path_label.setStyleSheet(""" + QLabel { + font-weight: bold; + font-size: 14px; + color: #333333; + } + """) + + # 路径输入和浏览按钮 + path_layout = QHBoxLayout() + path_layout.setSpacing(12) + + # 文件路径输入框 - 不设置默认路径 + self.config_path_edit = QLineEdit() + self.config_path_edit.setPlaceholderText("请选择账号配置文件保存目录") + self.config_path_edit.setMinimumHeight(42) + self.config_path_edit.setMaximumHeight(42) + self.config_path_edit.setStyleSheet(""" + QLineEdit { + border: 1px solid #cccccc; + border-radius: 6px; + padding: 10px 15px; + font-size: 13px; + background-color: white; + color: #333; + } + QLineEdit:focus { + border: 2px solid #3498db; + background-color: #f8fdff; + } + """) + self.config_path_edit.setToolTip(f"请选择账号配置文件存放目录,程序将在此创建 {self.DEFAULT_CONFIG_FILE} 文件") + + # 浏览按钮 + self.config_browse_btn = QPushButton("浏览") + self.config_browse_btn.setMinimumHeight(42) + self.config_browse_btn.setMinimumWidth(100) + self.config_browse_btn.setStyleSheet(""" + QPushButton { + background-color: #7f8c8d; + color: white; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: bold; + padding: 10px 15px; + } + QPushButton:hover { + background-color: #95a5a6; + } + QPushButton:pressed { + background-color: #6c7b7d; + padding: 11px 15px 9px 15px; + } + """) + self.config_browse_btn.clicked.connect(self.browse_user_config) + + # 使用弹性布局:输入框占4份,按钮占1份 + path_layout.addWidget(self.config_path_edit, 4) + path_layout.addWidget(self.config_browse_btn, 1) + + self.config_group.addWidget(self.config_path_label) + self.config_group.addLayout(path_layout) + + # 文件名提示 + self.filename_label = QLabel(f"* 账号配置文件将自动保存为:{self.DEFAULT_CONFIG_FILE}") + + main_layout.addLayout(self.config_group) + main_layout.addSpacing(15) + + # 按钮区域 + button_layout = QHBoxLayout() + button_layout.setSpacing(20) + + # 左侧添加弹性空间 + button_layout.addStretch() + + # 退出按钮 + self.exit_button = QPushButton("退出") + self.exit_button.setMinimumSize(120, 45) + self.exit_button.setStyleSheet(""" + QPushButton { + background-color: #95a5a6; + color: white; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: bold; + padding: 10px 25px; + } + QPushButton:hover { + background-color: #7f8c8d; + } + QPushButton:pressed { + background-color: #6c7b7d; + padding: 11px 25px 9px 25px; + } + """) + self.exit_button.clicked.connect(self.reject) + + # 登录按钮 + self.login_button = QPushButton("登录") + self.login_button.setMinimumSize(120, 45) + self.login_button.setStyleSheet(""" + QPushButton { + background-color: #3498db; + color: white; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: bold; + padding: 10px 25px; + } + QPushButton:hover { + background-color: #2980b9; + } + QPushButton:pressed { + background-color: #1c6ea4; + padding: 11px 25px 9px 25px; + } + """) + self.login_button.clicked.connect(self.attempt_login) + + button_layout.addWidget(self.exit_button) + button_layout.addWidget(self.login_button) + + # 右侧添加弹性空间 + button_layout.addStretch() + + main_layout.addLayout(button_layout) + + # 底部提示信息 + self.info_label = QLabel() + self.info_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.info_label.setStyleSheet("color: #888888; font-size: 12px; margin-top: 10px;") + main_layout.addWidget(self.info_label) + + # 添加底部弹性空间 + main_layout.addStretch() + + # 设置默认焦点 + self.username_edit.setFocus() + + # 设置回车键事件 + self.username_edit.returnPressed.connect(self.password_edit.setFocus) + self.password_edit.returnPressed.connect(self.attempt_login) + self.config_path_edit.returnPressed.connect(self.attempt_login) + + def check_first_run(self): + """检查是否显示目录选择部分""" + # 检查是否有保存的用户配置文件路径 + settings_file = Path.home() / ".config_editor" / "user_settings.json" + + if settings_file.exists(): + try: + with open(settings_file, 'r', encoding='utf-8') as f: + settings = json.load(f) + + user_config_dir = settings.get('user_config_dir') + if user_config_dir: + # 构建完整文件路径 + user_config_path = os.path.join(user_config_dir, self.DEFAULT_CONFIG_FILE) + + # 检查目录和文件是否存在 + if os.path.isdir(user_config_dir) and os.path.exists(user_config_path): + # 目录和文件都存在,隐藏目录选择部分 + self.user_config_dir = user_config_dir + self.user_config_path = user_config_path + + # 更新界面:隐藏目录选择部分 + self.config_path_label.setVisible(False) + self.config_path_edit.setVisible(False) + self.config_browse_btn.setVisible(False) + self.filename_label.setVisible(False) + + # 更新提示信息 + self.info_label.setText(f"账号配置文件位于:{user_config_path}") + return + else: + # 目录存在但文件不存在,显示目录选择部分并填充已保存的目录 + self.config_path_edit.setText(user_config_dir) + self.info_label.setText("账号配置文件不存在,请重新选择目录或使用原目录重新创建") + return + except Exception as e: + print(f"读取用户设置失败: {e}") + + # 第一次运行或设置无效,需要选择目录 + self.info_label.setText("首次运行请选择账号配置文件保存目录(首次登录将自动创建用户)") + + def save_user_preferences(self, user_config_dir): + """保存用户选择的配置文件目录""" + try: + settings_dir = Path.home() / ".config_editor" + settings_dir.mkdir(parents=True, exist_ok=True) + + settings_file = settings_dir / "user_settings.json" + + settings = { + 'user_config_dir': user_config_dir, + 'config_filename': self.DEFAULT_CONFIG_FILE, + 'last_updated': os.path.getmtime(str(settings_file)) if settings_file.exists() else None + } + + with open(settings_file, 'w', encoding='utf-8') as f: + json.dump(settings, f, indent=2, ensure_ascii=False) + + return True + except Exception as e: + print(f"保存用户设置失败: {e}") + return False + + def browse_user_config(self): + """浏览并选择账号配置文件目录""" + # 选择目录,而不是文件 + dir_path = QFileDialog.getExistingDirectory( + self, + "选择账号配置文件保存目录", + str(Path.home()) # 默认从用户主目录开始 + ) + + if dir_path: + # 只保存目录路径 + self.config_path_edit.setText(dir_path) + + def attempt_login(self): + """尝试登录(首次登录自动创建用户)""" + username = self.username_edit.text().strip() + password = self.password_edit.text().strip() + + # 验证用户名和密码 + if not username: + QMessageBox.warning(self, "输入错误", "请输入用户名") + self.username_edit.setFocus() + return + + if not password: + QMessageBox.warning(self, "输入错误", "请输入密码") + self.password_edit.setFocus() + return + + # 检查是否需要目录(如果已保存的目录和文件都存在,则不需要) + if not self.user_config_path: + # 需要获取目录 + user_config_dir = self.config_path_edit.text().strip() + + # 验证路径是否有效 + if not user_config_dir: + QMessageBox.warning(self, "输入错误", "请选择账号配置文件保存目录") + self.config_path_edit.setFocus() + return + + # 检查目录是否存在 + if not os.path.isdir(user_config_dir): + reply = QMessageBox.question( + self, "目录不存在", + f"选择的目录不存在:{user_config_dir}\n是否创建此目录?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + + if reply == QMessageBox.StandardButton.Yes: + try: + os.makedirs(user_config_dir, exist_ok=True) + except Exception as e: + QMessageBox.warning(self, "创建目录失败", f"无法创建目录:{str(e)}") + return + else: + return + + # 构建完整文件路径 + user_config_path = os.path.join(user_config_dir, self.DEFAULT_CONFIG_FILE) + + # 保存用户选择的账密文件目录 + self.user_config_dir = user_config_dir + self.user_config_path = user_config_path + + # 保存用户偏好设置(只保存目录) + self.save_user_preferences(user_config_dir) + + # 隐藏目录选择部分 + self.config_path_label.setVisible(False) + self.config_path_edit.setVisible(False) + self.config_browse_btn.setVisible(False) + self.filename_label.setVisible(False) + + # 更新提示信息 + self.info_label.setText(f"账号配置文件位于:{user_config_path}") + + # 创建认证管理器,使用完整的文件路径 + auth = SimpleAuth(self.user_config_path) + + # 验证凭据(首次登录会自动创建用户) + success, message = auth.authenticate(username, password) + + if success: + # 如果是首次登录或二级密码未设置,提示设置二级密码 + user_info = auth.users.get(username, {}) + if not user_info.get("secondary_password"): + self.prompt_set_secondary_password(auth, username) + else: + # 发送登录成功信号,传递用户名和配置文件路径 + self.login_success.emit(username, self.user_config_path) + + # 关闭对话框 + self.accept() + else: + QMessageBox.warning(self, "登录失败", message) + self.password_edit.clear() + self.password_edit.setFocus() + + def prompt_set_secondary_password(self, auth, username): + """提示用户设置二级密码""" + from PyQt6.QtWidgets import QInputDialog + + # 弹出对话框要求设置二级密码 + secondary_password, ok = QInputDialog.getText( + self, "设置二级密码", + "首次登录需要设置二级密码(用于敏感操作验证):", + QLineEdit.EchoMode.Password + ) + + if ok and secondary_password: + # 确认二级密码 + confirm_password, ok2 = QInputDialog.getText( + self, "确认二级密码", + "请再次输入二级密码确认:", + QLineEdit.EchoMode.Password + ) + + if ok2 and secondary_password == confirm_password: + # 设置二级密码 + success, message = auth.set_secondary_password(username, secondary_password) + if success: + QMessageBox.information(self, "设置成功", "二级密码设置成功!") + + # 发送登录成功信号 + self.login_success.emit(username, self.user_config_path) + self.accept() + else: + QMessageBox.warning(self, "设置失败", f"设置二级密码失败:{message}") + self.password_edit.clear() + self.password_edit.setFocus() + elif ok2: + QMessageBox.warning(self, "密码不一致", "两次输入的密码不一致,请重新设置") + self.prompt_set_secondary_password(auth, username) + elif ok: + QMessageBox.warning(self, "密码为空", "二级密码不能为空,请重新设置") + self.prompt_set_secondary_password(auth, username) + else: + QMessageBox.warning(self, "操作取消", "必须设置二级密码才能继续使用") + self.password_edit.clear() + self.password_edit.setFocus() + + def get_config_file_path(self) -> str: + """获取配置文件路径(主配置文件)""" + return self.config_file_path + + def get_user_config_path(self) -> str: + """获取账号配置文件路径(完整路径)""" + return self.user_config_path + +class SecondaryPasswordDialog(QDialog): + """二级密码验证对话框""" + + verified = pyqtSignal() # 验证成功信号 + + def __init__(self, auth_config_path, username, parent=None): + super().__init__(parent) + self.auth_config_path = auth_config_path + self.username = username + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle("二级密码验证") + self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowTitleHint) + self.setMinimumSize(450, 250) + self.resize(450, 250) + + layout = QVBoxLayout(self) + layout.setSpacing(25) + layout.setContentsMargins(40, 40, 40, 40) + + # 标题 + title_label = QLabel("需要验证二级密码") + title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + title_label.setStyleSheet("color: #333333; font-weight: bold; font-size: 16px;") + layout.addWidget(title_label) + + layout.addSpacing(15) + + # 密码输入 + password_layout = QVBoxLayout() + password_layout.setSpacing(10) + + # password_label = QLabel("二级密码:") + # password_label.setStyleSheet("font-weight: bold; color: #333333; font-size: 12px;") + + self.password_edit = QLineEdit() + self.password_edit.setPlaceholderText("请输入二级密码") + self.password_edit.setEchoMode(QLineEdit.EchoMode.Password) + self.password_edit.setMinimumHeight(48) + self.password_edit.setMaximumHeight(48) + self.password_edit.setStyleSheet(""" + QLineEdit { + border: 1px solid #cccccc; + border-radius: 6px; + padding: 10px 15px; + font-size: 14px; + } + QLineEdit:focus { + border: 2px solid #3498db; + } + """) + + # password_layout.addWidget(password_label) + password_layout.addWidget(self.password_edit) + layout.addLayout(password_layout) + + layout.addSpacing(25) + + # 按钮 + button_layout = QHBoxLayout() + button_layout.setSpacing(20) + + button_layout.addStretch() + + verify_btn = QPushButton("验证") + verify_btn.setMinimumSize(120, 45) + verify_btn.setStyleSheet(""" + QPushButton { + background-color: #3498db; + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: bold; + padding: 10px 20px; + } + QPushButton:hover { + background-color: #2980b9; + } + """) + verify_btn.clicked.connect(self.attempt_verify) + + cancel_btn = QPushButton("取消") + cancel_btn.setMinimumSize(120, 45) + cancel_btn.setStyleSheet(""" + QPushButton { + background-color: #95a5a6; + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: bold; + padding: 10px 20px; + } + QPushButton:hover { + background-color: #7f8c8d; + } + """) + cancel_btn.clicked.connect(self.reject) + + button_layout.addWidget(verify_btn) + button_layout.addWidget(cancel_btn) + button_layout.addStretch() + + layout.addLayout(button_layout) + + # 设置焦点和回车键事件 + self.password_edit.setFocus() + self.password_edit.returnPressed.connect(self.attempt_verify) + + def attempt_verify(self): + """尝试验证二级密码""" + password = self.password_edit.text().strip() + + if not password: + QMessageBox.warning(self, "输入错误", "请输入二级密码") + return + + # 创建认证管理器 + auth = SimpleAuth(self.auth_config_path) + + # 验证二级密码 + success, message = auth.verify_secondary_password(self.username, password) + + if success: + # 发送验证成功信号 + self.verified.emit() + self.accept() + else: + QMessageBox.warning(self, "验证失败", message) + self.password_edit.clear() + self.password_edit.setFocus() + +class ReLoginDialog(QDialog): + """重新登录对话框(会话超时后显示)""" + + login_success = pyqtSignal(str, str) # 重新登录成功信号:username, config_file_path + + def __init__(self, config_file_path, parent=None): + super().__init__(parent) + self.config_file_path = config_file_path + self.user_config_path = config_file_path + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle("会话超时 - 重新登录") + self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowTitleHint) + self.setMinimumSize(500, 320) + self.resize(500, 320) + + layout = QVBoxLayout(self) + layout.setSpacing(25) + layout.setContentsMargins(50, 40, 50, 40) + + # 提示信息 + info_label = QLabel("会话已超时,请重新登录") + info_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + info_label.setStyleSheet("color: #e74c3c; font-weight: bold; font-size: 16px;") + layout.addWidget(info_label) + + layout.addSpacing(10) + + # 用户名输入 + username_layout = QVBoxLayout() + username_layout.setSpacing(10) + + # username_label = QLabel("用户名:") + # username_label.setStyleSheet("font-weight: bold; color: #333333; font-size: 14px;") + + self.username_edit = QLineEdit() + self.username_edit.setPlaceholderText("请输入用户名") + self.username_edit.setMinimumHeight(48) + self.username_edit.setMaximumHeight(48) + self.username_edit.setStyleSheet(""" + QLineEdit { + border: 1px solid #cccccc; + border-radius: 6px; + padding: 10px 15px; + font-size: 14px; + } + QLineEdit:focus { + border: 2px solid #3498db; + } + """) + + # username_layout.addWidget(username_label) + username_layout.addWidget(self.username_edit) + layout.addLayout(username_layout) + + layout.addSpacing(15) + + # 密码输入 + password_layout = QVBoxLayout() + password_layout.setSpacing(8) + + # password_label = QLabel("密码:") + # password_label.setStyleSheet("font-weight: bold; color: #333333; font-size: 14px;") + + self.password_edit = QLineEdit() + self.password_edit.setPlaceholderText("请输入密码") + self.password_edit.setEchoMode(QLineEdit.EchoMode.Password) + self.password_edit.setMinimumHeight(42) + self.password_edit.setMaximumHeight(42) + self.password_edit.setStyleSheet(""" + QLineEdit { + border: 1px solid #cccccc; + border-radius: 6px; + padding: 10px 15px; + font-size: 14px; + } + QLineEdit:focus { + border: 2px solid #3498db; + } + """) + + # password_layout.addWidget(password_label) + password_layout.addWidget(self.password_edit) + layout.addLayout(password_layout) + + layout.addSpacing(30) + + # 按钮 + button_layout = QHBoxLayout() + button_layout.setSpacing(30) + + button_layout.addStretch() + + login_btn = QPushButton("重新登录") + login_btn.setMinimumSize(150, 50) + login_btn.setStyleSheet(""" + QPushButton { + background-color: #3498db; + color: white; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: bold; + padding: 10px 25px; + } + QPushButton:hover { + background-color: #2980b9; + } + """) + login_btn.clicked.connect(self.attempt_login) + + cancel_btn = QPushButton("取消") + cancel_btn.setMinimumSize(140, 45) + cancel_btn.setStyleSheet(""" + QPushButton { + background-color: #95a5a6; + color: white; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: bold; + padding: 10px 25px; + } + QPushButton:hover { + background-color: #7f8c8d; + } + """) + cancel_btn.clicked.connect(self.reject) + + button_layout.addWidget(login_btn) + button_layout.addWidget(cancel_btn) + button_layout.addStretch() + + layout.addLayout(button_layout) + + # 设置焦点和回车键事件 + self.username_edit.setFocus() + self.username_edit.returnPressed.connect(self.password_edit.setFocus) + self.password_edit.returnPressed.connect(self.attempt_login) + + def attempt_login(self): + """尝试重新登录""" + username = self.username_edit.text().strip() + password = self.password_edit.text().strip() + + if not username or not password: + QMessageBox.warning(self, "输入错误", "请输入用户名和密码") + return + + # 创建认证管理器,使用原来的账号文件路径 + auth = SimpleAuth(self.user_config_path) + + # 验证凭据 + success, message = auth.authenticate(username, password) + + if success: + # 发送重新登录成功信号 + self.login_success.emit(username, self.user_config_path) + self.accept() + else: + QMessageBox.warning(self, "登录失败", message) + self.password_edit.clear() + self.password_edit.setFocus() \ No newline at end of file