config_editor/login_dialog.py

783 lines
29 KiB
Python
Raw Normal View History

2026-01-19 08:02:47 +00:00
'''登录对话模块'''
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()