develop_main #3

Merged
hnu202326010322 merged 22 commits from develop into main 4 months ago

@ -1,2 +0,0 @@
# UI_math_system

@ -0,0 +1,153 @@
# 数学冒险岛 - 互动式数学学习软件
## 📘 项目简介
**“数学冒险岛”** 是一款面向小学、初中和高中学生的互动式数学学习软件。
通过 **游戏化界面设计** 与 **分学段题目设置**,让数学练习更具趣味性与挑战性。
软件支持用户邮件注册登录、学段选择、自定义题目数量、在线答题及结果反馈等功能,
适合各学段学生进行数学能力训练与提升。
---
## ✨ 功能特点
- **用户系统**:支持账号注册(含邮箱验证码)、登录、密码修改及退出功能
- **学段划分**:分为小学、初中、高中三个学段,自动匹配不同难度题目
- **个性化练习**可自定义题目数量1030 题),满足多样化练习需求
- **友好界面**:采用渐变卡通风格设计,搭配 Emoji 元素增强趣味性
- **即时反馈**:答题完成后即时展示得分结果,支持重新挑战
- **数据管理**:自动保存用户试卷记录,实现持久化存储
---
## 📁 项目结构
```bash
数学冒险岛/
├── core/ # 核心逻辑模块
│ ├── data_handler.py # 数据处理与文件存储
│ ├── email_service.py # 邮件验证码发送模块
│ ├── question_bank.py # 题目生成与难度匹配
│ ├── user_system.py # 用户注册、登录与管理
├── ui/ # 图形界面模块PyQt5 实现)
│ ├── login_ui.py # 登录界面
│ ├── main_window.py # 主窗口与页面切换逻辑
│ ├── question_ui.py # 题目答题界面
│ ├── register_ui.py # 注册界面
│ ├── result_ui.py # 答题结果展示界面
│ └── user_management_ui.py # 用户信息管理界面
├── generated_papers/ # 用户完成的试卷存储目录
├── main.py # 程序入口(运行此文件启动)
```
## 安装说明
### 环境要求
- Python 版本:`3.7+`
- 主要依赖:
- `PyQt5`(界面框架)
- `random`(随机题目生成)
- `smtplib`(邮箱验证码发送)
---
## 安装步骤
```bash
# 克隆仓库(若有)
git clone <仓库地址>
# 进入项目目录
cd math_adventure_island
# 安装依赖
pip install PyQt5
````
---
## 界面展示
以下表格展示了软件主要界面的功能与设计亮点:
| 页面名称 | 界面描述 | 主要功能 | 设计特点 |
|:--------:|:--------:|:--------:|:--------:|
| **主页面** | 登录 / 注册入口页面 | 用户登录、账号注册、退出系统 | 粉绿渐变背景、卡通风格、Emoji 装饰 |
| **学段选择页** | 选择学习阶段 | 小学 / 初中 / 高中学段选择、用户信息显示 | 分学段色彩、清晰布局、渐变背景延续 |
| **题目数量页** | 设置练习题目数量 | 输入题目数量、开始练习、返回上一级 | 简洁输入框、范围提示、友好错误提示 |
| **答题页** | 进行数学题目练习 | 显示题目、选择答案、进度跟踪 | 清晰排版、选项高亮、进度指示、交互反馈 |
| **结果页** | 展示答题成绩和统计 | 显示得分、重新练习、返回主页 | 数据可视化、醒目得分、鼓励性设计 |
## 使用指南
### 注册账号
1. 在主页面点击 **“注册账号”**
2. 输入用户名、邮箱和密码
3. 接收邮箱验证码并填写,完成注册
### 登录系统
1. 点击主页面 **“🎮 开始冒险”**
2. 输入注册的账号与密码进行登录
### 开始练习
1. 登录后选择学段:
* 小学乐园
* 初中城堡
* 高中太空
2. 输入题目数量1030 之间)
3. 点击 **“ 开始冒险”** 进入答题环节
### 查看结果
* 完成全部题目后系统自动展示得分结果
* 可选择 **“重新做题”** 或返回主页继续挑战
---
## 开发说明
### 技术栈
* **语言:** Python
* **框架:** PyQt5
* **架构:** 模块化设计 + QStackedWidget 页面切换
### 核心模块
| 文件名 | 主要功能 |
| ---------------- | ------------ |
| main_window.py | 主窗口逻辑与页面切换管理 |
| question_bank.py | 题目生成与试卷管理 |
| email_service.py | 注册验证码生成与邮件发送 |
| data_handler.py | 文件存储与用户数据处理 |
---
## 界面设计
* 使用 **QSS 样式表** 实现渐变背景、圆角按钮、柔和阴影等效果
* 通过 **QStackedWidget** 实现多页面无缝切换
* 界面配色采用卡通风格,适合中小学生视觉体验
---
## 联系方式
如需反馈问题或提出改进建议,请联系:
**1463365450@qq.com**
或在 **头歌** 提交 Issue

@ -0,0 +1,53 @@
import json
import os
from pathlib import Path
class DataHandler:
def __init__(self):
self.base_dir = Path("math_learning_data")
self.users_file = self.base_dir / "users.json"
self.ensure_data_dir()
def ensure_data_dir(self):
"""确保数据目录和文件存在并且文件内容是有效的JSON"""
if not self.base_dir.exists():
self.base_dir.mkdir(parents=True)
if not self.users_file.exists():
with open(self.users_file, "w", encoding="utf-8") as f:
json.dump({}, f, ensure_ascii=False) # 写入空对象
else:
# 如果文件存在但为空,写入空对象
if os.path.getsize(self.users_file) == 0:
with open(self.users_file, "w", encoding="utf-8") as f:
json.dump({}, f, ensure_ascii=False)
def load_users(self):
"""加载所有用户数据"""
with open(self.users_file, "r", encoding="utf-8") as f:
try:
return json.load(f)
except json.JSONDecodeError:
# 如果文件内容损坏,返回空字典
return {}
def save_users(self, users_data):
"""保存用户数据"""
with open(self.users_file, "w", encoding="utf-8") as f:
json.dump(users_data, f, ensure_ascii=False, indent=2)
def get_user(self, email):
"""获取用户信息"""
users = self.load_users()
return users.get(email)
def add_user(self, email, user_data):
"""添加新用户"""
users = self.load_users()
users[email] = user_data
self.save_users(users)
def update_user(self, email, user_data):
"""更新用户信息"""
self.add_user(email, user_data) # 直接覆盖更新

@ -0,0 +1,167 @@
import random
import time
import smtplib
import logging
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr
from tkinter import messagebox
from typing import Dict
from smtplib import SMTPResponseException
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class EmailService:
def __init__(self, smtp_server: str, smtp_port: int, sender_email: str, sender_password: str):
self.smtp_server = smtp_server
self.smtp_port = smtp_port
self.sender_email = sender_email
self.sender_password = sender_password
self.verification_codes: Dict[str, tuple[str, float]] = {} # 邮箱: (验证码, 过期时间)
self.code_validity = 60 # 验证码有效期1分钟
self.last_send_time: Dict[str, float] = {} # 记录上次发送时间
self.send_interval = 60 # 发送间隔60秒
def generate_code(self) -> str:
"""生成6位数字注册码"""
return str(random.randint(100000, 999999))
def can_send_code(self, email: str) -> bool:
"""检查是否可以发送注册码(防止频繁发送)"""
if email in self.last_send_time:
elapsed = time.time() - self.last_send_time[email]
return elapsed >= self.send_interval
return True
def send_verification_code(self, email: str, username: str = "用户") -> bool:
"""发送验证码到用户邮箱。
Args:
email: 目标邮箱地址
username: 用户昵称用于邮件正文
Returns:
成功返回 True失败返回 False
"""
if not self.can_send_code(email):
logger.warning("发送过于频繁,邮箱: %s", email)
return False
code = self.generate_code()
now = time.time()
self.verification_codes[email] = (code, now + self.code_validity)
self.last_send_time[email] = now
subject = "数学学习软件 - 注册验证码"
formatted_email = self._format_email(email)
body = self._build_email_body(username, code)
try:
self._send_email(email, subject, body)
logger.info("注册码已发送到 %s", email)
return True
except smtplib.SMTPAuthenticationError as e:
logger.error("邮箱认证失败: %s", e)
self._show_warning("邮箱认证失败", "请检查邮箱账号和授权码是否正确")
return False
except smtplib.SMTPConnectError as e:
logger.error("无法连接到SMTP服务器: %s", e)
self._show_warning("连接失败", "无法连接到邮件服务器,请检查网络连接")
return False
except Exception as e:
logger.error("邮件发送失败: %s", e, exc_info=True)
self._show_warning("发送失败", f"邮件发送失败: {e}")
return False
def _format_email(self, email: str) -> str:
"""隐藏部分邮箱字符,保护隐私。"""
try:
at_index = email.index('@')
if at_index >= 4:
return email[:3] + '...' + email[at_index - 4:at_index]
return email
except ValueError:
return email
def _build_email_body(self, username: str, code: str) -> str:
"""构建HTML邮件正文。"""
return f"""
<html>
<body style="font-family: 'Microsoft YaHei', Arial, sans-serif;
line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;
border: 1px solid #e0e0e0; border-radius: 8px;">
<div style="text-align: center;
border-bottom: 2px solid #4a90e2;
padding-bottom: 15px; margin-bottom: 20px;">
<h1 style="color: #4a90e2; margin: 0;">数学学习软件</h1>
<p style="color: #666; margin: 5px 0 0 0;">注册验证码</p>
</div>
<div style="background-color: #f8f9fa; padding: 20px;
border-radius: 5px; margin: 20px 0;">
<p>亲爱的 <strong>{username}</strong>您好</p>
<p>您正在注册数学学习软件请使用以下注册码完成注册</p>
<div style="text-align: center; margin: 25px 0;">
<span style="font-size: 32px; font-weight: bold; color: #e74c3c;
letter-spacing: 5px; background: #f8f9fa;
padding: 10px 20px; border: 2px dashed #e74c3c;
border-radius: 5px; display: inline-block;">
{code}
</span>
</div>
<p style="color: #e74c3c; font-weight: bold;">
注册码有效期1分钟请尽快完成注册
</p>
</div>
<div style="color: #999; font-size: 12px; text-align: center;
margin-top: 30px; padding-top: 15px;
border-top: 1px solid #e0e0e0;">
<p>如果这不是您的操作请忽略此邮件</p>
<p>此为系统邮件请勿回复</p>
<p style="font-weight: bold;">数学学习软件团队</p>
</div>
</div>
</body>
</html>
"""
def _send_email(self, to_email: str, subject: str, body: str) -> None:
"""发送邮件。"""
msg = MIMEText(body, "html", "utf-8")
msg["Subject"] = Header(subject, "utf-8")
msg["From"] = formataddr(("数学学习软件", self.sender_email))
msg["To"] = to_email
if self.smtp_port == 587:
server = smtplib.SMTP(self.smtp_server, self.smtp_port, timeout=10)
server.starttls()
else:
server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port, timeout=10)
server.set_debuglevel(1)
server.login(self.sender_email, self.sender_password)
server.sendmail(self.sender_email, to_email, msg.as_string())
server.quit()
def _show_warning(self, title: str, message: str) -> None:
"""弹出警告框。"""
messagebox.warning(None, title, message)
def verify_code(self, email: str, code: str) -> bool:
"""验证注册码是否有效"""
if email not in self.verification_codes:
return False
stored_code, expire_time = self.verification_codes[email]
if time.time() > expire_time:
del self.verification_codes[email]
return False
return stored_code == code

@ -0,0 +1,276 @@
# -*- coding: utf-8 -*-
"""数学学习软件 - 题目生成与试卷管理模块
提供小学初中高中三个学段的数学选择题自动生成查重保存功能
"""
from __future__ import annotations
import json
import math
import os
import random
from datetime import datetime
from typing import List, Tuple
# 模块级常量
PRIMARY_OPS = ("+", "-", "*", "/")
MIDDLE_OPS = ("+", "-", "*", "/", "^2", "sqrt")
HIGH_OPS = ("+", "-", "*", "/", "sin", "cos", "tan")
PRIMARY_RANGE = (1, 50)
MIDDLE_RANGE = (1, 100)
HIGH_ANGLE_RANGE = (0, 90) # 角度制
MAX_OPTS = 4
MAX_TRIES = 100
PAPERS_DIR = "generated_papers"
class Question:
"""单道选择题."""
__slots__ = ("content", "options", "answer")
def __init__(self, content: str, options: List[str], answer: str):
"""初始化.
Args:
content: 题干含表达式
options: 四个选项文本
answer: 正确答案文本
"""
self.content = content
self.options = options
self.answer = answer
class QuestionBank:
"""题库:按学段生成不重复的选择题,并保存为文本试卷."""
def __init__(self) -> None:
"""初始化."""
self.operators = {
"primary": PRIMARY_OPS,
"middle": MIDDLE_OPS,
"high": HIGH_OPS,
}
self.generated_questions: List[Question] = []
self.papers_dir = PAPERS_DIR
os.makedirs(self.papers_dir, exist_ok=True)
# ------------------------------------------------------------------
# 公共接口
# ------------------------------------------------------------------
def generate_question(self, level: str) -> Question:
for attempt in range(MAX_TRIES):
try:
expr, value = self._make_expression(level)
if not self._is_valid_value(value) or expr in [q.content for q in self.generated_questions]:
continue
opts, ans = self._make_options(value, level)
return Question(f"计算:{expr}", opts, ans)
except Exception as e:
print(f"[WARNING] 题目生成失败,尝试 {attempt + 1}/{MAX_TRIES},错误:{e}")
continue
# 兜底题目
print("[WARNING] 使用兜底题目")
expr, value = self._fallback_expression(level)
opts, ans = self._make_options(value, level)
return Question(f"计算:{expr}", opts, ans)
def generate_paper(self, level: str, count: int, username: str = "unknown") -> List[Question]:
"""生成整张试卷并落盘.
Args:
level: 学段
count: 题目数量
username: 用户昵称
Returns:
题目列表
"""
self.generated_questions.clear()
questions = [self.generate_question(level) for _ in range(count)]
self._save_paper(questions, level, username)
return questions
def calculate_score(self, answers: List[bool]) -> int:
"""计算百分制得分.
Args:
answers: 每题是否正确
Returns:
0~100
"""
if not answers:
return 0
return round(sum(answers) / len(answers) * 100)
# ------------------------------------------------------------------
# 私有辅助
# ------------------------------------------------------------------
def _make_expression(self, level: str) -> Tuple[str, float]:
"""返回表达式字符串与数值结果."""
if level == "primary":
return self._primary_expr()
if level == "middle":
return self._middle_expr()
return self._high_expr()
def _primary_expr(self) -> Tuple[str, float]:
"""小学表达式:整数结果."""
nums = [random.randint(*PRIMARY_RANGE) for _ in range(random.randint(2, 3))]
ops = list(random.choices(PRIMARY_OPS, k=len(nums) - 1))
self._ensure_int_div_sub(nums, ops)
parts = [str(nums[0])]
for o, n in zip(ops, nums[1:]):
parts.extend([o, str(n)])
expr = self._add_parentheses(parts)
val = self._safe_int_eval(expr)
if val is not None:
return expr, val
a, b = sorted(random.randint(*PRIMARY_RANGE) for _ in range(2))
op = random.choice(["+", "-", "*"])
expr = f"{a} {op} {b}"
return expr, eval(expr)
def _middle_expr(self) -> Tuple[str, float]:
"""初中表达式:含平方或开方."""
base = random.randint(*MIDDLE_RANGE)
if random.choice([True, False]):
inner = f"{base}^2"
val = base ** 2
else:
inner = f"sqrt({base})"
val = math.sqrt(base)
if random.choice([True, False]):
n2 = random.randint(*MIDDLE_RANGE)
op = random.choice(PRIMARY_OPS)
inner = f"({inner} {op} {n2})"
val = eval(f"{val} {op} {n2}")
return inner, val
def _high_expr(self) -> Tuple[str, float]:
"""高中表达式:含三角函数."""
angle = random.randint(*HIGH_ANGLE_RANGE)
func = random.choice(["sin", "cos", "tan"])
expr = f"{func}({angle})"
val = getattr(math, func)(math.radians(angle))
if random.choice([True, False]):
n2 = random.randint(1, 90)
op = random.choice(PRIMARY_OPS)
expr = f"({expr} {op} {n2})"
val = eval(f"{val} {op} {n2}")
return expr, val
def _fallback_expression(self, level: str) -> Tuple[str, float]:
"""兜底简单表达式."""
if level == "primary":
a, b = sorted(random.randint(*PRIMARY_RANGE) for _ in range(2))
expr = f"{a} + {b}"
return expr, eval(expr)
if level == "middle":
n = random.randint(1, 10)
expr = f"{n}^2"
return expr, n ** 2
angle = random.randint(1, 89)
expr = f"sin({angle})"
return expr, math.sin(math.radians(angle))
# -- 工具 -----------------------------------------------------------
@staticmethod
def _ensure_int_div_sub(nums: List[int], ops: List[str]) -> None:
"""调整 nums/ops 保证整数结果."""
for i, op in enumerate(ops):
if op == "/":
nums[i] *= nums[i + 1]
elif op == "-" and nums[i] < nums[i + 1]:
nums[i], nums[i + 1] = nums[i + 1], nums[i]
@staticmethod
def _add_parentheses(parts: List[str]) -> str:
"""随机给表达式加括号."""
if len(parts) >= 5 and random.choice([True, False]):
start = random.randint(0, len(parts) - 4)
if start % 2 == 0:
parts.insert(start, "(")
parts.insert(start + 4, ")")
return " ".join(parts)
@staticmethod
def _safe_int_eval(expr: str) -> float | None:
"""安全计算并返回整数结果."""
try:
val = eval(expr)
if abs(val - round(val)) < 1e-4:
return round(val)
except Exception:
pass
return None
@staticmethod
def _is_valid_value(val: float) -> bool:
"""检查数值是否合法."""
return not (math.isnan(val) or math.isinf(val) or abs(val) > 1e10)
# -- 选项 & 保存 ------------------------------------------------------
def _make_options(self, correct: float, level: str) -> Tuple[List[str], str]:
"""生成四个选项1正确+3干扰"""
if level == "primary":
correct_val = int(round(correct))
else:
correct_val = round(correct, 2)
opts = [correct_val]
while len(opts) < 4:
distractor = self._make_distractor(opts, correct_val, level)
if distractor not in opts:
opts.append(distractor)
random.shuffle(opts)
# ✅ 确保答案是从 opts 中获取的,而不是原始浮点数
ans = str(opts[opts.index(correct_val)])
return [str(o) for o in opts], ans
def _make_distractor(self, existing: List[float], correct: float, level: str) -> float:
"""生成一个不重复的干扰项."""
while True:
if level == "primary":
d = int(correct) + random.randint(-5, 5)
if d != correct and d > 0:
return d
else:
delta = abs(correct) * 0.3 if correct else 1
d = round(correct + random.uniform(-delta, delta), 2)
if d not in existing:
return d
def _save_paper(self, questions: List[Question], level: str, username: str) -> None:
"""试卷落盘."""
level_name = {"primary": "小学", "middle": "初中", "high": "高中"}.get(level, level)
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
path = os.path.join(self.papers_dir, f"{timestamp}.txt")
with open(path, "w", encoding="utf-8") as f:
f.write(f"数学学习软件 - {level_name}数学试卷\n")
f.write(f"生成时间:{datetime.now()}\n")
f.write("=" * 50 + "\n\n")
for idx, q in enumerate(questions, 1):
f.write(f"{idx}题:{q.content}\n选项:\n")
for i, opt in enumerate(q.options):
f.write(f" {chr(65+i)}. {opt}\n")
f.write(f"正确答案:{q.answer}\n\n")
f.write("=" * 50 + "\n")
f.write(f"{len(questions)}\n")
print(f"试卷已保存到:{path}")

@ -0,0 +1,163 @@
import re
import json
import os
import sys
from typing import Dict, Optional
from .email_service import EmailService
# 获取exe所在目录的绝对路径
if getattr(sys, 'frozen', False):
# 如果是打包后的exe
BASE_DIR = os.path.dirname(sys.executable)
else:
# 如果是开发环境
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
USER_DATA_FILE = os.path.join(BASE_DIR, "data", "users.json")
class UserSystem:
def __init__(self, email_service: EmailService):
self.email_service = email_service
# 确保data目录存在
os.makedirs(os.path.dirname(USER_DATA_FILE), exist_ok=True)
self.users: Dict[str, dict] = self.load_users()
self.current_user: Optional[str] = None
self.current_level: Optional[str] = None
def load_users(self) -> Dict[str, dict]:
"""从文件加载用户数据"""
print(f"尝试加载用户文件: {USER_DATA_FILE}") # 调试信息
print(f"文件是否存在: {os.path.exists(USER_DATA_FILE)}") # 调试信息
if os.path.exists(USER_DATA_FILE):
try:
with open(USER_DATA_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError) as e:
print(f"加载用户文件失败: {e}") # 调试信息
return {}
else:
print("用户文件不存在,创建空用户字典") # 调试信息
return {}
def save_users(self):
"""保存用户数据到文件"""
print(f"保存用户数据到: {USER_DATA_FILE}") # 调试信息
# 确保目录存在
os.makedirs(os.path.dirname(USER_DATA_FILE), exist_ok=True)
try:
with open(USER_DATA_FILE, "w", encoding="utf-8") as f:
json.dump(self.users, f, ensure_ascii=False, indent=4)
print("用户数据保存成功") # 调试信息
except Exception as e:
print(f"保存用户数据失败: {e}") # 调试信息
# 其他方法保持不变...
def is_valid_email(self, email: str) -> bool:
"""验证邮箱格式"""
pattern = r"^[\w\.-]+@[\w\.-]+\.\w+$"
return re.match(pattern, email) is not None
def is_valid_password(self, password: str) -> bool:
"""密码要求6-10位包含大小写字母和数字"""
if len(password) < 6 or len(password) > 10:
return False
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_digit = any(c.isdigit() for c in password)
return has_upper and has_lower and has_digit
def is_valid_username(self, username: str) -> bool:
"""用户名要求2-10位中文、字母或数字"""
if len(username) < 2 or len(username) > 10:
return False
pattern = r"^[\u4e00-\u9fa5a-zA-Z0-9]+$"
return re.match(pattern, username) is not None
def is_username_exists(self, username: str) -> bool:
"""检查用户名是否已存在"""
return username in self.users
def send_verification(self, email: str, username: str = "用户") -> bool:
"""发送验证码"""
# 检查邮箱是否已被其他用户使用
for user_data in self.users.values():
if user_data.get("email") == email:
return False
return self.email_service.send_verification_code(email, username)
def register(self, username: str, email: str, code: str, password: str) -> bool:
"""注册新用户"""
if self.is_username_exists(username):
return False
if not self.is_valid_password(password):
return False
if not self.is_valid_username(username):
return False
if not self.is_valid_email(email):
return False
# 检查邮箱是否已被使用
for user_data in self.users.values():
if user_data.get("email") == email:
return False
if not self.email_service.verify_code(email, code):
return False
self.users[username] = {
"password": password,
"email": email,
"level": None
}
self.save_users()
return True
def login(self, username: str, password: str) -> bool:
"""登录验证 - 使用用户名登录"""
user = self.users.get(username)
if user and user["password"] == password:
self.current_user = username
self.current_level = user.get("level")
return True
return False
def change_password(self, old_password: str, new_password: str) -> bool:
"""修改当前登录用户密码"""
if not self.current_user:
return False
user = self.users.get(self.current_user)
if not user or user["password"] != old_password:
return False
if not self.is_valid_password(new_password):
return False
user["password"] = new_password
self.save_users()
return True
def set_username(self, new_username: str) -> bool:
"""修改当前用户名"""
if not self.current_user:
return False
if not self.is_valid_username(new_username):
return False
if self.is_username_exists(new_username):
return False
# 更新用户名
user_data = self.users.pop(self.current_user)
self.users[new_username] = user_data
self.current_user = new_username
self.save_users()
return True
def set_level(self, level: str):
"""设置当前用户学段"""
if self.current_user:
self.users[self.current_user]["level"] = level
self.current_level = level
self.save_users()
def get_user_email(self, username: str) -> Optional[str]:
"""获取用户的邮箱"""
user = self.users.get(username)
return user.get("email") if user else None

@ -0,0 +1,26 @@
import sys
from PyQt5.QtWidgets import QApplication
from ui.main_window import MainWindow
from core.user_system import UserSystem
from core.email_service import EmailService
def main():
"""程序入口函数"""
app = QApplication(sys.argv)
email_service = EmailService(
smtp_server="smtp.qq.com",
smtp_port=465,
sender_email="1463365450@qq.com",
sender_password="pbpmkecsnahubaba"
)
user_system = UserSystem(email_service)
main_window = MainWindow(user_system)
main_window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

@ -0,0 +1,674 @@
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QPushButton, QMessageBox, QFrame, QSizePolicy,QDialog)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
class LoginPage(QWidget): # 改为继承 QWidget
def __init__(self, parent=None):
super().__init__(parent)
self.parent_window = parent
self.user_system = parent.user_system if parent else None
self.setup_ui()
def setup_ui(self):
# 移除 setWindowTitle 和 setMinimumSize使用父窗口的尺寸
# 设置可爱的渐变背景
self.setStyleSheet("""
QWidget {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
}
""")
# 主布局
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(0)
# 添加返回按钮区域
back_button_layout = QHBoxLayout()
back_button_layout.setContentsMargins(0, 0, 0, 10)
self.back_btn = QPushButton("←返回")
self.back_btn.setMinimumHeight(50)
self.back_btn.setMaximumWidth(120)
self.back_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #A0E7E5, stop:1 #6CD6D3);
color: white;
font: bold 11pt '微软雅黑';
border-radius: 15px;
border: 2px solid #8ADBD9;
padding: 5px 15px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8ADBD9, stop:1 #5AC7C4);
border: 2px solid #6CD6D3;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #5AC7C4, stop:1 #4BB5B2);
}
""")
self.back_btn.clicked.connect(self.go_back)
back_button_layout.addWidget(self.back_btn)
back_button_layout.addStretch() # 将按钮推到左边
main_layout.addLayout(back_button_layout)
# 卡片容器 - 自适应尺寸
card = QFrame()
card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
card.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 white, stop:1 #FFF9FA);
border-radius: 20px;
border: 3px solid #FF9EBC;
}
""")
main_layout.addWidget(card)
# 卡片布局
card_layout = QVBoxLayout(card)
card_layout.setContentsMargins(30, 25, 30, 25)
card_layout.setSpacing(0)
# 顶部弹性空间
card_layout.addStretch(1)
# 标题区域
title_frame = QFrame()
title_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_frame.setStyleSheet("background: transparent; border: none;")
title_layout = QVBoxLayout(title_frame)
title_layout.setSpacing(8)
# 装饰性emoji
emoji_label = QLabel("🔐✨🎮")
emoji_label.setStyleSheet("""
QLabel {
font: bold 18pt 'Arial';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
emoji_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_layout.addWidget(emoji_label)
# 主标题
title_label = QLabel("用户登录")
title_label.setStyleSheet("""
QLabel {
font: bold 22pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_layout.addWidget(title_label)
subtitle_label = QLabel("🌟 欢迎回到数学冒险岛 🌟")
subtitle_label.setStyleSheet("""
QLabel {
font: 12pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
subtitle_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_layout.addWidget(subtitle_label)
card_layout.addWidget(title_frame)
card_layout.addSpacing(30)
# 输入区域
input_frame = QFrame()
input_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
input_frame.setStyleSheet("background: transparent; border: none;")
input_layout = QVBoxLayout(input_frame)
input_layout.setSpacing(20)
# 用户名区域
username_layout = QVBoxLayout()
username_layout.setSpacing(5)
username_label = QLabel("👤 用户名:")
username_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
username_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
username_layout.addWidget(username_label)
self.username_input = QLineEdit()
self.username_input.setMinimumHeight(40)
self.username_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.username_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
QLineEdit::placeholder {
color: #CCCCCC;
font: 11pt '微软雅黑';
}
""")
self.username_input.setPlaceholderText("请输入用户名...")
username_layout.addWidget(self.username_input)
input_layout.addLayout(username_layout)
# 密码区域
password_layout = QVBoxLayout()
password_layout.setSpacing(5)
password_label = QLabel("🔒 密码:")
password_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
password_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
password_layout.addWidget(password_label)
self.password_input = QLineEdit()
self.password_input.setMinimumHeight(40)
self.password_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.password_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
QLineEdit::placeholder {
color: #CCCCCC;
font: 11pt '微软雅黑';
}
""")
self.password_input.setEchoMode(QLineEdit.Password)
self.password_input.setPlaceholderText("请输入密码...")
password_layout.addWidget(self.password_input)
input_layout.addLayout(password_layout)
card_layout.addWidget(input_frame)
card_layout.addSpacing(25)
# 按钮区域
button_frame = QFrame()
button_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
button_frame.setStyleSheet("background: transparent; border: none;")
button_layout = QVBoxLayout(button_frame)
button_layout.setSpacing(12)
# 登录按钮
self.login_btn = QPushButton("🚀 开始冒险")
self.login_btn.setMinimumHeight(45)
self.login_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.login_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF9EBC, stop:1 #FF6B9C);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 20px;
border: 2px solid #FF85A1;
max-width: 300px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF85A1, stop:1 #FF5784);
border: 2px solid #FF6B9C;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF5784, stop:1 #FF3D6D);
}
""")
self.login_btn.clicked.connect(self.do_login)
button_layout.addWidget(self.login_btn, alignment=Qt.AlignCenter)
# 修改密码按钮
self.change_pwd_btn = QPushButton("🔑 修改密码")
self.change_pwd_btn.setMinimumHeight(40)
self.change_pwd_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.change_pwd_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #A0E7E5, stop:1 #6CD6D3);
color: white;
font: bold 12pt '微软雅黑';
border-radius: 18px;
border: 2px solid #8ADBD9;
max-width: 280px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8ADBD9, stop:1 #5AC7C4);
border: 2px solid #6CD6D3;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #5AC7C4, stop:1 #4BB5B2);
}
""")
self.change_pwd_btn.clicked.connect(self.show_change_pwd)
button_layout.addWidget(self.change_pwd_btn, alignment=Qt.AlignCenter)
card_layout.addWidget(button_frame)
card_layout.addStretch(2)
# 装饰性底部
decoration_label = QLabel("✨🌈🎯 数学冒险岛 🌟🎮🚀")
decoration_label.setStyleSheet("""
QLabel {
font: bold 10pt 'Arial';
color: #FF9EBC;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
decoration_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
card_layout.addWidget(decoration_label)
# 绑定回车键
self.username_input.returnPressed.connect(self.do_login)
self.password_input.returnPressed.connect(self.do_login)
# 设置焦点
self.username_input.setFocus()
def go_back(self):
"""返回上一级页面"""
if self.parent_window:
self.parent_window.show_main_page() # 返回欢迎页面
def do_login(self):
"""执行登录"""
username = self.username_input.text().strip()
password = self.password_input.text()
if not username:
QMessageBox.warning(self, "⚠️ 提示", "请输入用户名")
self.username_input.setFocus()
return
if not password:
QMessageBox.warning(self, "⚠️ 提示", "请输入密码")
self.password_input.setFocus()
return
if self.user_system.login(username, password):
QMessageBox.information(self, "🎉 登录成功", f"欢迎 {username} 回到数学冒险岛!")
# 不再使用 self.accept(),而是通知父窗口切换页面
if self.parent_window:
self.parent_window.show_level_page()
else:
QMessageBox.warning(self, "❌ 登录失败", "用户名或密码错误,请重试")
self.password_input.clear()
self.password_input.setFocus()
def show_change_pwd(self):
"""显示修改密码界面"""
from .login_ui import ChangePasswordUI
change_pwd_ui = ChangePasswordUI(self)
change_pwd_ui.exec_() # 修改密码还是用弹窗,因为涉及敏感操作
def showEvent(self, event):
"""显示页面时清空输入框"""
super().showEvent(event)
self.username_input.clear()
self.password_input.clear()
self.username_input.setFocus()
class ChangePasswordUI(QDialog): # 这个保持为 QDialog因为是敏感操作
def __init__(self, parent=None):
super().__init__(parent)
self.parent_window = parent
self.user_system = parent.user_system if parent else None
self.setup_ui()
def setup_ui(self):
self.setWindowTitle("🔑 修改密码 - 数学冒险岛 🔑")
self.setMinimumSize(450, 550)
# 设置可爱的渐变背景
self.setStyleSheet("""
QDialog {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
}
""")
# 主布局
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(0)
# 卡片容器 - 自适应尺寸
card = QFrame()
card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
card.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 white, stop:1 #FFF9FA);
border-radius: 20px;
border: 3px solid #FF9EBC;
}
""")
main_layout.addWidget(card)
# 卡片布局
card_layout = QVBoxLayout(card)
card_layout.setContentsMargins(30, 25, 30, 25)
card_layout.setSpacing(0)
# 顶部弹性空间
card_layout.addStretch(1)
# 标题区域
title_frame = QFrame()
title_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_frame.setStyleSheet("background: transparent; border: none;")
title_layout = QVBoxLayout(title_frame)
title_layout.setSpacing(8)
# 装饰性emoji
emoji_label = QLabel("🔑✨🛡️")
emoji_label.setStyleSheet("""
QLabel {
font: bold 18pt 'Arial';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
emoji_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_layout.addWidget(emoji_label)
# 主标题
title_label = QLabel("修改密码")
title_label.setStyleSheet("""
QLabel {
font: bold 22pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_layout.addWidget(title_label)
subtitle_label = QLabel("🔒 保护你的冒险账户安全 🔒")
subtitle_label.setStyleSheet("""
QLabel {
font: 12pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
subtitle_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_layout.addWidget(subtitle_label)
card_layout.addWidget(title_frame)
card_layout.addSpacing(30)
# 输入区域
input_frame = QFrame()
input_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
input_frame.setStyleSheet("background: transparent; border: none;")
input_layout = QVBoxLayout(input_frame)
input_layout.setSpacing(20)
# 原密码
old_pwd_layout = QVBoxLayout()
old_pwd_layout.setSpacing(5)
old_pwd_label = QLabel("🔓 原密码:")
old_pwd_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
old_pwd_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
old_pwd_layout.addWidget(old_pwd_label)
self.old_pwd_input = QLineEdit()
self.old_pwd_input.setMinimumHeight(38)
self.old_pwd_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.old_pwd_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
""")
self.old_pwd_input.setEchoMode(QLineEdit.Password)
self.old_pwd_input.setPlaceholderText("请输入原密码...")
old_pwd_layout.addWidget(self.old_pwd_input)
input_layout.addLayout(old_pwd_layout)
# 新密码
new_pwd_layout = QVBoxLayout()
new_pwd_layout.setSpacing(5)
new_pwd_label = QLabel("🆕 新密码:")
new_pwd_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
new_pwd_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
new_pwd_layout.addWidget(new_pwd_label)
self.new_pwd_input = QLineEdit()
self.new_pwd_input.setMinimumHeight(38)
self.new_pwd_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.new_pwd_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
""")
self.new_pwd_input.setEchoMode(QLineEdit.Password)
self.new_pwd_input.setPlaceholderText("请输入新密码...")
new_pwd_layout.addWidget(self.new_pwd_input)
input_layout.addLayout(new_pwd_layout)
# 确认新密码
confirm_pwd_layout = QVBoxLayout()
confirm_pwd_layout.setSpacing(5)
confirm_pwd_label = QLabel("✅ 确认新密码:")
confirm_pwd_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
confirm_pwd_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
confirm_pwd_layout.addWidget(confirm_pwd_label)
self.confirm_pwd_input = QLineEdit()
self.confirm_pwd_input.setMinimumHeight(38)
self.confirm_pwd_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.confirm_pwd_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
""")
self.confirm_pwd_input.setEchoMode(QLineEdit.Password)
self.confirm_pwd_input.setPlaceholderText("请再次输入新密码...")
confirm_pwd_layout.addWidget(self.confirm_pwd_input)
input_layout.addLayout(confirm_pwd_layout)
card_layout.addWidget(input_frame)
card_layout.addSpacing(15)
# 密码提示
hint_label = QLabel("📝 密码要求6-10位必须包含大小写字母和数字")
hint_label.setStyleSheet("""
QLabel {
font: 9pt '微软雅黑';
color: #FF9EBC;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
hint_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
card_layout.addWidget(hint_label)
card_layout.addSpacing(25)
# 确认按钮
button_frame = QFrame()
button_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
button_frame.setStyleSheet("background: transparent; border: none;")
button_layout = QVBoxLayout(button_frame)
self.confirm_btn = QPushButton("✨ 确认修改")
self.confirm_btn.setMinimumHeight(45)
self.confirm_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.confirm_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #B5EAD7, stop:1 #8CD9B3);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 20px;
border: 2px solid #A0E7E5;
max-width: 300px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8CD9B3, stop:1 #6CD6D3);
border: 2px solid #8CD9B3;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #6CD6D3, stop:1 #5AC7C4);
}
""")
self.confirm_btn.clicked.connect(self.change_password)
button_layout.addWidget(self.confirm_btn, alignment=Qt.AlignCenter)
card_layout.addWidget(button_frame)
card_layout.addStretch(2)
# 装饰性底部
decoration_label = QLabel("🔐🌟🛡️ 账户安全最重要 🌈✨🎯")
decoration_label.setStyleSheet("""
QLabel {
font: bold 10pt 'Arial';
color: #FF9EBC;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
decoration_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
card_layout.addWidget(decoration_label)
# 绑定回车键
self.old_pwd_input.returnPressed.connect(self.change_password)
self.new_pwd_input.returnPressed.connect(self.change_password)
self.confirm_pwd_input.returnPressed.connect(self.change_password)
# 设置焦点
self.old_pwd_input.setFocus()
def change_password(self):
old_password = self.old_pwd_input.text()
new_password = self.new_pwd_input.text()
confirm_password = self.confirm_pwd_input.text()
if not old_password or not new_password or not confirm_password:
QMessageBox.warning(self, "⚠️ 提示", "请填写完整信息")
return
if new_password != confirm_password:
QMessageBox.warning(self, "⚠️ 提示", "两次输入的新密码不一致")
self.new_pwd_input.clear()
self.confirm_pwd_input.clear()
self.new_pwd_input.setFocus()
return
# 检查是否已登录
if not self.user_system.current_user:
QMessageBox.warning(self, "⚠️ 提示", "请先登录后再修改密码")
self.close()
return
if self.user_system.change_password(old_password, new_password):
QMessageBox.information(self, "🎉 修改成功", "密码修改成功!")
self.accept()
else:
QMessageBox.warning(self, "❌ 修改失败", "原密码错误或新密码不符合要求")
self.old_pwd_input.clear()
self.new_pwd_input.clear()
self.confirm_pwd_input.clear()
self.old_pwd_input.setFocus()

@ -0,0 +1,764 @@
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLabel, QPushButton, QLineEdit,
QMessageBox, QFrame, QGridLayout, QSizePolicy, QStackedWidget)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QPalette, QColor
# 导入各个页面需要先修改这些类继承QWidget
from .login_ui import LoginPage
from .register_ui import RegisterPage
from .question_ui import QuestionPage
from .result_ui import ResultPage
class MainWindow(QMainWindow):
def __init__(self,user_system):
super().__init__()
self.user_system = user_system
self.menu_bar = None
self.current_level = None
self.question_count = None
# 创建堆叠窗口
self.stacked_widget = QStackedWidget()
self.init_ui()
def init_ui(self):
"""初始化主窗口界面"""
self.setWindowTitle("🎮 数学冒险岛 🎮")
# self.setMinimumSize(900, 650)
# 设置固定大小(例如 900×650
# self.setFixedSize(900, 1200)
# 获取字体高度来设置相对大小
font_metrics = self.fontMetrics()
char_height = font_metrics.height()
# 基于字符高度设置尺寸
self.setFixedSize(40 * char_height, 55 * char_height)
# 设置可爱的渐变背景色
palette = self.palette()
palette.setColor(QPalette.Window, QColor(255, 240, 245))
self.setPalette(palette)
# 设置中央部件为堆叠窗口
self.setCentralWidget(self.stacked_widget)
# 初始化各个页面
self.init_pages()
# 显示主页面
self.show_main_page()
def init_pages(self):
"""初始化所有页面"""
# 主页面
self.main_page = self.create_main_page()
self.stacked_widget.addWidget(self.main_page)
# 登录页面
self.login_page = LoginPage(self)
self.stacked_widget.addWidget(self.login_page)
# 注册页面
self.register_page = RegisterPage(self)
self.stacked_widget.addWidget(self.register_page)
# 学段选择页面
self.level_page = self.create_level_page()
self.stacked_widget.addWidget(self.level_page)
# 题目数量页面
self.count_page = self.create_count_page()
self.stacked_widget.addWidget(self.count_page)
def show_main_page(self):
"""显示主页面"""
self.stacked_widget.setCurrentWidget(self.main_page)
self.clear_menu_bar()
def show_login_page(self):
"""显示登录页面"""
self.stacked_widget.setCurrentWidget(self.login_page)
self.clear_menu_bar()
def show_register_page(self):
"""显示注册页面"""
self.stacked_widget.setCurrentWidget(self.register_page)
self.clear_menu_bar()
def show_level_page(self):
"""显示学段选择页面"""
if not self.user_system or not self.user_system.current_user:
QMessageBox.warning(self, "提示", "请先登录")
return
self.stacked_widget.setCurrentWidget(self.level_page)
self.create_user_menu()
def show_count_page(self):
"""显示题目数量输入页面"""
if not self.user_system or not self.user_system.current_user:
QMessageBox.warning(self, "提示", "请先登录")
return
self.update_count_page(self.current_level)
self.stacked_widget.setCurrentWidget(self.count_page)
def show_question_page(self, level: str, count: int):
try:
# ✅ 每次创建新页面前,清理旧的 QuestionPage如果有
for i in reversed(range(self.stacked_widget.count())):
widget = self.stacked_widget.widget(i)
if isinstance(widget, QuestionPage):
self.stacked_widget.removeWidget(widget)
widget.deleteLater() # 释放内存
# ✅ 再创建新页面
question_page = QuestionPage(self, level, count)
self.stacked_widget.addWidget(question_page)
self.stacked_widget.setCurrentWidget(question_page)
except Exception as e:
QMessageBox.critical(self, "❌ 程序错误", f"生成题目时发生错误:\n{str(e)}")
print(f"[ERROR] 题目生成失败: {e}")
try:
question_page = QuestionPage(self, level, count)
self.stacked_widget.addWidget(question_page)
self.stacked_widget.setCurrentWidget(question_page)
except Exception as e:
QMessageBox.critical(self, "❌ 程序错误", f"生成题目时发生错误:\n{str(e)}")
print(f"[ERROR] 题目生成失败: {e}")
def show_result_page(self, score: int, level: str, count: int):
"""显示结果页面"""
result_page = ResultPage(self, score, level, count)
self.stacked_widget.addWidget(result_page)
self.stacked_widget.setCurrentWidget(result_page)
def remove_current_page(self):
"""移除当前页面(用于答题和结果页面)"""
current_index = self.stacked_widget.currentIndex()
# 只移除动态添加的页面(索引大于固定页面数量)
if current_index >= 5: # 主页面、登录、注册、学段、题目数量
current_widget = self.stacked_widget.currentWidget()
self.stacked_widget.removeWidget(current_widget)
def create_main_page(self):
"""创建主页面"""
widget = QWidget()
# 主布局 - 使用弹性布局
main_layout = QVBoxLayout(widget)
main_layout.setContentsMargins(30, 20, 30, 20)
main_layout.setSpacing(0)
# 主容器框架 - 自适应尺寸
container = QFrame()
container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
container.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
border-radius: 20px;
border: 3px solid #FF9EBC;
}
""")
main_layout.addWidget(container)
# 容器布局 - 使用弹性布局
container_layout = QVBoxLayout(container)
container_layout.setContentsMargins(40, 30, 40, 30)
container_layout.setSpacing(0)
# 顶部弹性空间
container_layout.addStretch(1)
# --- 标题区域 ---
title_frame = QFrame()
title_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_frame.setStyleSheet("background: transparent; border: none;")
title_layout = QVBoxLayout(title_frame)
title_layout.setSpacing(10)
# 装饰性emoji
emoji_label = QLabel("✨🎓🧮🌟")
emoji_label.setStyleSheet("""
QLabel {
font: bold 20pt 'Arial';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(emoji_label)
# 主标题
title_label = QLabel("数学冒险岛")
title_label.setStyleSheet("""
QLabel {
font: bold 28pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(title_label)
# 副标题
subtitle_label = QLabel("🚀 开启你的数学冒险之旅! 🚀")
subtitle_label.setStyleSheet("""
QLabel {
font: 14pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(subtitle_label)
container_layout.addWidget(title_frame)
container_layout.addSpacing(40)
# --- 按钮区域 ---
button_frame = QFrame()
button_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
button_frame.setStyleSheet("background: transparent; border: none;")
button_layout = QVBoxLayout(button_frame)
button_layout.setSpacing(20)
# 登录按钮
login_btn = QPushButton("🎮 开始冒险")
login_btn.setMinimumSize(250, 60)
login_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
login_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF9EBC, stop:1 #FF6B9C);
color: white;
font: bold 16pt '微软雅黑';
border-radius: 30px;
border: 3px solid #FF85A1;
max-width: 300px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF85A1, stop:1 #FF5784);
border: 3px solid #FF6B9C;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF5784, stop:1 #FF3D6D);
}
""")
login_btn.clicked.connect(self.show_login_page) # 改为页面切换
button_layout.addWidget(login_btn, alignment=Qt.AlignCenter)
# 注册按钮
register_btn = QPushButton("📝 注册账号")
register_btn.setMinimumSize(250, 60)
register_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
register_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #A0E7E5, stop:1 #6CD6D3);
color: white;
font: bold 16pt '微软雅黑';
border-radius: 30px;
border: 3px solid #8ADBD9;
max-width: 300px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8ADBD9, stop:1 #5AC7C4);
border: 3px solid #6CD6D3;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #5AC7C4, stop:1 #4BB5B2);
}
""")
register_btn.clicked.connect(self.show_register_page) # 改为页面切换
button_layout.addWidget(register_btn, alignment=Qt.AlignCenter)
container_layout.addWidget(button_frame)
container_layout.addStretch(2)
# --- 装饰性元素 ---
decoration_frame = QFrame()
decoration_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
decoration_frame.setStyleSheet("background: transparent; border: none;")
decoration_layout = QHBoxLayout(decoration_frame)
math_symbols = QLabel("🔢 ✖️ ➗ 📐 📊 🧮")
math_symbols.setStyleSheet("""
QLabel {
font: bold 16pt 'Arial';
color: #FF9EBC;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
decoration_layout.addWidget(math_symbols)
container_layout.addWidget(decoration_frame)
container_layout.addSpacing(20)
# --- 底部信息 ---
footer_frame = QFrame()
footer_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
footer_frame.setStyleSheet("background: transparent; border: none;")
footer_layout = QHBoxLayout(footer_frame)
footer_label = QLabel("🌈 版权所有 © 2025 数学冒险岛 🌈")
footer_label.setStyleSheet("""
QLabel {
font: 10pt '微软雅黑';
color: #888888;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
footer_layout.addWidget(footer_label)
container_layout.addWidget(footer_frame)
return widget
def create_level_page(self):
"""创建学段选择页面"""
widget = QWidget()
# 主布局
main_layout = QVBoxLayout(widget)
main_layout.setContentsMargins(30, 20, 30, 20)
main_layout.setSpacing(0)
# 主容器
frame = QFrame()
frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
frame.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
border-radius: 20px;
border: 3px solid #FF9EBC;
}
""")
main_layout.addWidget(frame)
frame_layout = QVBoxLayout(frame)
frame_layout.setContentsMargins(40, 30, 40, 30)
frame_layout.setSpacing(0)
# 顶部空间
frame_layout.addStretch(1)
# 欢迎信息
welcome_text = f"🎉 欢迎回来! 🎉"
self.welcome_label = QLabel(welcome_text)
self.welcome_label.setStyleSheet("""
QLabel {
font: bold 18pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
self.welcome_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
frame_layout.addWidget(self.welcome_label)
frame_layout.addSpacing(40)
# 学段选择标题
select_label = QLabel("🎯 选择你的冒险地图 🎯")
select_label.setStyleSheet("""
QLabel {
font: bold 20pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
select_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
frame_layout.addWidget(select_label)
frame_layout.addSpacing(40)
# 按钮区域
btn_frame = QFrame()
btn_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
btn_frame.setStyleSheet("background: transparent; border: none;")
btn_layout = QGridLayout(btn_frame)
btn_layout.setSpacing(20)
btn_layout.setVerticalSpacing(25)
levels = [("🏫 小学乐园", "primary"), ("🏰 初中城堡", "middle"), ("🚀 高中太空", "high")]
colors = [
("#FF9EBC", "#FF6B9C"), # 粉色
("#A0E7E5", "#6CD6D3"), # 青色
("#B5EAD7", "#8CD9B3") # 绿色
]
for i, (text, level) in enumerate(levels):
color_pair = colors[i % len(colors)]
btn = QPushButton(text)
btn.setMinimumSize(280, 70)
btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
btn.setStyleSheet(f"""
QPushButton {{
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 {color_pair[0]}, stop:1 {color_pair[1]});
color: white;
font: bold 18pt '微软雅黑';
border-radius: 35px;
border: 3px solid {color_pair[0]};
max-width: 320px;
}}
QPushButton:hover {{
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 {color_pair[1]}, stop:1 {color_pair[0]});
}}
QPushButton:pressed {{
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #555555, stop:1 {color_pair[1]});
}}
""")
btn.clicked.connect(lambda checked, l=level: self.on_level_selected(l))
btn_layout.addWidget(btn, i, 0, alignment=Qt.AlignCenter)
frame_layout.addWidget(btn_frame)
frame_layout.addStretch(1)
# 功能按钮区域
func_frame = QFrame()
func_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
func_frame.setStyleSheet("background: transparent; border: none;")
func_layout = QHBoxLayout(func_frame)
func_layout.setSpacing(20)
# 修改密码按钮
change_pwd_btn = QPushButton("🔑 修改密码")
change_pwd_btn.setMinimumSize(180, 50)
change_pwd_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
change_pwd_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFDAC1, stop:1 #FFB347);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 25px;
border: 3px solid #FFB347;
max-width: 200px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFB347, stop:1 #FF9800);
}
""")
change_pwd_btn.clicked.connect(self.open_change_password)
func_layout.addWidget(change_pwd_btn)
# 返回上级按钮
back_btn = QPushButton("🔙 返回主页")
back_btn.setMinimumSize(180, 50)
back_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
back_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #C7CEEA, stop:1 #9FA8DA);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 25px;
border: 3px solid #9FA8DA;
max-width: 200px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #9FA8DA, stop:1 #7986CB);
}
""")
back_btn.clicked.connect(self.show_main_page) # 改为页面切换
func_layout.addWidget(back_btn)
frame_layout.addWidget(func_frame, alignment=Qt.AlignCenter)
frame_layout.addStretch(1)
return widget
def create_count_page(self):
"""创建题目数量页面"""
widget = QWidget()
# 主布局
main_layout = QVBoxLayout(widget)
main_layout.setContentsMargins(30, 20, 30, 20)
main_layout.setSpacing(0)
# 主容器
frame = QFrame()
frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
frame.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
border-radius: 20px;
border: 3px solid #FF9EBC;
}
""")
main_layout.addWidget(frame)
frame_layout = QVBoxLayout(frame)
frame_layout.setContentsMargins(40, 30, 40, 30)
frame_layout.setSpacing(0)
# 顶部空间
frame_layout.addStretch(1)
# 显示当前学段
self.level_label = QLabel()
self.level_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.level_label.setStyleSheet("""
QLabel {
font: bold 16pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
frame_layout.addWidget(self.level_label)
frame_layout.addSpacing(30)
# 输入提示
count_label = QLabel("🎯 请输入题目数量10-30")
count_label.setStyleSheet("""
QLabel {
font: bold 18pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
count_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
frame_layout.addWidget(count_label)
# 装饰性emoji
emoji_label = QLabel("🔢 ✨ 📚 💫")
emoji_label.setStyleSheet("""
QLabel {
font: bold 16pt 'Arial';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
emoji_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
frame_layout.addWidget(emoji_label)
frame_layout.addSpacing(30)
# 输入框
self.count_input = QLineEdit()
self.count_input.setFont(QFont("微软雅黑", 14))
self.count_input.setMinimumSize(200, 50)
self.count_input.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.count_input.setAlignment(Qt.AlignCenter)
self.count_input.setStyleSheet("""
QLineEdit {
background: white;
border: 3px solid #FF9EBC;
border-radius: 20px;
font: bold 14pt '微软雅黑';
color: #FF6B9C;
padding: 5px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 3px solid #FF6B9C;
background: #FFF9FA;
}
""")
frame_layout.addWidget(self.count_input, alignment=Qt.AlignCenter)
frame_layout.addSpacing(40)
# 按钮区域
btn_frame = QFrame()
btn_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
btn_frame.setStyleSheet("background: transparent; border: none;")
btn_layout = QHBoxLayout(btn_frame)
btn_layout.setSpacing(30)
# 开始答题按钮
start_btn = QPushButton("🚀 开始冒险")
start_btn.setMinimumSize(210, 60)
start_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
start_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF9EBC, stop:1 #FF6B9C);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 30px;
border: 3px solid #FF85A1;
max-width: 220px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF85A1, stop:1 #FF5784);
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF5784, stop:1 #FF3D6D);
}
""")
start_btn.clicked.connect(self.start_question)
btn_layout.addWidget(start_btn)
# 返回按钮
back_btn = QPushButton("🔙 返回选择")
back_btn.setMinimumSize(210, 60)
back_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
back_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #C7CEEA, stop:1 #9FA8DA);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 30px;
border: 3px solid #9FA8DA;
max-width: 220px;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #9FA8DA, stop:1 #7986CB);
}
""")
back_btn.clicked.connect(self.show_level_page) # 改为页面切换
btn_layout.addWidget(back_btn)
frame_layout.addWidget(btn_frame, alignment=Qt.AlignCenter)
frame_layout.addStretch(2)
return widget
def update_level_page(self):
"""更新学段选择页面的欢迎信息"""
if self.user_system and self.user_system.current_user:
welcome_text = f"🎉 欢迎回来,{self.user_system.current_user} 🎉"
self.welcome_label.setText(welcome_text)
def update_count_page(self, level: str):
"""更新题目数量页面的学段信息"""
level_text = {"primary": "🏫 小学乐园", "middle": "🏰 初中城堡", "high": "🚀 高中太空"}.get(level, level)
self.level_label.setText(f"🗺️ 当前地图:{level_text}")
self.count_input.clear()
self.count_input.setFocus()
# 修改原有方法
def open_login(self):
"""打开登录界面 - 改为页面切换"""
self.show_login_page()
def open_register(self):
"""打开注册界面 - 改为页面切换"""
self.show_register_page()
def on_level_selected(self, level: str):
"""学段选择后进入题目数量输入"""
if self.user_system:
self.user_system.set_level(level)
self.current_level = level
self.update_count_page(level)
self.show_count_page()
def start_question(self):
"""开始答题"""
try:
count = int(self.count_input.text())
if 10 <= count <= 30:
self.question_count = count
self.show_question_page(self.current_level, count)
else:
QMessageBox.warning(self, "⚠️ 提示", "请输入10-30之间的数字哦")
except ValueError:
QMessageBox.warning(self, "⚠️ 提示", "请输入有效的数字!")
# 其他方法保持不变...
def create_user_menu(self):
"""创建用户菜单栏"""
if self.menu_bar:
self.menu_bar.clear()
else:
self.menu_bar = self.menuBar()
self.menu_bar.setStyleSheet("""
QMenuBar {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
color: #FF6B9C;
font: bold 12pt '微软雅黑';
border-bottom: 2px solid #FF9EBC;
}
QMenuBar::item {
background: transparent;
padding: 5px 10px;
}
QMenuBar::item:selected {
background: #FF9EBC;
border-radius: 10px;
}
""")
# 用户菜单
user_menu = self.menu_bar.addMenu(f"🎮 欢迎(点这里哦),{self.user_system.current_user}")
user_menu.setStyleSheet("""
QMenu {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
}
QMenu::item {
padding: 8px 20px;
font: 11pt '微软雅黑';
}
QMenu::item:selected {
background: #FFE4EC;
border-radius: 10px;
}
""")
user_menu.addAction("🔑 修改密码", self.open_change_password)
user_menu.addSeparator()
user_menu.addAction("🚪 退出登录", self.logout)
def open_change_password(self):
"""打开修改密码界面"""
if self.user_system and self.user_system.current_user:
from .login_ui import ChangePasswordUI
change_password_ui = ChangePasswordUI(self)
change_password_ui.exec_() # 这个还是弹窗,因为涉及敏感操作
else:
QMessageBox.warning(self, "提示", "请先登录")
def logout(self):
"""退出登录"""
if self.user_system:
self.user_system.current_user = None
self.user_system.current_level = None
QMessageBox.information(self, "🎮 再见", "期待下次与你一起冒险!")
self.show_main_page()
def clear_menu_bar(self):
"""清空菜单栏"""
if self.menu_bar:
self.menu_bar.clear()
self.menu_bar = None
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle('Fusion')
window = MainWindow()
window.show()
sys.exit(app.exec_())

@ -0,0 +1,455 @@
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QMessageBox, QFrame, QRadioButton,
QButtonGroup, QScrollArea, QWidget, QProgressBar, QSizePolicy)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from .result_ui import ResultPage
from core.question_bank import QuestionBank
class QuestionPage(QWidget): # 改为继承 QWidget
def __init__(self, parent, level: str, count: int):
super().__init__(parent)
try:
self.parent_window = parent
self.level = level
self.count = count
self.current_idx = 0
self.answers = [None] * count
self.question_bank = QuestionBank()
self.paper = self.question_bank.generate_paper(level, count)
self.setup_ui()
self.show_question()
except Exception as e:
print(f"[ERROR] QuestionPage 初始化失败: {e}")
raise e # 重新抛出,供上层捕获
def setup_ui(self):
"""初始化界面"""
# 移除 setWindowTitle 和 setMinimumSize使用父窗口的尺寸
# 设置可爱的渐变背景
self.setStyleSheet("""
QWidget {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
}
""")
# 主布局
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(25, 20, 25, 20)
main_layout.setSpacing(0)
# 顶部信息区域
top_frame = QFrame()
top_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
top_frame.setStyleSheet("""
QFrame {
background: transparent;
border: none;
}
""")
top_layout = QVBoxLayout(top_frame)
top_layout.setSpacing(15)
# 标题
title_label = QLabel("🧮 数学冒险岛 - 答题挑战 🧮")
title_label.setStyleSheet("""
QLabel {
font: bold 24pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
top_layout.addWidget(title_label)
# 进度显示
self.progress_label = QLabel()
self.progress_label.setStyleSheet("""
QLabel {
font: bold 16pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
self.progress_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
top_layout.addWidget(self.progress_label)
# 进度条
self.progress_bar = QProgressBar()
self.progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.progress_bar.setFixedHeight(25)
self.progress_bar.setStyleSheet("""
QProgressBar {
border: 3px solid #FF9EBC;
border-radius: 12px;
background-color: white;
text-align: center;
font: bold 12pt '微软雅黑';
color: #FF6B9C;
}
QProgressBar::chunk {
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
stop:0 #FF9EBC, stop:0.5 #FF6B9C, stop:1 #FF3D6D);
border-radius: 8px;
}
""")
top_layout.addWidget(self.progress_bar)
main_layout.addWidget(top_frame)
main_layout.addSpacing(20)
# 题目内容框架(可滚动)
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setStyleSheet("""
QScrollArea {
border: none;
background: transparent;
}
QScrollBar:vertical {
background: white;
width: 15px;
margin: 0px;
}
QScrollBar::handle:vertical {
background: #FF9EBC;
border-radius: 7px;
min-height: 20px;
}
QScrollBar::handle:vertical:hover {
background: #FF6B9C;
}
""")
main_layout.addWidget(scroll_area)
# 滚动区域的内容部件
scroll_content = QWidget()
scroll_area.setWidget(scroll_content)
content_layout = QVBoxLayout(scroll_content)
content_layout.setContentsMargins(0, 0, 0, 0)
# 题目卡片
question_card = QFrame()
question_card.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 white, stop:1 #FFF9FA);
border-radius: 20px;
border: 3px solid #FF9EBC;
}
""")
question_layout = QVBoxLayout(question_card)
question_layout.setContentsMargins(30, 25, 30, 25)
question_layout.setSpacing(20)
# 题干
self.question_label = QLabel()
self.question_label.setStyleSheet("""
QLabel {
font: bold 18pt '微软雅黑';
color: #5A5A5A;
background: transparent;
line-height: 1.5;
}
""")
self.question_label.setWordWrap(True)
self.question_label.setAlignment(Qt.AlignLeft | Qt.AlignTop)
question_layout.addWidget(self.question_label)
# 选项框架
self.options_frame = QFrame()
self.options_frame.setStyleSheet("""
QFrame {
background: transparent;
border: none;
}
""")
self.options_layout = QVBoxLayout(self.options_frame)
self.options_layout.setSpacing(12)
question_layout.addWidget(self.options_frame)
content_layout.addWidget(question_card)
main_layout.addSpacing(20)
# 底部按钮框架
button_frame = QFrame()
button_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
button_frame.setStyleSheet("background: transparent; border: none;")
button_layout = QHBoxLayout(button_frame)
button_layout.setSpacing(20)
# 上一题按钮
self.prev_btn = QPushButton("⬅️ 上一题")
self.prev_btn.setMinimumSize(120, 50)
self.prev_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.prev_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #C7CEEA, stop:1 #9FA8DA);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 25px;
border: 3px solid #9FA8DA;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #9FA8DA, stop:1 #7986CB);
border: 3px solid #7986CB;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #7986CB, stop:1 #5C6BC0);
}
QPushButton:disabled {
background: #E0E0E0;
color: #9E9E9E;
border: 3px solid #CCCCCC;
}
""")
self.prev_btn.clicked.connect(self.prev_question)
button_layout.addWidget(self.prev_btn)
button_layout.addStretch(1)
# 下一题按钮
self.next_btn = QPushButton("下一题 ➡️")
self.next_btn.setMinimumSize(120, 50)
self.next_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.next_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #A0E7E5, stop:1 #6CD6D3);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 25px;
border: 3px solid #8ADBD9;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8ADBD9, stop:1 #5AC7C4);
border: 3px solid #6CD6D3;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #5AC7C4, stop:1 #4BB5B2);
}
""")
self.next_btn.clicked.connect(self.next_question)
button_layout.addWidget(self.next_btn)
button_layout.addStretch(1)
# 提交按钮
self.submit_btn = QPushButton("🎯 提交试卷")
self.submit_btn.setMinimumSize(140, 50)
self.submit_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.submit_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF9EBC, stop:1 #FF6B9C);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 25px;
border: 3px solid #FF85A1;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF85A1, stop:1 #FF5784);
border: 3px solid #FF6B9C;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF5784, stop:1 #FF3D6D);
}
""")
self.submit_btn.clicked.connect(self.submit_paper)
button_layout.addWidget(self.submit_btn)
main_layout.addWidget(button_frame)
# 选项按钮组
self.option_group = QButtonGroup(self)
self.option_group.buttonClicked.connect(self.on_option_selected)
def show_question(self):
"""显示当前题目"""
if self.current_idx >= len(self.paper):
return
question = self.paper[self.current_idx]
# 更新进度
progress_text = f"📊 第 {self.current_idx + 1} 题 / 共 {self.count}"
self.progress_label.setText(progress_text)
# 更新进度条
progress_percent = int((self.current_idx + 1) / self.count * 100)
self.progress_bar.setValue(progress_percent)
self.progress_bar.setFormat(f"🚀 进度: {progress_percent}%")
# 显示题干
self.question_label.setText(question.content)
# 清空选项框架
for i in reversed(range(self.options_layout.count())):
widget = self.options_layout.itemAt(i).widget()
if widget:
widget.deleteLater()
# 移除所有按钮
self.option_group = QButtonGroup(self)
self.option_group.buttonClicked.connect(self.on_option_selected)
# 创建选项按钮
option_labels = ["A", "B", "C", "D"]
option_emojis = ["🔸", "🔹", "🔸", "🔹"]
for i, option_text in enumerate(question.options):
# 选项卡片
option_card = QFrame()
option_card.setStyleSheet("""
QFrame {
background: white;
border: 2px solid #FFD1DC;
border-radius: 15px;
}
QFrame:hover {
background: #FFF9FA;
border: 2px solid #FF9EBC;
}
""")
option_card.setFixedHeight(60)
option_layout = QHBoxLayout(option_card)
option_layout.setContentsMargins(20, 10, 20, 10)
# 单选按钮
radio_btn = QRadioButton(f"{option_emojis[i]} {option_labels[i]}. {option_text}")
radio_btn.setStyleSheet("""
QRadioButton {
font: 16pt '微软雅黑';
color: #5A5A5A;
background: transparent;
spacing: 15px;
}
QRadioButton::indicator {
width: 24px;
height: 24px;
}
QRadioButton::indicator:unchecked {
border: 3px solid #FF9EBC;
border-radius: 12px;
background-color: white;
}
QRadioButton::indicator:checked {
border: 3px solid #FF6B9C;
border-radius: 12px;
background-color: #FF6B9C;
}
QRadioButton:hover {
color: #FF6B9C;
}
""")
self.option_group.addButton(radio_btn, i)
option_layout.addWidget(radio_btn)
option_layout.addStretch(1)
self.options_layout.addWidget(option_card)
# 恢复已选答案
if self.answers[self.current_idx] is not None:
button = self.option_group.button(self.answers[self.current_idx])
if button:
button.setChecked(True)
# 更新按钮状态
self.update_button_states()
def on_option_selected(self, button):
"""选项被选择时的处理"""
self.save_current_answer()
def update_button_states(self):
"""更新按钮状态"""
# 上一题按钮
self.prev_btn.setEnabled(self.current_idx > 0)
# 下一题按钮
if self.current_idx == self.count - 1:
self.next_btn.setText("最后一题 🏁")
else:
self.next_btn.setText("下一题 ➡️")
def save_current_answer(self):
"""保存当前题目的答案"""
selected_button = self.option_group.checkedButton()
if selected_button:
self.answers[self.current_idx] = self.option_group.id(selected_button)
def prev_question(self):
"""上一题"""
self.save_current_answer()
if self.current_idx > 0:
self.current_idx -= 1
self.show_question()
def next_question(self):
"""下一题"""
self.save_current_answer()
if self.current_idx < self.count - 1:
self.current_idx += 1
self.show_question()
else:
# 如果是最后一题,提示提交
QMessageBox.information(self, "🎉 完成", "太棒了!你已经完成了所有题目,请提交试卷查看成绩!")
def submit_paper(self):
"""提交试卷"""
self.save_current_answer()
# 检查是否所有题目都已作答
unanswered = [i+1 for i, answer in enumerate(self.answers) if answer is None]
if unanswered:
reply = QMessageBox.question(
self,
"🤔 确认提交",
f"还有 {len(unanswered)} 题未作答(题号:{unanswered}),确定要提交吗?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply != QMessageBox.Yes:
return
# 计算得分
correct_answers = []
for i, question in enumerate(self.paper):
selected_index = self.answers[i]
if selected_index is not None:
selected_answer = question.options[selected_index]
is_correct = (selected_answer == question.answer)
correct_answers.append(is_correct)
else:
correct_answers.append(False)
score = self.question_bank.calculate_score(correct_answers)
# 显示结果页面
if self.parent_window:
self.parent_window.show_result_page(score, self.level, self.count)
def showEvent(self, event):
"""显示页面时重置状态"""
super().showEvent(event)
# 重置答题状态
self.current_idx = 0
self.answers = [None] * self.count
self.show_question()

@ -0,0 +1,589 @@
# 标准库
import sys
# 第三方库
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QMessageBox, QFrame, QGridLayout, QSizePolicy
)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont
class RegisterPage(QWidget): # 改为继承 QWidget
def __init__(self, parent=None):
super().__init__(parent)
self.parent_window = parent
self.user_system = parent.user_system if parent else None
self.countdown_active = False
self.countdown_seconds = 60
self.countdown_timer = QTimer()
self.countdown_timer.timeout.connect(self.update_countdown)
self.setup_ui()
def setup_ui(self):
"""初始化注册界面"""
# 移除 setWindowTitle 和 setMinimumSize使用父窗口的尺寸
# 设置可爱的渐变背景
self.setStyleSheet("""
QWidget {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
}
""")
# 主布局
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(25, 20, 25, 20)
main_layout.setSpacing(0)
# 卡片容器
card = QFrame()
card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
card.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 white, stop:1 #FFF9FA);
border-radius: 20px;
border: 3px solid #FF9EBC;
}
""")
main_layout.addWidget(card)
# 卡片布局
card_layout = QVBoxLayout(card)
card_layout.setContentsMargins(35, 30, 35, 30)
card_layout.setSpacing(0)
# 顶部按钮栏
top_button_layout = QHBoxLayout()
top_button_layout.setAlignment(Qt.AlignLeft)
# 返回主页按钮
self.back_button = QPushButton("← 返回主页")
self.back_button.setMinimumSize(100, 35)
self.back_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.back_button.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #B5EAD7, stop:1 #8CD9C7);
color: white;
font: bold 11pt '微软雅黑';
border-radius: 15px;
border: 2px solid #A0E7E5;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #A0E7E5, stop:1 #7BCFBD);
border: 2px solid #8CD9C7;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #7BCFBD, stop:1 #6AC0AE);
}
""")
self.back_button.clicked.connect(self.go_back_to_main)
top_button_layout.addWidget(self.back_button)
top_button_layout.addStretch(1) # 将按钮推到左侧
card_layout.addLayout(top_button_layout)
card_layout.addSpacing(10)
# 顶部弹性空间
card_layout.addStretch(1)
# 标题区域
title_frame = QFrame()
title_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_frame.setStyleSheet("background: transparent; border: none;")
title_layout = QVBoxLayout(title_frame)
title_layout.setSpacing(10)
# 装饰性emoji
emoji_label = QLabel("🎉✨📝")
emoji_label.setStyleSheet("""
QLabel {
font: bold 20pt 'Arial';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(emoji_label)
# 主标题
title_label = QLabel("用户注册")
title_label.setStyleSheet("""
QLabel {
font: bold 24pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(title_label)
subtitle_label = QLabel("🚀 加入数学冒险岛大家庭 🚀")
subtitle_label.setStyleSheet("""
QLabel {
font: 12pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(subtitle_label)
card_layout.addWidget(title_frame)
card_layout.addSpacing(30)
# 表单区域 - 使用垂直布局包装网格布局
form_container = QFrame()
form_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
form_container.setStyleSheet("background: transparent; border: none;")
form_container_layout = QVBoxLayout(form_container)
# 网格布局
form_layout = QGridLayout()
form_layout.setVerticalSpacing(15) # 减少垂直间距
form_layout.setHorizontalSpacing(15)
form_layout.setColumnStretch(1, 1)
# 用户名输入
username_label = QLabel("👤 用户名:")
username_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
form_layout.addWidget(username_label, 0, 0, Qt.AlignLeft)
self.username_input = QLineEdit()
self.username_input.setMinimumHeight(40)
self.username_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.username_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
QLineEdit::placeholder {
color: #CCCCCC;
font: 11pt '微软雅黑';
}
""")
self.username_input.setPlaceholderText("请输入用户名...")
form_layout.addWidget(self.username_input, 0, 1)
# 用户名提示
username_hint = QLabel("📝 用户名要求2-10位中文、字母或数字")
username_hint.setStyleSheet("""
QLabel {
font: 9pt '微软雅黑';
color: #FF9EBC;
background: transparent;
}
""")
form_layout.addWidget(username_hint, 1, 1, Qt.AlignLeft)
# 添加间距行
form_layout.addWidget(QLabel(""), 2, 0, 1, 2) # 空行作为间距
# 邮箱输入
email_label = QLabel("📧 邮箱:")
email_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
form_layout.addWidget(email_label, 3, 0, Qt.AlignLeft)
self.email_input = QLineEdit()
self.email_input.setMinimumHeight(40)
self.email_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.email_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
QLineEdit::placeholder {
color: #CCCCCC;
font: 11pt '微软雅黑';
}
""")
self.email_input.setPlaceholderText("请输入邮箱...")
form_layout.addWidget(self.email_input, 3, 1)
# 添加间距行
form_layout.addWidget(QLabel(""), 4, 0, 1, 2) # 空行作为间距
# 注册码
code_label = QLabel("🔐 注册码:")
code_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
form_layout.addWidget(code_label, 5, 0, Qt.AlignLeft)
code_input_layout = QHBoxLayout()
code_input_layout.setSpacing(10)
self.code_input = QLineEdit()
self.code_input.setMinimumHeight(55)
self.code_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.code_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
QLineEdit::placeholder {
color: #CCCCCC;
font: 11pt '微软雅黑';
}
""")
self.code_input.setPlaceholderText("请输入注册码...")
code_input_layout.addWidget(self.code_input)
self.code_btn = QPushButton("获取注册码")
self.code_btn.setMinimumSize(120, 40)
self.code_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.code_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #A0E7E5, stop:1 #6CD6D3);
color: white;
font: bold 11pt '微软雅黑';
border-radius: 15px;
border: 2px solid #8ADBD9;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8ADBD9, stop:1 #5AC7C4);
border: 2px solid #6CD6D3;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #5AC7C4, stop:1 #4BB5B2);
}
QPushButton:disabled {
background: #E0E0E0;
color: #9E9E9E;
border: 2px solid #CCCCCC;
}
""")
self.code_btn.clicked.connect(self.send_code)
code_input_layout.addWidget(self.code_btn)
form_layout.addLayout(code_input_layout, 5, 1)
# 添加间距行
form_layout.addWidget(QLabel(""), 6, 0, 1, 2) # 空行作为间距
# 密码
pwd_label = QLabel("🔒 密码:")
pwd_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
form_layout.addWidget(pwd_label, 7, 0, Qt.AlignLeft)
self.pwd_input = QLineEdit()
self.pwd_input.setMinimumHeight(40)
self.pwd_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.pwd_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
QLineEdit::placeholder {
color: #CCCCCC;
font: 11pt '微软雅黑';
}
""")
self.pwd_input.setEchoMode(QLineEdit.Password)
self.pwd_input.setPlaceholderText("请输入密码...")
form_layout.addWidget(self.pwd_input, 7, 1)
# 密码提示
pwd_hint = QLabel("📝 密码要求6-10位必须包含大小写字母和数字")
pwd_hint.setStyleSheet("""
QLabel {
font: 9pt '微软雅黑';
color: #FF9EBC;
background: transparent;
}
""")
form_layout.addWidget(pwd_hint, 8, 1, Qt.AlignLeft)
# 添加间距行
form_layout.addWidget(QLabel(""), 9, 0, 1, 2) # 空行作为间距
# 确认密码
confirm_pwd_label = QLabel("✅ 确认密码:")
confirm_pwd_label.setStyleSheet("""
QLabel {
font: bold 12pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
form_layout.addWidget(confirm_pwd_label, 10, 0, Qt.AlignLeft)
self.confirm_pwd_input = QLineEdit()
self.confirm_pwd_input.setMinimumHeight(40)
self.confirm_pwd_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.confirm_pwd_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 15px;
font: 12pt '微软雅黑';
color: #FF6B9C;
padding: 8px 15px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
QLineEdit::placeholder {
color: #CCCCCC;
font: 11pt '微软雅黑';
}
""")
self.confirm_pwd_input.setEchoMode(QLineEdit.Password)
self.confirm_pwd_input.setPlaceholderText("请再次输入密码...")
form_layout.addWidget(self.confirm_pwd_input, 10, 1)
form_container_layout.addLayout(form_layout)
form_container_layout.addStretch(1)
card_layout.addWidget(form_container)
card_layout.addStretch(1)
# 按钮布局
button_layout = QHBoxLayout()
button_layout.setSpacing(20)
button_layout.setAlignment(Qt.AlignCenter)
# 返回主页按钮(底部)
back_button_bottom = QPushButton("🔙 返回主页")
back_button_bottom.setMinimumSize(120, 45)
back_button_bottom.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
back_button_bottom.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #C9C9C9, stop:1 #A8A8A8);
color: white;
font: bold 12pt '微软雅黑';
border-radius: 20px;
border: 2px solid #B8B8B8;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #B8B8B8, stop:1 #989898);
border: 2px solid #A8A8A8;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #989898, stop:1 #888888);
}
""")
back_button_bottom.clicked.connect(self.go_back_to_main)
button_layout.addWidget(back_button_bottom)
# 注册按钮
register_btn = QPushButton("🎉 立即注册")
register_btn.setMinimumSize(150, 50)
register_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
register_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF9EBC, stop:1 #FF6B9C);
color: white;
font: bold 14pt '微软雅黑';
border-radius: 25px;
border: 3px solid #FF85A1;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF85A1, stop:1 #FF5784);
border: 3px solid #FF6B9C;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF5784, stop:1 #FF3D6D);
}
""")
register_btn.clicked.connect(self.do_register)
button_layout.addWidget(register_btn)
card_layout.addLayout(button_layout)
card_layout.addSpacing(10)
# 装饰性底部
decoration_label = QLabel("✨🌈🎮 开启你的数学冒险之旅 🌟📚🚀")
decoration_label.setStyleSheet("""
QLabel {
font: bold 10pt 'Arial';
color: #FF9EBC;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
card_layout.addWidget(decoration_label)
# 设置焦点
self.username_input.setFocus()
def go_back_to_main(self):
"""返回主页"""
if self.parent_window:
self.parent_window.show_main_page()
def send_code(self):
"""发送注册码"""
if self.countdown_active:
return
email = self.email_input.text().strip()
username = self.username_input.text().strip() or "用户"
if not email:
QMessageBox.warning(self, "⚠️ 提示", "请输入邮箱地址")
self.email_input.setFocus()
return
if not self.user_system.is_valid_email(email):
QMessageBox.warning(self, "⚠️ 提示", "请输入有效的邮箱地址")
self.email_input.setFocus()
return
if self.user_system.send_verification(email, username):
QMessageBox.information(self, "📧 发送成功", "注册码已发送到您的邮箱,请查收!")
self.start_countdown()
else:
QMessageBox.warning(self, "❌ 发送失败", "邮箱已被使用或发送失败,请重试")
def start_countdown(self):
"""注册码按钮倒计时"""
print("开始倒计时") # 调试信息
self.countdown_active = True
self.code_btn.setEnabled(False)
self.countdown_seconds = 60
self.countdown_timer.start(1000)
self.update_countdown()
def update_countdown(self):
"""更新倒计时显示"""
print(f"倒计时更新: {self.countdown_seconds}") # 调试信息
if self.countdown_seconds > 0:
self.code_btn.setText(f"重新发送({self.countdown_seconds}s)")
self.countdown_seconds -= 1
# 强制更新UI
self.code_btn.repaint()
else:
self.countdown_timer.stop()
self.code_btn.setText("获取注册码")
self.code_btn.setEnabled(True)
self.countdown_active = False
print("倒计时结束") # 调试信息
def do_register(self):
"""执行注册"""
username = self.username_input.text().strip()
email = self.email_input.text().strip()
code = self.code_input.text().strip()
pwd = self.pwd_input.text()
confirm_pwd = self.confirm_pwd_input.text()
if not username or not email or not code or not pwd or not confirm_pwd:
QMessageBox.warning(self, "⚠️ 提示", "请填写完整信息")
return
if pwd != confirm_pwd:
QMessageBox.warning(self, "⚠️ 提示", "两次输入的密码不一致")
self.pwd_input.clear()
self.confirm_pwd_input.clear()
self.pwd_input.setFocus()
return
if self.user_system.register(username, email, code, pwd):
QMessageBox.information(self, "🎉 注册成功",
"注册成功!现在可以使用用户名登录,开始你的数学冒险之旅!")
# 不再使用 self.accept(),而是通知父窗口切换页面
if self.parent_window:
self.parent_window.show_main_page() # 注册成功后回到主页面
else:
QMessageBox.warning(self, "❌ 注册失败",
"注册失败:注册码错误、用户名已存在或信息不符合要求")
def showEvent(self, event):
"""显示页面时清空输入框"""
super().showEvent(event)
self.username_input.clear()
self.email_input.clear()
self.code_input.clear()
self.pwd_input.clear()
self.confirm_pwd_input.clear()
self.username_input.setFocus()
# 重置倒计时
if self.countdown_timer.isActive():
self.countdown_timer.stop()
self.code_btn.setText("获取注册码")
self.code_btn.setEnabled(True)
self.countdown_active = False
def closeEvent(self, event):
"""关闭事件处理"""
if self.countdown_timer.isActive():
self.countdown_timer.stop()
event.accept()

@ -0,0 +1,296 @@
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QMessageBox)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
class ResultPage(QWidget): # 改为继承 QWidget
def __init__(self, parent, score: int, level: str, count: int):
super().__init__(parent)
self.parent_window = parent
self.level = level
self.count = count
self.score = score
self.setup_ui()
def setup_ui(self):
"""初始化界面"""
# 移除 setWindowTitle 和 setMinimumSize使用父窗口的尺寸
# 设置可爱的渐变背景
self.setStyleSheet("""
QWidget {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
}
""")
# 主布局
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(30, 30, 30, 30)
main_layout.setSpacing(0)
# 卡片容器
card = QFrame()
card.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 white, stop:1 #FFF9FA);
border-radius: 25px;
border: 3px solid #FF9EBC;
}
""")
main_layout.addWidget(card)
# 卡片布局
card_layout = QVBoxLayout(card)
card_layout.setContentsMargins(40, 40, 40, 40)
card_layout.setSpacing(0)
# 顶部弹性空间
card_layout.addStretch(1)
# 标题区域
title_frame = QFrame()
title_frame.setStyleSheet("background: transparent; border: none;")
title_layout = QVBoxLayout(title_frame)
title_layout.setSpacing(15)
# 装饰性emoji
emoji_label = QLabel("🎊✨🏆")
emoji_label.setStyleSheet("""
QLabel {
font: bold 28pt 'Arial';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(emoji_label)
# 主标题
title_label = QLabel("冒险完成!")
title_label.setStyleSheet("""
QLabel {
font: bold 32pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(title_label)
# 副标题
level_text = {"primary": "小学乐园", "middle": "初中城堡", "high": "高中太空"}.get(self.level, self.level)
subtitle_label = QLabel(f"🎯 你在 {level_text} 完成了 {self.count} 道题目 🎯")
subtitle_label.setStyleSheet("""
QLabel {
font: 16pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(subtitle_label)
card_layout.addWidget(title_frame)
card_layout.addSpacing(40)
# 得分区域
score_frame = QFrame()
score_frame.setStyleSheet("background: transparent; border: none;")
score_layout = QVBoxLayout(score_frame)
score_layout.setSpacing(10)
# 得分标题
score_title_label = QLabel("你的冒险得分")
score_title_label.setStyleSheet("""
QLabel {
font: bold 18pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
score_layout.addWidget(score_title_label)
# 得分显示 - 根据分数显示不同颜色和评价
if self.score >= 90:
score_color = "#FF6B9C"
score_emoji = "🎉"
evaluation = "太棒了!你是数学小天才!"
elif self.score >= 80:
score_color = "#FF9EBC"
score_emoji = "🌟"
evaluation = "优秀!继续加油!"
elif self.score >= 70:
score_color = "#A0E7E5"
score_emoji = "👍"
evaluation = "良好!表现不错!"
elif self.score >= 60:
score_color = "#B5EAD7"
score_emoji = "💪"
evaluation = "及格!还有进步空间!"
else:
score_color = "#C7CEEA"
score_emoji = "📚"
evaluation = "加油!多练习会更好!"
score_display_frame = QFrame()
score_display_frame.setStyleSheet(f"""
QFrame {{
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 white, stop:1 #FFF9FA);
border-radius: 20px;
border: 3px solid {score_color};
}}
""")
score_display_layout = QVBoxLayout(score_display_frame)
score_display_layout.setContentsMargins(30, 20, 30, 20)
# 得分数字
score_value_label = QLabel(f"{score_emoji} {self.score}{score_emoji}")
score_value_label.setStyleSheet(f"""
QLabel {{
font: bold 48pt '微软雅黑';
color: {score_color};
background: transparent;
qproperty-alignment: AlignCenter;
}}
""")
score_display_layout.addWidget(score_value_label)
# 评价
evaluation_label = QLabel(evaluation)
evaluation_label.setStyleSheet(f"""
QLabel {{
font: bold 16pt '微软雅黑';
color: {score_color};
background: transparent;
qproperty-alignment: AlignCenter;
}}
""")
score_display_layout.addWidget(evaluation_label)
score_layout.addWidget(score_display_frame)
card_layout.addWidget(score_frame)
card_layout.addSpacing(40)
# 按钮框架
button_frame = QFrame()
button_frame.setStyleSheet("background: transparent; border: none;")
button_layout = QHBoxLayout(button_frame)
button_layout.setSpacing(20)
# 按钮样式
button_style = """
QPushButton {
font: bold 14pt '微软雅黑';
border-radius: 25px;
min-width: 140px;
min-height: 50px;
border: 3px solid;
}
"""
# 再做一套按钮
again_btn = QPushButton("🔄 再来一次")
again_btn.setStyleSheet(button_style + """
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #A0E7E5, stop:1 #6CD6D3);
color: white;
border-color: #8ADBD9;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8ADBD9, stop:1 #5AC7C4);
border-color: #6CD6D3;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #5AC7C4, stop:1 #4BB5B2);
}
""")
again_btn.clicked.connect(self.do_again)
button_layout.addWidget(again_btn)
# 返回学段按钮
level_btn = QPushButton("🗺️ 选择地图")
level_btn.setStyleSheet(button_style + """
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFDAC1, stop:1 #FFB347);
color: white;
border-color: #FFB347;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFB347, stop:1 #FF9800);
border-color: #FF9800;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF9800, stop:1 #F57C00);
}
""")
level_btn.clicked.connect(self.exit_to_level)
button_layout.addWidget(level_btn)
# 退出程序按钮
quit_btn = QPushButton("🚪 结束冒险")
quit_btn.setStyleSheet(button_style + """
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #C7CEEA, stop:1 #9FA8DA);
color: white;
border-color: #9FA8DA;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #9FA8DA, stop:1 #7986CB);
border-color: #7986CB;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #7986CB, stop:1 #5C6BC0);
}
""")
quit_btn.clicked.connect(self.quit_app)
button_layout.addWidget(quit_btn)
card_layout.addWidget(button_frame)
card_layout.addStretch(1)
# 装饰性底部
decoration_label = QLabel("✨🌈🎮 数学冒险岛 - 学数学,真有趣! 🌟📚🚀")
decoration_label.setStyleSheet("""
QLabel {
font: bold 12pt 'Arial';
color: #FF9EBC;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
card_layout.addWidget(decoration_label)
def quit_app(self):
"""退出程序"""
reply = QMessageBox.question(self, "🎮 结束冒险",
"确定要结束数学冒险吗?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if reply == QMessageBox.Yes:
if self.parent_window:
self.parent_window.close()
def do_again(self):
"""重新做题"""
if self.parent_window:
self.parent_window.remove_current_page() # 移除结果页面
self.parent_window.show_count_page() # 回到题目数量页面
def exit_to_level(self):
"""返回学段选择"""
if self.parent_window:
self.parent_window.remove_current_page() # 移除结果页面
self.parent_window.show_level_page() # 回到学段选择页面

@ -0,0 +1,297 @@
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QPushButton, QMessageBox, QFrame, QSizePolicy)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
class ChangeUsernamePage(QWidget): # 改为继承 QWidget
def __init__(self, parent=None):
super().__init__(parent)
self.parent_window = parent
self.user_system = parent.user_system if parent and hasattr(parent, 'user_system') else None
self.setup_ui()
def setup_ui(self):
"""初始化界面"""
# 移除 setWindowTitle 和 setMinimumSize使用父窗口的尺寸
# 设置可爱的渐变背景
self.setStyleSheet("""
QWidget {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFD1DC, stop:1 #B5EAD7);
}
""")
# 主布局
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(25, 20, 25, 20)
main_layout.setSpacing(0)
# 卡片容器
card = QFrame()
card.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
card.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 white, stop:1 #FFF9FA);
border-radius: 20px;
border: 3px solid #FF9EBC;
}
""")
main_layout.addWidget(card)
# 卡片布局
card_layout = QVBoxLayout(card)
card_layout.setContentsMargins(35, 30, 35, 30)
card_layout.setSpacing(0)
# 顶部弹性空间
card_layout.addStretch(1)
# 标题区域
title_frame = QFrame()
title_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
title_frame.setStyleSheet("background: transparent; border: none;")
title_layout = QVBoxLayout(title_frame)
title_layout.setSpacing(10)
# 装饰性emoji
emoji_label = QLabel("👤✨🆕")
emoji_label.setStyleSheet("""
QLabel {
font: bold 20pt 'Arial';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(emoji_label)
# 主标题
title_label = QLabel("修改用户名")
title_label.setStyleSheet("""
QLabel {
font: bold 24pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(title_label)
subtitle_label = QLabel("🎯 给你的冒险角色换个新名字吧! 🎯")
subtitle_label.setStyleSheet("""
QLabel {
font: 12pt '微软雅黑';
color: #5A5A5A;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
title_layout.addWidget(subtitle_label)
card_layout.addWidget(title_frame)
card_layout.addSpacing(30)
# 当前用户名显示
current_user_frame = QFrame()
current_user_frame.setStyleSheet("""
QFrame {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FFF9FA, stop:1 #FFE4EC);
border-radius: 15px;
border: 2px solid #FFD1DC;
}
""")
current_user_layout = QVBoxLayout(current_user_frame)
current_user_layout.setContentsMargins(20, 15, 20, 15)
current_title_label = QLabel("🎮 当前冒险者")
current_title_label.setStyleSheet("""
QLabel {
font: bold 14pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
current_user_layout.addWidget(current_title_label)
# 检查用户系统是否存在
if self.user_system and self.user_system.current_user:
current_username = self.user_system.current_user
else:
current_username = "未登录"
current_user_label = QLabel(f"{current_username}")
current_user_label.setStyleSheet("""
QLabel {
font: bold 18pt '微软雅黑';
color: #FF6B9C;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
current_user_layout.addWidget(current_user_label)
card_layout.addWidget(current_user_frame)
card_layout.addSpacing(25)
# 新用户名输入区域
input_frame = QFrame()
input_frame.setStyleSheet("background: transparent; border: none;")
input_layout = QVBoxLayout(input_frame)
input_layout.setSpacing(8)
# 新用户名标签
new_username_label = QLabel("🆕 新用户名:")
new_username_label.setStyleSheet("""
QLabel {
font: bold 14pt '微软雅黑';
color: #FF6B9C;
background: transparent;
}
""")
input_layout.addWidget(new_username_label)
# 新用户名输入框
self.new_username_input = QLineEdit()
self.new_username_input.setMinimumHeight(45)
self.new_username_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.new_username_input.setStyleSheet("""
QLineEdit {
background: white;
border: 2px solid #FF9EBC;
border-radius: 18px;
font: 14pt '微软雅黑';
color: #FF6B9C;
padding: 8px 20px;
selection-background-color: #FFE4EC;
}
QLineEdit:focus {
border: 2px solid #FF6B9C;
background: #FFF9FA;
}
QLineEdit::placeholder {
color: #CCCCCC;
font: 12pt '微软雅黑';
}
""")
self.new_username_input.setPlaceholderText("请输入新的冒险者名字...")
input_layout.addWidget(self.new_username_input)
# 用户名提示
hint_label = QLabel("📝 用户名要求2-10位中文、字母或数字")
hint_label.setStyleSheet("""
QLabel {
font: 10pt '微软雅黑';
color: #FF9EBC;
background: transparent;
}
""")
input_layout.addWidget(hint_label)
card_layout.addWidget(input_frame)
card_layout.addSpacing(30)
# 确认按钮
button_frame = QFrame()
button_frame.setStyleSheet("background: transparent; border: none;")
button_layout = QVBoxLayout(button_frame)
self.confirm_btn = QPushButton("✨ 确认修改")
self.confirm_btn.setMinimumSize(200, 50)
self.confirm_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.confirm_btn.setStyleSheet("""
QPushButton {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #B5EAD7, stop:1 #8CD9B3);
color: white;
font: bold 16pt '微软雅黑';
border-radius: 25px;
border: 3px solid #A0E7E5;
}
QPushButton:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8CD9B3, stop:1 #6CD6D3);
border: 3px solid #8CD9B3;
}
QPushButton:pressed {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #6CD6D3, stop:1 #5AC7C4);
}
""")
self.confirm_btn.clicked.connect(self.change_username)
button_layout.addWidget(self.confirm_btn, alignment=Qt.AlignCenter)
card_layout.addWidget(button_frame)
card_layout.addStretch(1)
# 装饰性底部
decoration_label = QLabel("🌟🌈🎮 换个名字,开启新的冒险! 📚✨🚀")
decoration_label.setStyleSheet("""
QLabel {
font: bold 10pt 'Arial';
color: #FF9EBC;
background: transparent;
qproperty-alignment: AlignCenter;
}
""")
card_layout.addWidget(decoration_label)
# 绑定回车键
self.new_username_input.returnPressed.connect(self.change_username)
# 设置焦点
self.new_username_input.setFocus()
def change_username(self):
"""修改用户名"""
if not self.user_system:
QMessageBox.warning(self, "❌ 错误", "用户系统未初始化")
return
if not self.user_system.current_user:
QMessageBox.warning(self, "⚠️ 提示", "请先登录")
if self.parent_window:
self.parent_window.show_main_page() # 回到主页面
return
new_username = self.new_username_input.text().strip()
if not new_username:
QMessageBox.warning(self, "⚠️ 提示", "请输入新用户名")
self.new_username_input.setFocus()
return
if new_username == self.user_system.current_user:
QMessageBox.warning(self, "⚠️ 提示", "新用户名与当前用户名相同,请换个不同的名字吧!")
self.new_username_input.clear()
self.new_username_input.setFocus()
return
if self.user_system.set_username(new_username):
QMessageBox.information(self, "🎉 修改成功",
f"用户名修改成功!\n\n"
f"从现在开始,你就是勇敢的冒险者:\n"
f"{new_username}")
# 不再使用 self.accept(),而是通知父窗口切换页面
if self.parent_window:
self.parent_window.show_level_page() # 回到学段选择页面
else:
QMessageBox.warning(self, "❌ 修改失败",
"用户名修改失败:\n"
"• 用户名不符合要求2-10位中文、字母或数字\n"
"• 或用户名已被其他冒险者使用")
self.new_username_input.clear()
self.new_username_input.setFocus()
def showEvent(self, event):
"""显示页面时重置状态"""
super().showEvent(event)
self.new_username_input.clear()
self.new_username_input.setFocus()
# 更新当前用户名显示
if hasattr(self, 'current_user_label') and self.user_system and self.user_system.current_user:
self.current_user_label.setText(f"{self.user_system.current_user}")
Loading…
Cancel
Save