上传文件至 /
This commit is contained in:
parent
ab5321a93c
commit
2c9087ccf9
240
login.py
Normal file
240
login.py
Normal file
@ -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
|
||||||
783
login_dialog.py
Normal file
783
login_dialog.py
Normal file
@ -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()
|
||||||
Loading…
x
Reference in New Issue
Block a user