|
|
|
|
@ -1,13 +1,12 @@
|
|
|
|
|
from PyQt6.QtWidgets import (
|
|
|
|
|
QMainWindow, QWidget, QStackedWidget, QVBoxLayout, QHBoxLayout,
|
|
|
|
|
QLabel, QLineEdit, QPushButton, QMessageBox, QCheckBox
|
|
|
|
|
QLabel, QLineEdit, QPushButton, QMessageBox, QRadioButton, QButtonGroup, QSpinBox
|
|
|
|
|
)
|
|
|
|
|
from PyQt6.QtCore import Qt
|
|
|
|
|
|
|
|
|
|
from src.services.storage_service import StorageService
|
|
|
|
|
from src.services.email_service import EmailService
|
|
|
|
|
from src.services.user_service import UserService
|
|
|
|
|
from src.services.question_service import QuestionService
|
|
|
|
|
from services.storage_service import StorageService
|
|
|
|
|
from services.user_service import UserService
|
|
|
|
|
from services.question_service import QuestionService
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
|
|
|
@ -21,23 +20,29 @@ class MainWindow(QMainWindow):
|
|
|
|
|
|
|
|
|
|
# 业务服务初始化
|
|
|
|
|
self.storage_service = StorageService()
|
|
|
|
|
self.email_service = EmailService(self.storage_service)
|
|
|
|
|
self.user_service = UserService(self.storage_service, self.email_service)
|
|
|
|
|
self.session_email = None
|
|
|
|
|
self.user_service = UserService(self.storage_service)
|
|
|
|
|
self.question_service = QuestionService()
|
|
|
|
|
|
|
|
|
|
# 页面容器
|
|
|
|
|
self.stack = QStackedWidget()
|
|
|
|
|
self.setCentralWidget(self.stack)
|
|
|
|
|
|
|
|
|
|
# 先构建最基础的两个页面:登录与SMTP设置
|
|
|
|
|
self._build_login_page()
|
|
|
|
|
self._build_smtp_page()
|
|
|
|
|
# 构建页面:登录、注册(邮箱/验证码/用户名密码)、选择、答题、结果、修改密码
|
|
|
|
|
self._build_login_page() # index 0
|
|
|
|
|
self._build_register_email_page() # index 1
|
|
|
|
|
self._build_verify_page() # index 2
|
|
|
|
|
self._build_set_credentials_page() # index 3
|
|
|
|
|
self._build_choice_page() # index 4
|
|
|
|
|
self._build_quiz_page() # index 5
|
|
|
|
|
self._build_result_page() # index 6
|
|
|
|
|
self._build_change_password_page() # index 7
|
|
|
|
|
|
|
|
|
|
# 默认显示登录页
|
|
|
|
|
self.stack.setCurrentIndex(0)
|
|
|
|
|
|
|
|
|
|
def _build_login_page(self) -> None:
|
|
|
|
|
"""构建登录页面:邮箱+密码+登录按钮。"""
|
|
|
|
|
"""构建登录页面:支持用户名或邮箱登录。"""
|
|
|
|
|
page = QWidget()
|
|
|
|
|
layout = QVBoxLayout(page)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
|
@ -47,9 +52,9 @@ class MainWindow(QMainWindow):
|
|
|
|
|
title.setStyleSheet("font-size: 22px; font-weight: bold; margin: 16px 0;")
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
self.login_email = QLineEdit()
|
|
|
|
|
self.login_email.setPlaceholderText("请输入邮箱")
|
|
|
|
|
layout.addWidget(self.login_email)
|
|
|
|
|
self.login_identifier = QLineEdit()
|
|
|
|
|
self.login_identifier.setPlaceholderText("请输入邮箱或用户名")
|
|
|
|
|
layout.addWidget(self.login_identifier)
|
|
|
|
|
|
|
|
|
|
self.login_password = QLineEdit()
|
|
|
|
|
self.login_password.setPlaceholderText("请输入密码")
|
|
|
|
|
@ -58,84 +63,523 @@ class MainWindow(QMainWindow):
|
|
|
|
|
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
self.btn_login = QPushButton("登录")
|
|
|
|
|
self.btn_to_smtp = QPushButton("SMTP设置")
|
|
|
|
|
self.btn_to_register = QPushButton("去注册")
|
|
|
|
|
btn_row.addWidget(self.btn_login)
|
|
|
|
|
btn_row.addWidget(self.btn_to_smtp)
|
|
|
|
|
btn_row.addWidget(self.btn_to_register)
|
|
|
|
|
layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
self.btn_login.clicked.connect(self._on_login)
|
|
|
|
|
self.btn_to_smtp.clicked.connect(lambda: self.stack.setCurrentIndex(1))
|
|
|
|
|
self.btn_to_register.clicked.connect(lambda: self.stack.setCurrentIndex(1))
|
|
|
|
|
|
|
|
|
|
self.stack.addWidget(page)
|
|
|
|
|
|
|
|
|
|
def _on_login(self) -> None:
|
|
|
|
|
"""处理登录逻辑:校验输入并调用用户服务。"""
|
|
|
|
|
email = (self.login_email.text() or "").strip()
|
|
|
|
|
"""处理登录逻辑:根据输入自动识别邮箱或用户名进行登录,并在成功后进入选择页。"""
|
|
|
|
|
identifier = (self.login_identifier.text() or "").strip()
|
|
|
|
|
password = self.login_password.text() or ""
|
|
|
|
|
if not email or not password:
|
|
|
|
|
QMessageBox.warning(self, "提示", "邮箱与密码均不能为空")
|
|
|
|
|
if not identifier or not password:
|
|
|
|
|
QMessageBox.warning(self, "提示", "账号与密码均不能为空")
|
|
|
|
|
return
|
|
|
|
|
ok, msg = self.user_service.login(email, password)
|
|
|
|
|
# 判断是否为邮箱
|
|
|
|
|
if "@" in identifier:
|
|
|
|
|
ok, msg = self.user_service.login(identifier, password)
|
|
|
|
|
if ok:
|
|
|
|
|
self.session_email = identifier
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "提示", msg or "登录失败")
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
ok, msg = self.user_service.login_by_username(identifier, password)
|
|
|
|
|
if ok:
|
|
|
|
|
# 通过用户名获取邮箱,记录会话
|
|
|
|
|
user = self.storage_service.get_user_by_username(identifier)
|
|
|
|
|
self.session_email = user.get('email') if user else None
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "提示", msg or "登录失败")
|
|
|
|
|
return
|
|
|
|
|
QMessageBox.information(self, "提示", "登录成功")
|
|
|
|
|
# 跳转到选择页
|
|
|
|
|
self.stack.setCurrentIndex(4)
|
|
|
|
|
|
|
|
|
|
def _build_register_email_page(self) -> None:
|
|
|
|
|
"""构建注册第一步:邮箱输入页面。"""
|
|
|
|
|
page = QWidget()
|
|
|
|
|
layout = QVBoxLayout(page)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
|
|
|
|
|
|
title = QLabel("注册 - 邮箱验证")
|
|
|
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
title.setStyleSheet("font-size: 22px; font-weight: bold; margin: 16px 0;")
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
self.register_email = QLineEdit()
|
|
|
|
|
self.register_email.setPlaceholderText("请输入邮箱地址")
|
|
|
|
|
layout.addWidget(self.register_email)
|
|
|
|
|
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
self.btn_send_code = QPushButton("获取验证码")
|
|
|
|
|
self.btn_back_to_login = QPushButton("返回登录")
|
|
|
|
|
btn_row.addWidget(self.btn_send_code)
|
|
|
|
|
btn_row.addWidget(self.btn_back_to_login)
|
|
|
|
|
layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
self.btn_send_code.clicked.connect(self._on_send_code)
|
|
|
|
|
self.btn_back_to_login.clicked.connect(lambda: self.stack.setCurrentIndex(0))
|
|
|
|
|
|
|
|
|
|
self.stack.addWidget(page)
|
|
|
|
|
|
|
|
|
|
def _on_send_code(self) -> None:
|
|
|
|
|
"""处理发送验证码逻辑:验证邮箱格式并生成验证码。"""
|
|
|
|
|
email = (self.register_email.text() or "").strip()
|
|
|
|
|
if not email:
|
|
|
|
|
QMessageBox.warning(self, "提示", "请输入邮箱地址")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
ok, msg = self.user_service.request_registration(email)
|
|
|
|
|
if ok:
|
|
|
|
|
QMessageBox.information(self, "提示", "登录成功")
|
|
|
|
|
self.session_email = email
|
|
|
|
|
QMessageBox.information(self, "提示", msg)
|
|
|
|
|
# 跳转到验证码页面
|
|
|
|
|
self.stack.setCurrentIndex(2)
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "提示", msg or "登录失败")
|
|
|
|
|
QMessageBox.warning(self, "提示", msg)
|
|
|
|
|
|
|
|
|
|
def _build_smtp_page(self) -> None:
|
|
|
|
|
"""构建 SMTP 设置页面:服务器、端口、账号、授权码、TLS/SSL、发件人名。"""
|
|
|
|
|
def _build_verify_page(self) -> None:
|
|
|
|
|
"""构建注册第二步:验证码验证页面。"""
|
|
|
|
|
page = QWidget()
|
|
|
|
|
layout = QVBoxLayout(page)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
|
|
|
|
|
|
title = QLabel("SMTP 设置")
|
|
|
|
|
title = QLabel("注册 - 验证码验证")
|
|
|
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
title.setStyleSheet("font-size: 22px; font-weight: bold; margin: 16px 0;")
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
self.smtp_server = QLineEdit(); self.smtp_server.setPlaceholderText("服务器,例如 smtp.qq.com")
|
|
|
|
|
self.smtp_port = QLineEdit(); self.smtp_port.setPlaceholderText("端口,通常 465 或 587")
|
|
|
|
|
self.smtp_user = QLineEdit(); self.smtp_user.setPlaceholderText("邮箱账号,例如 123456@qq.com")
|
|
|
|
|
self.smtp_pass = QLineEdit(); self.smtp_pass.setPlaceholderText("邮箱授权码/应用密码")
|
|
|
|
|
self.smtp_pass.setEchoMode(QLineEdit.EchoMode.Password)
|
|
|
|
|
self.smtp_tls = QCheckBox("启用 TLS")
|
|
|
|
|
self.smtp_ssl = QCheckBox("启用 SSL")
|
|
|
|
|
self.smtp_sender = QLineEdit(); self.smtp_sender.setPlaceholderText("发件人显示名称,可选")
|
|
|
|
|
|
|
|
|
|
layout.addWidget(self.smtp_server)
|
|
|
|
|
layout.addWidget(self.smtp_port)
|
|
|
|
|
layout.addWidget(self.smtp_user)
|
|
|
|
|
layout.addWidget(self.smtp_pass)
|
|
|
|
|
layout.addWidget(self.smtp_tls)
|
|
|
|
|
layout.addWidget(self.smtp_ssl)
|
|
|
|
|
layout.addWidget(self.smtp_sender)
|
|
|
|
|
self.verify_code = QLineEdit()
|
|
|
|
|
self.verify_code.setPlaceholderText("请输入6位验证码")
|
|
|
|
|
layout.addWidget(self.verify_code)
|
|
|
|
|
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
self.btn_save_smtp = QPushButton("保存设置")
|
|
|
|
|
self.btn_back_login = QPushButton("返回登录")
|
|
|
|
|
btn_row.addWidget(self.btn_save_smtp)
|
|
|
|
|
btn_row.addWidget(self.btn_back_login)
|
|
|
|
|
self.btn_verify = QPushButton("验证")
|
|
|
|
|
self.btn_back_to_email = QPushButton("返回上一步")
|
|
|
|
|
btn_row.addWidget(self.btn_verify)
|
|
|
|
|
btn_row.addWidget(self.btn_back_to_email)
|
|
|
|
|
layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
self.btn_save_smtp.clicked.connect(self._on_save_smtp)
|
|
|
|
|
self.btn_back_login.clicked.connect(lambda: self.stack.setCurrentIndex(0))
|
|
|
|
|
self.btn_verify.clicked.connect(self._on_verify_code)
|
|
|
|
|
self.btn_back_to_email.clicked.connect(lambda: self.stack.setCurrentIndex(1))
|
|
|
|
|
|
|
|
|
|
self.stack.addWidget(page)
|
|
|
|
|
|
|
|
|
|
def _on_save_smtp(self) -> None:
|
|
|
|
|
"""保存 SMTP 配置到本地 JSON,以支持验证码发送。"""
|
|
|
|
|
def _on_verify_code(self) -> None:
|
|
|
|
|
"""处理验证码验证逻辑。"""
|
|
|
|
|
code = (self.verify_code.text() or "").strip()
|
|
|
|
|
if not code:
|
|
|
|
|
QMessageBox.warning(self, "提示", "请输入验证码")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not self.session_email:
|
|
|
|
|
QMessageBox.warning(self, "提示", "会话已过期,请重新注册")
|
|
|
|
|
self.stack.setCurrentIndex(1)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
ok, msg = self.user_service.verify_code(self.session_email, code)
|
|
|
|
|
if ok:
|
|
|
|
|
QMessageBox.information(self, "提示", msg)
|
|
|
|
|
# 跳转到设置用户名密码页面
|
|
|
|
|
self.stack.setCurrentIndex(3)
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "提示", msg)
|
|
|
|
|
|
|
|
|
|
def _build_set_credentials_page(self) -> None:
|
|
|
|
|
"""构建注册第三步:设置用户名和密码页面。"""
|
|
|
|
|
page = QWidget()
|
|
|
|
|
layout = QVBoxLayout(page)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
|
|
|
|
|
|
title = QLabel("注册 - 设置用户名和密码")
|
|
|
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
title.setStyleSheet("font-size: 22px; font-weight: bold; margin: 16px 0;")
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
self.register_username = QLineEdit()
|
|
|
|
|
self.register_username.setPlaceholderText("请输入用户名(3-16位,字母数字下划线)")
|
|
|
|
|
layout.addWidget(self.register_username)
|
|
|
|
|
|
|
|
|
|
self.register_password = QLineEdit()
|
|
|
|
|
self.register_password.setPlaceholderText("请输入密码(6-10位,包含大小写字母和数字)")
|
|
|
|
|
self.register_password.setEchoMode(QLineEdit.EchoMode.Password)
|
|
|
|
|
layout.addWidget(self.register_password)
|
|
|
|
|
|
|
|
|
|
self.register_confirm = QLineEdit()
|
|
|
|
|
self.register_confirm.setPlaceholderText("请确认密码")
|
|
|
|
|
self.register_confirm.setEchoMode(QLineEdit.EchoMode.Password)
|
|
|
|
|
layout.addWidget(self.register_confirm)
|
|
|
|
|
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
self.btn_complete_register = QPushButton("完成注册")
|
|
|
|
|
self.btn_back_to_verify = QPushButton("返回上一步")
|
|
|
|
|
btn_row.addWidget(self.btn_complete_register)
|
|
|
|
|
btn_row.addWidget(self.btn_back_to_verify)
|
|
|
|
|
layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
self.btn_complete_register.clicked.connect(self._on_complete_register)
|
|
|
|
|
self.btn_back_to_verify.clicked.connect(lambda: self.stack.setCurrentIndex(2))
|
|
|
|
|
|
|
|
|
|
self.stack.addWidget(page)
|
|
|
|
|
|
|
|
|
|
def _on_complete_register(self) -> None:
|
|
|
|
|
"""处理完成注册逻辑:设置用户名和密码。"""
|
|
|
|
|
username = (self.register_username.text() or "").strip()
|
|
|
|
|
password = self.register_password.text() or ""
|
|
|
|
|
confirm = self.register_confirm.text() or ""
|
|
|
|
|
|
|
|
|
|
if not username or not password or not confirm:
|
|
|
|
|
QMessageBox.warning(self, "提示", "请填写完整信息")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not self.session_email:
|
|
|
|
|
QMessageBox.warning(self, "提示", "会话已过期,请重新注册")
|
|
|
|
|
self.stack.setCurrentIndex(1)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 设置用户名
|
|
|
|
|
ok, msg = self.user_service.set_username(self.session_email, username)
|
|
|
|
|
if not ok:
|
|
|
|
|
QMessageBox.warning(self, "提示", msg)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 设置密码
|
|
|
|
|
ok, msg = self.user_service.set_password(self.session_email, password, confirm)
|
|
|
|
|
if not ok:
|
|
|
|
|
QMessageBox.warning(self, "提示", msg)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 完成注册
|
|
|
|
|
ok, msg = self.user_service.complete_registration(self.session_email)
|
|
|
|
|
if ok:
|
|
|
|
|
QMessageBox.information(self, "提示", "注册成功!请登录")
|
|
|
|
|
# 清空表单并跳转到登录页
|
|
|
|
|
self.register_email.clear()
|
|
|
|
|
self.verify_code.clear()
|
|
|
|
|
self.register_username.clear()
|
|
|
|
|
self.register_password.clear()
|
|
|
|
|
self.register_confirm.clear()
|
|
|
|
|
self.session_email = None
|
|
|
|
|
self.stack.setCurrentIndex(0)
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "提示", msg)
|
|
|
|
|
|
|
|
|
|
def _build_choice_page(self) -> None:
|
|
|
|
|
"""构建选择页面:选择年级和题目数量。"""
|
|
|
|
|
page = QWidget()
|
|
|
|
|
layout = QVBoxLayout(page)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
|
|
|
|
|
|
title = QLabel("选择题目难度和数量")
|
|
|
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
title.setStyleSheet("font-size: 22px; font-weight: bold; margin: 16px 0;")
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
# 年级选择
|
|
|
|
|
grade_label = QLabel("选择年级:")
|
|
|
|
|
layout.addWidget(grade_label)
|
|
|
|
|
|
|
|
|
|
self.grade_group = QButtonGroup()
|
|
|
|
|
grade_layout = QHBoxLayout()
|
|
|
|
|
|
|
|
|
|
self.primary_radio = QRadioButton("小学")
|
|
|
|
|
self.middle_radio = QRadioButton("初中")
|
|
|
|
|
self.high_radio = QRadioButton("高中")
|
|
|
|
|
|
|
|
|
|
self.grade_group.addButton(self.primary_radio, 0)
|
|
|
|
|
self.grade_group.addButton(self.middle_radio, 1)
|
|
|
|
|
self.grade_group.addButton(self.high_radio, 2)
|
|
|
|
|
|
|
|
|
|
grade_layout.addWidget(self.primary_radio)
|
|
|
|
|
grade_layout.addWidget(self.middle_radio)
|
|
|
|
|
grade_layout.addWidget(self.high_radio)
|
|
|
|
|
layout.addLayout(grade_layout)
|
|
|
|
|
|
|
|
|
|
# 默认选择小学
|
|
|
|
|
self.primary_radio.setChecked(True)
|
|
|
|
|
|
|
|
|
|
# 题目数量选择
|
|
|
|
|
count_label = QLabel("选择题目数量:")
|
|
|
|
|
layout.addWidget(count_label)
|
|
|
|
|
|
|
|
|
|
self.question_count = QSpinBox()
|
|
|
|
|
self.question_count.setMinimum(10)
|
|
|
|
|
self.question_count.setMaximum(30)
|
|
|
|
|
self.question_count.setValue(15)
|
|
|
|
|
layout.addWidget(self.question_count)
|
|
|
|
|
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
self.btn_start_quiz = QPushButton("开始答题")
|
|
|
|
|
self.btn_change_password = QPushButton("修改密码")
|
|
|
|
|
self.btn_logout = QPushButton("退出登录")
|
|
|
|
|
btn_row.addWidget(self.btn_start_quiz)
|
|
|
|
|
btn_row.addWidget(self.btn_change_password)
|
|
|
|
|
btn_row.addWidget(self.btn_logout)
|
|
|
|
|
layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
self.btn_start_quiz.clicked.connect(self._on_start_quiz)
|
|
|
|
|
self.btn_change_password.clicked.connect(lambda: self.stack.setCurrentIndex(7))
|
|
|
|
|
self.btn_logout.clicked.connect(self._on_logout)
|
|
|
|
|
|
|
|
|
|
self.stack.addWidget(page)
|
|
|
|
|
|
|
|
|
|
def _on_start_quiz(self) -> None:
|
|
|
|
|
"""开始答题:生成题目并跳转到答题页面。"""
|
|
|
|
|
# 获取选择的年级
|
|
|
|
|
grade_map = {0: 'primary', 1: 'middle', 2: 'high'}
|
|
|
|
|
grade = grade_map[self.grade_group.checkedId()]
|
|
|
|
|
count = self.question_count.value()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
port_val = int(self.smtp_port.text().strip() or '587')
|
|
|
|
|
except ValueError:
|
|
|
|
|
QMessageBox.warning(self, '提示', '端口必须为数字')
|
|
|
|
|
self.questions = self.question_service.generate_questions(grade, count)
|
|
|
|
|
self.current_question = 0
|
|
|
|
|
self.user_answers = []
|
|
|
|
|
self._show_current_question()
|
|
|
|
|
self.stack.setCurrentIndex(5)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
QMessageBox.warning(self, "提示", f"生成题目失败:{str(e)}")
|
|
|
|
|
|
|
|
|
|
def _on_logout(self) -> None:
|
|
|
|
|
"""退出登录:清空会话并返回登录页。"""
|
|
|
|
|
self.session_email = None
|
|
|
|
|
self.login_identifier.clear()
|
|
|
|
|
self.login_password.clear()
|
|
|
|
|
QMessageBox.information(self, "提示", "已退出登录")
|
|
|
|
|
self.stack.setCurrentIndex(0)
|
|
|
|
|
|
|
|
|
|
def _build_quiz_page(self) -> None:
|
|
|
|
|
"""构建答题页面:显示题目和选项。"""
|
|
|
|
|
page = QWidget()
|
|
|
|
|
layout = QVBoxLayout(page)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
|
|
|
|
|
|
# 进度显示
|
|
|
|
|
self.progress_label = QLabel()
|
|
|
|
|
self.progress_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
self.progress_label.setStyleSheet("font-size: 16px; margin: 10px 0;")
|
|
|
|
|
layout.addWidget(self.progress_label)
|
|
|
|
|
|
|
|
|
|
# 题目显示
|
|
|
|
|
self.question_label = QLabel()
|
|
|
|
|
self.question_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
|
|
|
self.question_label.setStyleSheet("font-size: 18px; margin: 20px 0; padding: 10px; border: 1px solid #ccc;")
|
|
|
|
|
self.question_label.setWordWrap(True)
|
|
|
|
|
layout.addWidget(self.question_label)
|
|
|
|
|
|
|
|
|
|
# 选项
|
|
|
|
|
self.option_group = QButtonGroup()
|
|
|
|
|
self.option_radios = []
|
|
|
|
|
for i in range(4):
|
|
|
|
|
radio = QRadioButton()
|
|
|
|
|
radio.setStyleSheet("font-size: 16px; margin: 5px 0;")
|
|
|
|
|
self.option_group.addButton(radio, i)
|
|
|
|
|
self.option_radios.append(radio)
|
|
|
|
|
layout.addWidget(radio)
|
|
|
|
|
|
|
|
|
|
# 按钮
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
self.btn_prev = QPushButton("上一题")
|
|
|
|
|
self.btn_next = QPushButton("下一题")
|
|
|
|
|
self.btn_submit = QPushButton("提交答案")
|
|
|
|
|
btn_row.addWidget(self.btn_prev)
|
|
|
|
|
btn_row.addWidget(self.btn_next)
|
|
|
|
|
btn_row.addWidget(self.btn_submit)
|
|
|
|
|
layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
self.btn_prev.clicked.connect(self._on_prev_question)
|
|
|
|
|
self.btn_next.clicked.connect(self._on_next_question)
|
|
|
|
|
self.btn_submit.clicked.connect(self._on_submit_quiz)
|
|
|
|
|
|
|
|
|
|
self.stack.addWidget(page)
|
|
|
|
|
|
|
|
|
|
def _show_current_question(self) -> None:
|
|
|
|
|
"""显示当前题目。"""
|
|
|
|
|
if not hasattr(self, 'questions') or not self.questions:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
question = self.questions[self.current_question]
|
|
|
|
|
total = len(self.questions)
|
|
|
|
|
|
|
|
|
|
# 更新进度
|
|
|
|
|
self.progress_label.setText(f"第 {self.current_question + 1} 题 / 共 {total} 题")
|
|
|
|
|
|
|
|
|
|
# 更新题目
|
|
|
|
|
self.question_label.setText(question['stem'])
|
|
|
|
|
|
|
|
|
|
# 更新选项
|
|
|
|
|
for i, option in enumerate(question['options']):
|
|
|
|
|
self.option_radios[i].setText(f"{chr(65+i)}. {option}")
|
|
|
|
|
|
|
|
|
|
# 恢复之前的选择
|
|
|
|
|
if self.current_question < len(self.user_answers):
|
|
|
|
|
selected = self.user_answers[self.current_question]
|
|
|
|
|
if selected is not None:
|
|
|
|
|
self.option_radios[selected].setChecked(True)
|
|
|
|
|
else:
|
|
|
|
|
# 清空选择
|
|
|
|
|
for radio in self.option_radios:
|
|
|
|
|
radio.setChecked(False)
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
self.btn_prev.setEnabled(self.current_question > 0)
|
|
|
|
|
self.btn_next.setEnabled(self.current_question < total - 1)
|
|
|
|
|
|
|
|
|
|
def _on_prev_question(self) -> None:
|
|
|
|
|
"""上一题。"""
|
|
|
|
|
self._save_current_answer()
|
|
|
|
|
if self.current_question > 0:
|
|
|
|
|
self.current_question -= 1
|
|
|
|
|
self._show_current_question()
|
|
|
|
|
|
|
|
|
|
def _on_next_question(self) -> None:
|
|
|
|
|
"""下一题。"""
|
|
|
|
|
self._save_current_answer()
|
|
|
|
|
if self.current_question < len(self.questions) - 1:
|
|
|
|
|
self.current_question += 1
|
|
|
|
|
self._show_current_question()
|
|
|
|
|
|
|
|
|
|
def _save_current_answer(self) -> None:
|
|
|
|
|
"""保存当前题目的答案。"""
|
|
|
|
|
selected = self.option_group.checkedId()
|
|
|
|
|
# 确保user_answers列表足够长
|
|
|
|
|
while len(self.user_answers) <= self.current_question:
|
|
|
|
|
self.user_answers.append(None)
|
|
|
|
|
self.user_answers[self.current_question] = selected if selected >= 0 else None
|
|
|
|
|
|
|
|
|
|
def _on_submit_quiz(self) -> None:
|
|
|
|
|
"""提交答案并计算分数。"""
|
|
|
|
|
self._save_current_answer()
|
|
|
|
|
|
|
|
|
|
# 检查是否有未答题目
|
|
|
|
|
unanswered = []
|
|
|
|
|
for i, answer in enumerate(self.user_answers):
|
|
|
|
|
if answer is None:
|
|
|
|
|
unanswered.append(i + 1)
|
|
|
|
|
|
|
|
|
|
if unanswered:
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
self, "提示",
|
|
|
|
|
f"还有第 {', '.join(map(str, unanswered))} 题未作答,确定要提交吗?",
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
|
|
|
)
|
|
|
|
|
if reply == QMessageBox.StandardButton.No:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 计算分数
|
|
|
|
|
correct = 0
|
|
|
|
|
total = len(self.questions)
|
|
|
|
|
for i, question in enumerate(self.questions):
|
|
|
|
|
if i < len(self.user_answers) and self.user_answers[i] == question['answer_index']:
|
|
|
|
|
correct += 1
|
|
|
|
|
|
|
|
|
|
self.score = correct
|
|
|
|
|
self.total_questions = total
|
|
|
|
|
self._show_result()
|
|
|
|
|
self.stack.setCurrentIndex(6)
|
|
|
|
|
|
|
|
|
|
def _build_result_page(self) -> None:
|
|
|
|
|
"""构建结果页面:显示分数和操作选项。"""
|
|
|
|
|
page = QWidget()
|
|
|
|
|
layout = QVBoxLayout(page)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
|
|
|
|
|
title = QLabel("答题结果")
|
|
|
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
title.setStyleSheet("font-size: 22px; font-weight: bold; margin: 16px 0;")
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
self.score_label = QLabel()
|
|
|
|
|
self.score_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
self.score_label.setStyleSheet("font-size: 24px; margin: 20px 0; color: #2196F3;")
|
|
|
|
|
layout.addWidget(self.score_label)
|
|
|
|
|
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
self.btn_continue = QPushButton("继续做题")
|
|
|
|
|
self.btn_exit = QPushButton("退出")
|
|
|
|
|
btn_row.addWidget(self.btn_continue)
|
|
|
|
|
btn_row.addWidget(self.btn_exit)
|
|
|
|
|
layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
self.btn_continue.clicked.connect(lambda: self.stack.setCurrentIndex(4))
|
|
|
|
|
self.btn_exit.clicked.connect(self._on_logout)
|
|
|
|
|
|
|
|
|
|
self.stack.addWidget(page)
|
|
|
|
|
|
|
|
|
|
def _show_result(self) -> None:
|
|
|
|
|
"""显示答题结果。"""
|
|
|
|
|
if hasattr(self, 'score') and hasattr(self, 'total_questions'):
|
|
|
|
|
percentage = (self.score / self.total_questions) * 100
|
|
|
|
|
self.score_label.setText(
|
|
|
|
|
f"您答对了 {self.score} 题,共 {self.total_questions} 题\n"
|
|
|
|
|
f"正确率:{percentage:.1f}%"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _build_change_password_page(self) -> None:
|
|
|
|
|
"""构建修改密码页面。"""
|
|
|
|
|
page = QWidget()
|
|
|
|
|
layout = QVBoxLayout(page)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
|
|
|
|
|
|
title = QLabel("修改密码")
|
|
|
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
|
title.setStyleSheet("font-size: 22px; font-weight: bold; margin: 16px 0;")
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
self.old_password = QLineEdit()
|
|
|
|
|
self.old_password.setPlaceholderText("请输入原密码")
|
|
|
|
|
self.old_password.setEchoMode(QLineEdit.EchoMode.Password)
|
|
|
|
|
layout.addWidget(self.old_password)
|
|
|
|
|
|
|
|
|
|
self.new_password = QLineEdit()
|
|
|
|
|
self.new_password.setPlaceholderText("请输入新密码(6-10位,包含大小写字母和数字)")
|
|
|
|
|
self.new_password.setEchoMode(QLineEdit.EchoMode.Password)
|
|
|
|
|
layout.addWidget(self.new_password)
|
|
|
|
|
|
|
|
|
|
self.confirm_new_password = QLineEdit()
|
|
|
|
|
self.confirm_new_password.setPlaceholderText("请确认新密码")
|
|
|
|
|
self.confirm_new_password.setEchoMode(QLineEdit.EchoMode.Password)
|
|
|
|
|
layout.addWidget(self.confirm_new_password)
|
|
|
|
|
|
|
|
|
|
btn_row = QHBoxLayout()
|
|
|
|
|
self.btn_change_pwd = QPushButton("修改密码")
|
|
|
|
|
self.btn_back_to_choice = QPushButton("返回")
|
|
|
|
|
btn_row.addWidget(self.btn_change_pwd)
|
|
|
|
|
btn_row.addWidget(self.btn_back_to_choice)
|
|
|
|
|
layout.addLayout(btn_row)
|
|
|
|
|
|
|
|
|
|
self.btn_change_pwd.clicked.connect(self._on_change_password)
|
|
|
|
|
self.btn_back_to_choice.clicked.connect(lambda: self.stack.setCurrentIndex(4))
|
|
|
|
|
|
|
|
|
|
self.stack.addWidget(page)
|
|
|
|
|
|
|
|
|
|
def _on_change_password(self) -> None:
|
|
|
|
|
"""处理修改密码逻辑。"""
|
|
|
|
|
old_pwd = self.old_password.text() or ""
|
|
|
|
|
new_pwd = self.new_password.text() or ""
|
|
|
|
|
confirm_pwd = self.confirm_new_password.text() or ""
|
|
|
|
|
|
|
|
|
|
if not old_pwd or not new_pwd or not confirm_pwd:
|
|
|
|
|
QMessageBox.warning(self, "提示", "请填写完整信息")
|
|
|
|
|
return
|
|
|
|
|
config = {
|
|
|
|
|
'server': self.smtp_server.text().strip(),
|
|
|
|
|
'port': port_val,
|
|
|
|
|
'username': self.smtp_user.text().strip(),
|
|
|
|
|
'password': self.smtp_pass.text(),
|
|
|
|
|
'use_tls': self.smtp_tls.isChecked(),
|
|
|
|
|
'use_ssl': self.smtp_ssl.isChecked(),
|
|
|
|
|
'sender_name': self.smtp_sender.text().strip() or 'Math Study App',
|
|
|
|
|
}
|
|
|
|
|
self.email_service.update_smtp_config(config)
|
|
|
|
|
QMessageBox.information(self, '提示', 'SMTP设置已保存')
|
|
|
|
|
|
|
|
|
|
if not self.session_email:
|
|
|
|
|
QMessageBox.warning(self, "提示", "会话已过期,请重新登录")
|
|
|
|
|
self.stack.setCurrentIndex(0)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
ok, msg = self.user_service.change_password(self.session_email, old_pwd, new_pwd, confirm_pwd)
|
|
|
|
|
if ok:
|
|
|
|
|
QMessageBox.information(self, "提示", "密码修改成功")
|
|
|
|
|
# 清空表单并返回选择页
|
|
|
|
|
self.old_password.clear()
|
|
|
|
|
self.new_password.clear()
|
|
|
|
|
self.confirm_new_password.clear()
|
|
|
|
|
self.stack.setCurrentIndex(4)
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "提示", msg)
|