更新1 #5

Closed
hnu202326010111 wants to merge 0 commits from wangzuwang_branch into develop

@ -1,177 +1,102 @@
软1_[彭云昊]_[王祖旺]_结对项目
中小学数学学习软件 - 结对编程项目
## 项目简介
项目简介
本项目是一个面向中小学学生的数学学习桌面应用程序,提供个性化的数学题目练习和评估功能。系统根据学生所在学段(小学、初中、高中)生成相应难度的数学题目,通过图形化界面提供友好的学习体验。
## 功能特性
### 用户管理
- ✅ 用户注册:通过邮箱验证码完成注册
- ✅ 密码设置6-10位必须包含大小写字母和数字
- ✅ 用户登录:安全的身份验证机制
- ✅ 密码修改:支持原密码验证
- ✅ 账户注销:永久删除账户及所有数据
### 题目生成
- ✅ 小学题目:加减乘除四则运算,支持括号
- ✅ 初中题目:平方、开根运算
- ✅ 高中题目三角函数计算sin, cos, tan
- ✅ 智能防重复:确保同一试卷无重复题目
### 学习流程
- ✅ 难度选择:小学/初中/高中三级难度
- ✅ 题目数量用户自定义题目数量10-30题
- ✅ 选择题形式每题4个选项单选作答
- ✅ 实时评分:提交后立即显示得分情况
- ✅ 学习延续:支持连续练习或退出选择
- ✅ 答题进度:支持上一题/下一题导航
- ✅ 成绩反馈:根据得分提供个性化评语
## 技术栈
- 编程语言Python 3.x
- GUI框架Tkinter内置Python GUI库
- 数据存储JSON文件无需数据库
- 邮件服务SMTP协议QQ邮箱
- 架构模式:前后端分离架构
## 项目结构
```
功能特性
用户管理
• ✅ 用户注册:通过邮箱验证码完成注册
• ✅ 密码设置6-10位必须包含大小写字母和数字
• ✅ 用户登录:安全的身份验证机制
• ✅ 密码修改:支持原密码验证
• ✅ 账户注销:永久删除账户及所有数据
题目生成
• ✅ 小学题目:加减乘除四则运算,支持括号
• ✅ 初中题目:平方、开根运算
• ✅ 高中题目:三角函数计算
• ✅ 智能防重复:确保同一试卷无重复题目
学习流程
• ✅ 难度选择:小学/初中/高中三级难度
• ✅ 题目数量用户自定义题目数量10-30题
• ✅ 选择题形式每题4个选项单选作答
• ✅ 实时评分:提交后立即显示得分情况
• ✅ 学习延续:支持连续练习或退出选择
• ✅ 答题进度:支持上一题/下一题导航
技术栈
• 编程语言Python 3.x
• GUI框架Tkinter内置Python GUI库
• 数据存储JSON文件无需数据库
• 邮件服务SMTP协议QQ邮箱
• 架构模式MVC模型-视图-控制器)
项目结构
text
复制
下载
软1_[彭云昊]_[王祖旺]_结对项目/
├── src/
│ ├── main.py # 程序入口
│ ├── main_app.py # 主应用模块,界面控制器
│ ├── backend_service.py # 后端服务模块,前后端通信中介
│ ├── user_manager.py # 用户管理模块
│ ├── question_bank.py # 题库管理模块
│ ├── question_generator.py # 题目生成器模块
│ ├── quiz.py # 测验管理模块
│ └── users.json # 用户数据文件(运行时生成)
└── doc/
└── README.md # 项目说明文档
```
## 架构设计
本项目采用前后端分离的架构设计,确保代码的可维护性和可扩展性:
- **前端层** (`main_app.py`)负责UI展示和用户交互不包含业务逻辑
- **服务层** (`backend_service.py`):作为前后端通信中介,转发请求到业务模块
- **业务逻辑层** (`user_manager.py`, `question_bank.py`, `quiz.py`):处理具体业务逻辑
- **数据层** (`users.json`):持久化存储用户数据
### 设计原则
1. 严格的前后端分离,前端仅负责界面展示和用户交互
2. 业务逻辑完全封装在后端模块中
3. 模块间通过定义良好的接口进行通信
4. 高内聚低耦合,便于独立测试和维护
## 安装与运行
### 环境要求
- Python 3.11.9或更高版本
- 网络连接(用于邮箱验证码发送)
- Windows 操作系统
### 运行步骤
1. 下载项目文件到本地
2. 确保所有Python文件在同一目录下
3. 运行主程序:
```bash
├── main.py # 程序入口
├── main_app.py # 主应用模块,界面控制器
├── user_manager.py # 用户管理模块
├── question_bank.py # 题库管理模块
├── question_generator.py # 题目生成器模块
├── quiz.py # 测验管理模块
├── users.json # 用户数据文件(运行时生成)
└── README.md # 项目说明文档
安装与运行
环境要求
• Python 3.7 或更高版本
• 网络连接(用于邮箱验证码发送)
运行步骤
1. 下载项目文件到本地
2. 确保所有Python文件在同一目录下
3. 运行主程序:
bash
复制
下载
python main.py
```
4. 按照界面提示进行注册和登录
## 使用说明
### 用户注册流程
1. 点击"注册新用户"
2. 输入用户名和邮箱
3. 点击"获取注册码",系统将发送验证码到邮箱
4. 输入收到的注册码
5. 设置符合要求的密码6-10位包含大小写字母和数字
6. 完成注册并登录
### 学习流程
1. 登录系统后进入主菜单
2. 选择题目难度(小学/初中/高中)
3. 输入题目数量10-30题
4. 开始答题,选择正确答案
5. 提交答案或切换题目
6. 完成所有题目后查看成绩和评语
## 预设测试账号
系统支持新用户注册,也可使用以下方式测试:
- 邮箱:任意有效邮箱(接收验证码)
- 密码符合规范的密码Abc123
## 分支管理
4. 按照界面提示进行注册和登录
预设测试账号
系统支持新用户注册,也可使用以下测试账号:
• 邮箱:任意有效邮箱(接收验证码)
• 密码符合规范的密码Abc123
分支管理
本项目遵循Git分支管理规范
- main分支稳定版本存放经过测试的代码
- develop分支开发主线集成最新功能
- 个人分支每位开发者的功能分支zhangsan_branch
## 代码提交规则
- 源代码:必须通过个人分支 + Pull Request
- 文档直接推送到develop分支
## 开发规范
### 代码规范
- 遵循PEP 8 Python编码规范
- 使用类型注解提高代码可读性
- 模块化设计,高内聚低耦合
- 严格遵循前后端分离原则
### 提交信息规范
- feat: 新功能
- fix: 修复bug
- docs: 文档更新
- style: 代码格式调整
- refactor: 代码重构
## 功能模块详解
### 1. 用户认证模块 (user_manager.py)
- 邮箱格式验证
- 密码强度校验6-10位包含大小写字母和数字
- 验证码发送与验证通过QQ邮箱SMTP服务
- 用户数据持久化JSON文件
- 用户会话管理
### 2. 题目生成模块 (question_generator.py)
- 小学题目2-5个数字的加减乘除运算支持括号确保结果非负
- 初中题目:平方运算、开根运算(完全平方数)
- 高中题目三角函数sin/cos/tan特殊角度0°, 30°, 45°, 60°, 90°计算
- 智能选项生成为每道题生成4个合理选项包含正确答案和干扰项
### 3. 测验模块 (quiz.py)
- 管理一次答题会话的所有题目
- 跟踪用户答题进度和答案
- 计算最终得分和正确率
- 提供题目导航功能
### 4. 界面模块 (main_app.py)
- 响应式图形界面设计
- 实时输入验证
- 友好的用户交互反馈
- 多框架界面切换
- 美观的UI主题和颜色方案
### 5. 后端服务模块 (backend_service.py)
- 作为前端与业务逻辑模块之间的通信中介
- 封装所有业务规则和逻辑判断
- 提供统一的API接口给前端调用
## 数据存储
• main分支稳定版本存放经过测试的代码
• develop分支开发主线集成最新功能
• 个人分支每位开发者的功能分支zhangsan_branch
代码提交规则
• 源代码:必须通过个人分支 + Pull Request
• 文档直接推送到develop分支
开发规范
代码规范
• 遵循PEP 8 Python编码规范
• 使用类型注解提高代码可读性
• 模块化设计,高内聚低耦合
提交信息规范
• feat: 新功能
• fix: 修复bug
• docs: 文档更新
• style: 代码格式调整
• refactor: 代码重构
功能模块详解
1. 用户认证模块 (user_manager.py)
• 邮箱格式验证
• 密码强度校验
• 验证码发送与验证
• 用户数据持久化JSON文件
2. 题目生成模块 (question_generator.py)
• 小学题目2-5个数字的加减乘除运算确保结果非负
• 初中题目平方运算1-12、开根运算完全平方数
• 高中题目三角函数sin/cos/tan特殊角度计算
• 选项生成智能生成4个合理选项包含正确答案
3. 界面模块 (main_app.py)
• 响应式图形界面设计
• 实时输入验证
• 友好的用户交互反馈
• 多框架界面切换
数据存储
项目使用JSON文件存储用户数据文件结构如下
```json
json
复制
下载
{
"user@example.com": {
"username": "张三",
@ -180,37 +105,28 @@ python main.py
"is_registered": true
}
}
```
## 配置说明
`user_manager.py` 中配置以下参数:
- 邮箱服务配置SMTP服务器、端口、授权码
- 用户数据文件路径
## 测试用例
### 功能测试
- 用户注册流程测试
- 登录验证测试
- 密码修改测试
- 题目生成测试(各学段)
- 答题评分测试
### 边界测试
- 密码格式边界测试
- 题目数量边界测试10-30题
- 邮箱格式验证测试
## 已知限制
- 邮箱服务依赖QQ邮箱SMTP服务需配置正确的授权码
- 题目数量建议10-30题过多可能影响性能
- 网络要求:发送验证码需要网络连接
- 平台兼容主要支持Windows其他平台可能需调整
## 开发团队
配置说明
在 user_manager.py 中配置以下参数:
• 邮箱服务配置SMTP服务器、端口、授权码
• 用户数据文件路径
测试用例
功能测试
• 用户注册流程测试
• 登录验证测试
• 密码修改测试
• 题目生成测试(各学段)
• 答题评分测试
边界测试
• 密码格式边界测试
• 题目数量边界测试
• 邮箱格式验证测试
已知限制
• 邮箱服务依赖QQ邮箱SMTP服务需配置正确的授权码
• 题目数量建议10-30题过多可能影响性能
• 网络要求:发送验证码需要网络连接
• 平台兼容主要支持Windows其他平台可能需调整
开发团队
• 班级软1
• 组长:[彭云昊]202326010111
• 组员:[王祖旺]202326010117
- 班级软1
- 组长:[彭云昊]202326010111
- 组员:[王祖旺]202326010117

@ -1,270 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
后端服务模块作为前端与后端之间的通信中介
"""
from typing import List, Optional, Dict, Any
from user_manager import UserManager
from question_bank import QuestionBank
from quiz import Quiz
class BackendService:
"""
后端服务类作为前端与实际业务逻辑模块之间的通信中介
"""
def __init__(self):
"""
初始化后端服务
"""
self.user_manager = UserManager()
self.question_bank = QuestionBank()
self.current_quiz: Optional[Quiz] = None
def login(self, username: str, password: str) -> bool:
"""
用户登录
@param username: 用户名
@param password: 密码
@return: 登录是否成功
"""
return self.user_manager.login(username, password)
def register_user(self, email: str, username: str) -> bool:
"""
注册新用户
@param email: 邮箱
@param username: 用户名
@return: 是否成功发送注册码
"""
return self.user_manager.register_user(email, username)
def verify_registration_code(self, email: str, code: str) -> bool:
"""
验证注册码
@param email: 邮箱
@param code: 注册码
@return: 验证是否成功
"""
return self.user_manager.verify_registration_code(email, code)
def set_password(self, email: str, password: str) -> bool:
"""
设置密码
@param email: 邮箱
@param password: 密码
@return: 是否设置成功
"""
return self.user_manager.set_password(email, password)
def change_password(self, old_password: str, new_password: str) -> bool:
"""
修改密码
@param old_password: 原密码
@param new_password: 新密码
@return: 是否修改成功
"""
return self.user_manager.change_password(old_password, new_password)
def logout(self):
"""
退出登录
"""
self.user_manager.logout()
def delete_account(self, email: str, password: str) -> bool:
"""
删除账户
@param email: 邮箱
@param password: 密码
@return: 是否删除成功
"""
return self.user_manager.delete_account(email, password)
def start_quiz(self, level: str, question_count: int) -> bool:
"""
开始测验
@param level: 题目难度等级
@param question_count: 题目数量
@return: 是否成功开始测验
"""
try:
questions = self.question_bank.generate_questions(level, question_count)
self.current_quiz = Quiz(questions)
return True
except Exception:
return False
def get_current_question(self):
"""
获取当前题目
@return: 当前题目
"""
if self.current_quiz:
return self.current_quiz.get_current_question()
return None
def get_current_options(self) -> List[str]:
"""
获取当前题目的选项
@return: 选项列表
"""
if self.current_quiz:
return self.current_quiz.get_current_options()
return []
def answer_question(self, answer: str) -> bool:
"""
回答当前题目
@param answer: 答案
@return: 是否回答成功
"""
if self.current_quiz:
try:
self.current_quiz.answer_question(answer)
return True
except Exception:
return False
return False
def next_question(self) -> bool:
"""
跳转到下一题
@return: 是否成功跳转
"""
if self.current_quiz:
return self.current_quiz.next_question()
return False
def previous_question(self) -> bool:
"""
跳转到上一题
@return: 是否成功跳转
"""
if self.current_quiz:
return self.current_quiz.previous_question()
return False
def is_quiz_finished(self) -> bool:
"""
检查测验是否已完成
@return: 测验是否已完成
"""
if self.current_quiz:
return self.current_quiz.is_finished()
return True
def calculate_score(self) -> float:
"""
计算测验得分
@return: 得分百分比
"""
if self.current_quiz:
return self.current_quiz.calculate_score()
return 0.0
def get_quiz_progress(self) -> Dict[str, Any]:
"""
获取测验进度信息
@return: 进度信息字典
"""
if self.current_quiz:
return {
"current_index": self.current_quiz.current_question_index,
"total_questions": len(self.current_quiz.questions),
"current_answer": self.current_quiz.answers[
self.current_quiz.current_question_index] if self.current_quiz.answers else None
}
return {
"current_index": 0,
"total_questions": 0,
"current_answer": None
}
def get_quiz_result_details(self) -> Dict[str, Any]:
"""
获取测验结果详情
@return: 结果详情字典
"""
if not self.current_quiz:
return {}
correct_count = 0
for q, a in zip(self.current_quiz.questions, self.current_quiz.answers):
if a is not None:
try:
# 将字符串答案转换为浮点数进行比较
if abs(q.answer - float(a)) < 1e-6:
correct_count += 1
except ValueError:
# 如果转换失败,说明答案无效,不计入正确答案
pass
total_count = len(self.current_quiz.questions)
return {
"correct_count": correct_count,
"total_count": total_count
}
def get_score_grade(self, score: float) -> Dict[str, str]:
"""
根据分数获取评分等级和评语
@param score: 分数
@return: 包含等级颜色和评语的字典
"""
if score >= 90:
return {
"grade": "优秀",
"comment": "优秀! 继续保持!",
"color": "success"
}
elif score >= 80:
return {
"grade": "良好",
"comment": "良好! 还可以做得更好!",
"color": "success"
}
elif score >= 60:
return {
"grade": "及格",
"comment": "及格了,需要继续努力!",
"color": "warning"
}
else:
return {
"grade": "不及格",
"comment": "需要加强练习哦!",
"color": "danger"
}
def get_current_username(self) -> str:
"""
获取当前登录用户的用户名
@return: 用户名
"""
if self.user_manager.current_user:
return self.user_manager.current_user.username
return "未知用户"

@ -1,3 +1,4 @@
# !/usr/bin/env python3
# -*- coding: utf-8 -*-
@ -7,11 +8,14 @@
import tkinter as tk
from tkinter import messagebox, ttk
import random
from typing import List, Optional
from backend_service import BackendService
from question_generator import Expression
from user_manager import UserManager
from question_bank import QuestionBank
from quiz import Quiz
from question_generator import Expression
class MathQuizApp:
"""
@ -31,7 +35,8 @@ class MathQuizApp:
self.root.configure(bg="#f0f0f0")
# 初始化系统组件
self.backend_service = BackendService()
self.user_manager = UserManager()
self.question_bank = QuestionBank()
# 设置样式
self.setup_styles()
@ -45,7 +50,7 @@ class MathQuizApp:
self.quiz_frame = tk.Frame(self.root, bg="#f0f0f0")
self.result_frame = tk.Frame(self.root, bg="#f0f0f0")
self.change_password_frame = tk.Frame(self.root, bg="#f0f0f0")
self.delete_account_frame = tk.Frame(self.root, bg="#f0f0f0")
self.delete_account_frame = tk.Frame(self.root, bg="#f0f0f0") # 添加删除账户框架
# 初始化应用状态
self.current_quiz: Optional[Quiz] = None
@ -88,7 +93,7 @@ class MathQuizApp:
self.quiz_frame = tk.Frame(self.root, bg="#f0f0f0")
self.result_frame = tk.Frame(self.root, bg="#f0f0f0")
self.change_password_frame = tk.Frame(self.root, bg="#f0f0f0")
self.delete_account_frame = tk.Frame(self.root, bg="#f0f0f0")
self.delete_account_frame = tk.Frame(self.root, bg="#f0f0f0") # 添加删除账户框架
def show_frame(self, frame: tk.Frame):
"""
@ -97,12 +102,9 @@ class MathQuizApp:
@param frame: 要显示的框架
"""
# 隐藏所有框架
for f in [self.login_frame, self.register_frame,
self.set_password_frame,
self.main_menu_frame, self.quiz_setup_frame,
self.quiz_frame,
self.result_frame, self.change_password_frame,
self.delete_account_frame]:
for f in [self.login_frame, self.register_frame, self.set_password_frame,
self.main_menu_frame, self.quiz_setup_frame, self.quiz_frame,
self.result_frame, self.change_password_frame, self.delete_account_frame]:
f.pack_forget()
# 显示指定框架
@ -117,53 +119,37 @@ class MathQuizApp:
widget.destroy()
# 创建登录界面
main_frame = tk.Frame(self.login_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
main_frame = tk.Frame(self.login_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=50, padx=50, fill="both", expand=True)
title_frame = tk.Frame(main_frame, bg=self.primary_color)
title_frame.pack(fill="x", pady=(0, 30))
tk.Label(title_frame, text="用户登录", font=self.title_font,
bg=self.primary_color, fg="white").pack(pady=20)
tk.Label(title_frame, text="用户登录", font=self.title_font, bg=self.primary_color, fg="white").pack(pady=20)
form_frame = tk.Frame(main_frame, bg="#ffffff")
form_frame.pack(pady=10)
tk.Label(form_frame, text="用户名:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.login_email_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
relief=tk.FLAT, bd=5,
tk.Label(form_frame, text="用户名:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.login_email_entry = tk.Entry(form_frame, width=30, font=self.normal_font, relief=tk.FLAT, bd=5,
bg="#f0f0f0")
self.login_email_entry.pack(pady=5)
tk.Label(form_frame, text="密码:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.login_password_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
show="*", relief=tk.FLAT,
tk.Label(form_frame, text="密码:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.login_password_entry = tk.Entry(form_frame, width=30, font=self.normal_font, show="*", relief=tk.FLAT,
bd=5, bg="#f0f0f0")
self.login_password_entry.pack(pady=5)
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(pady=20)
tk.Button(button_frame, text="登录", command=self.login, width=20,
bg=self.primary_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="注册新用户",
command=self.show_register_frame, width=20,
bg=self.secondary_color,
tk.Button(button_frame, text="登录", command=self.login, width=20, bg=self.primary_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="注册新用户", command=self.show_register_frame, width=20, bg=self.secondary_color,
fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=5)
tk.Button(button_frame, text="注销账户",
command=self.show_delete_account_frame, width=20,
bg=self.danger_color,
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
tk.Button(button_frame, text="注销账户", command=self.show_delete_account_frame, width=20, bg=self.danger_color,
fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=5)
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.login_frame)
@ -178,10 +164,10 @@ class MathQuizApp:
messagebox.showerror("错误", "请输入用户名和密码")
return
if self.backend_service.login(username, password):
if self.user_manager.login(username, password):
messagebox.showinfo("成功", "登录成功")
self.show_main_menu_frame()
# 错误信息在backend_service.login中已经显示
# 错误信息在user_manager.login中已经显示
def show_register_frame(self):
"""
@ -192,62 +178,44 @@ class MathQuizApp:
widget.destroy()
# 创建注册界面
main_frame = tk.Frame(self.register_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
main_frame = tk.Frame(self.register_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=30, padx=50, fill="both", expand=True)
title_frame = tk.Frame(main_frame, bg=self.primary_color)
title_frame.pack(fill="x", pady=(0, 20))
tk.Label(title_frame, text="用户注册", font=self.title_font,
bg=self.primary_color, fg="white").pack(pady=20)
tk.Label(title_frame, text="用户注册", font=self.title_font, bg=self.primary_color, fg="white").pack(pady=20)
form_frame = tk.Frame(main_frame, bg="#ffffff")
form_frame.pack(pady=10)
tk.Label(form_frame, text="用户名:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.register_username_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
relief=tk.FLAT, bd=5,
tk.Label(form_frame, text="用户名:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.register_username_entry = tk.Entry(form_frame, width=30, font=self.normal_font, relief=tk.FLAT, bd=5,
bg="#f0f0f0")
self.register_username_entry.pack(pady=5)
tk.Label(form_frame, text="邮箱:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.register_email_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
relief=tk.FLAT, bd=5,
tk.Label(form_frame, text="邮箱:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.register_email_entry = tk.Entry(form_frame, width=30, font=self.normal_font, relief=tk.FLAT, bd=5,
bg="#f0f0f0")
self.register_email_entry.pack(pady=5)
tk.Button(form_frame, text="获取注册码",
command=self.send_registration_code, width=20,
bg=self.primary_color,
tk.Button(form_frame, text="获取注册码", command=self.send_registration_code, width=20, bg=self.primary_color,
fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=10)
tk.Label(form_frame, text="注册码:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.registration_code_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
relief=tk.FLAT, bd=5,
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=10)
tk.Label(form_frame, text="注册码:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.registration_code_entry = tk.Entry(form_frame, width=30, font=self.normal_font, relief=tk.FLAT, bd=5,
bg="#f0f0f0")
self.registration_code_entry.pack(pady=5)
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(pady=20)
tk.Button(button_frame, text="验证注册码",
command=self.verify_registration_code, width=20,
tk.Button(button_frame, text="验证注册码", command=self.verify_registration_code, width=20,
bg=self.success_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="返回登录", command=self.show_login_frame,
width=20, bg="#cccccc",
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="返回登录", command=self.show_login_frame, width=20, bg="#cccccc",
fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=5)
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.register_frame)
@ -263,7 +231,7 @@ class MathQuizApp:
return
# 注意register_user 方法内部已经包含了消息提示,不需要额外的消息框
self.backend_service.register_user(email, username)
self.user_manager.register_user(email, username)
def verify_registration_code(self):
"""
@ -276,7 +244,7 @@ class MathQuizApp:
messagebox.showerror("错误", "请输入邮箱和注册码")
return
if self.backend_service.verify_registration_code(email, code):
if self.user_manager.verify_registration_code(email, code):
messagebox.showinfo("成功", "注册码验证成功,请设置密码")
self.show_set_password_frame(email)
@ -291,54 +259,40 @@ class MathQuizApp:
widget.destroy()
# 创建设置密码界面
main_frame = tk.Frame(self.set_password_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
main_frame = tk.Frame(self.set_password_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=30, padx=50, fill="both", expand=True)
title_frame = tk.Frame(main_frame, bg=self.primary_color)
title_frame.pack(fill="x", pady=(0, 20))
tk.Label(title_frame, text="设置密码", font=self.title_font,
bg=self.primary_color, fg="white").pack(pady=20)
tk.Label(title_frame, text="设置密码", font=self.title_font, bg=self.primary_color, fg="white").pack(pady=20)
form_frame = tk.Frame(main_frame, bg="#ffffff")
form_frame.pack(pady=10)
tk.Label(form_frame, text="邮箱: " + email, font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
tk.Label(form_frame, text="邮箱: " + email, font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
tk.Label(form_frame, text="密码:", font=self.normal_font,
bg="#ffffff", fg=self.primary_color).pack(
tk.Label(form_frame, text="密码:", font=self.normal_font, bg="#ffffff", fg=self.primary_color).pack(
pady=(10, 5), anchor="w")
tk.Label(form_frame, text="6-10位必须包含大小写字母和数字",
font=("Arial", 10), bg="#ffffff",
tk.Label(form_frame, text="6-10位必须包含大小写字母和数字", font=("Arial", 10), bg="#ffffff",
fg="gray").pack(pady=5, anchor="w")
self.set_password_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
show="*", relief=tk.FLAT, bd=5,
self.set_password_entry = tk.Entry(form_frame, width=30, font=self.normal_font, show="*", relief=tk.FLAT, bd=5,
bg="#f0f0f0")
self.set_password_entry.pack(pady=5)
tk.Label(form_frame, text="确认密码:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.confirm_password_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
show="*", relief=tk.FLAT,
tk.Label(form_frame, text="确认密码:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.confirm_password_entry = tk.Entry(form_frame, width=30, font=self.normal_font, show="*", relief=tk.FLAT,
bd=5, bg="#f0f0f0")
self.confirm_password_entry.pack(pady=5)
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(pady=20)
tk.Button(button_frame, text="设置密码",
command=lambda: self.set_password(email), width=20,
tk.Button(button_frame, text="设置密码", command=lambda: self.set_password(email), width=20,
bg=self.success_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="返回登录", command=self.show_login_frame,
width=20, bg="#cccccc",
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="返回登录", command=self.show_login_frame, width=20, bg="#cccccc",
fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=5)
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.set_password_frame)
@ -359,7 +313,8 @@ class MathQuizApp:
messagebox.showerror("错误", "两次输入的密码不一致")
return
if self.backend_service.set_password(email, password):
if self.user_manager.set_password(email, password):
messagebox.showinfo("成功", "密码设置成功,请登录")
self.show_login_frame()
def show_main_menu_frame(self):
@ -371,47 +326,34 @@ class MathQuizApp:
widget.destroy()
# 创建主菜单界面
main_frame = tk.Frame(self.main_menu_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
main_frame = tk.Frame(self.main_menu_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=30, padx=50, fill="both", expand=True)
title_frame = tk.Frame(main_frame, bg=self.primary_color)
title_frame.pack(fill="x", pady=(0, 20))
tk.Label(title_frame, text="主菜单", font=self.title_font,
bg=self.primary_color, fg="white").pack(pady=20)
tk.Label(title_frame, text="主菜单", font=self.title_font, bg=self.primary_color, fg="white").pack(pady=20)
username = self.backend_service.get_current_username()
tk.Label(main_frame, text=f"欢迎, {username}!",
font=self.header_font, bg="#ffffff").pack(pady=10)
user_email = self.user_manager.current_user.email if self.user_manager.current_user else "未知用户"
tk.Label(main_frame, text=f"欢迎, {user_email}!", font=self.header_font, bg="#ffffff").pack(pady=10)
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(pady=20)
tk.Button(button_frame, text="小学题目",
command=lambda: self.show_quiz_setup_frame("elementary"),
width=20, height=2, bg="#4CAF50", fg="white",
font=self.button_font, relief=tk.FLAT, bd=0).pack(
tk.Button(button_frame, text="小学题目", command=lambda: self.show_quiz_setup_frame("elementary"),
width=20, height=2, bg="#4CAF50", fg="white", font=self.button_font, relief=tk.FLAT, bd=0).pack(
pady=10)
tk.Button(button_frame, text="初中题目",
command=lambda: self.show_quiz_setup_frame("middle"),
width=20, height=2, bg="#2196F3", fg="white",
font=self.button_font, relief=tk.FLAT, bd=0).pack(
tk.Button(button_frame, text="初中题目", command=lambda: self.show_quiz_setup_frame("middle"),
width=20, height=2, bg="#2196F3", fg="white", font=self.button_font, relief=tk.FLAT, bd=0).pack(
pady=10)
tk.Button(button_frame, text="高中题目",
command=lambda: self.show_quiz_setup_frame("high"),
width=20, height=2, bg="#FF9800", fg="white",
font=self.button_font, relief=tk.FLAT, bd=0).pack(
tk.Button(button_frame, text="高中题目", command=lambda: self.show_quiz_setup_frame("high"),
width=20, height=2, bg="#FF9800", fg="white", font=self.button_font, relief=tk.FLAT, bd=0).pack(
pady=10)
tk.Button(button_frame, text="修改密码",
command=self.show_change_password_frame,
width=20, bg=self.secondary_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
tk.Button(button_frame, text="修改密码", command=self.show_change_password_frame,
width=20, bg=self.secondary_color, fg="white", font=self.button_font, relief=tk.FLAT, bd=0,
pady=5).pack(pady=10)
tk.Button(button_frame, text="退出登录", command=self.logout,
width=20, bg=self.danger_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
pady=5).pack(pady=5)
tk.Button(button_frame, text="退出登录", command=self.logout, width=20, bg=self.danger_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0, pady=5).pack(pady=5)
self.show_frame(self.main_menu_frame)
@ -428,45 +370,34 @@ class MathQuizApp:
widget.destroy()
# 创建测验设置界面
main_frame = tk.Frame(self.quiz_setup_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
main_frame = tk.Frame(self.quiz_setup_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=30, padx=50, fill="both", expand=True)
level_names = {"elementary": "小学", "middle": "初中", "high": "高中"}
level_colors = {"elementary": "#4CAF50", "middle": "#2196F3",
"high": "#FF9800"}
level_colors = {"elementary": "#4CAF50", "middle": "#2196F3", "high": "#FF9800"}
title_frame = tk.Frame(main_frame, bg=level_colors[level])
title_frame.pack(fill="x", pady=(0, 20))
tk.Label(title_frame, text=f"{level_names[level]}数学题目",
font=self.title_font, bg=level_colors[level],
tk.Label(title_frame, text=f"{level_names[level]}数学题目", font=self.title_font, bg=level_colors[level],
fg="white").pack(pady=20)
form_frame = tk.Frame(main_frame, bg="#ffffff")
form_frame.pack(pady=20)
tk.Label(form_frame, text="请输入题目数量 (10-30):",
font=self.normal_font, bg="#ffffff").pack(pady=10)
self.question_count_entry = tk.Entry(form_frame, width=20,
font=self.normal_font,
justify="center",
relief=tk.FLAT, bd=5,
bg="#f0f0f0")
tk.Label(form_frame, text="请输入题目数量 (10-30):", font=self.normal_font, bg="#ffffff").pack(pady=10)
self.question_count_entry = tk.Entry(form_frame, width=20, font=self.normal_font, justify="center",
relief=tk.FLAT, bd=5, bg="#f0f0f0")
self.question_count_entry.pack(pady=5)
self.question_count_entry.insert(0, "10") # 默认10题
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(pady=20)
tk.Button(button_frame, text="开始答题", command=self.start_quiz,
width=20, bg=self.success_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="返回主菜单",
command=self.show_main_menu_frame, width=20, bg="#cccccc",
tk.Button(button_frame, text="开始答题", command=self.start_quiz, width=20, bg=self.success_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="返回主菜单", command=self.show_main_menu_frame, width=20, bg="#cccccc",
fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=5)
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.quiz_setup_frame)
@ -486,17 +417,18 @@ class MathQuizApp:
self.question_count = count
# 生成题目
if self.backend_service.start_quiz(self.current_level, self.question_count):
try:
questions = self.question_bank.generate_questions(self.current_level, self.question_count)
self.current_quiz = Quiz(questions)
self.show_quiz_frame()
else:
messagebox.showerror("错误", "生成题目时出错")
except Exception as e:
messagebox.showerror("错误", f"生成题目时出错: {str(e)}")
def show_quiz_frame(self):
"""
显示答题界面
"""
current_question = self.backend_service.get_current_question()
if not current_question:
if not self.current_quiz:
return
# 清除之前的内容
@ -504,40 +436,40 @@ class MathQuizApp:
widget.destroy()
# 创建答题界面
main_frame = tk.Frame(self.quiz_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
current_question = self.current_quiz.get_current_question()
if not current_question:
return
main_frame = tk.Frame(self.quiz_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=20, padx=30, fill="both", expand=True)
# 显示题目进度
progress_info = self.backend_service.get_quiz_progress()
progress = f"题目 {progress_info['current_index'] + 1}/{progress_info['total_questions']}"
progress = f"题目 {self.current_quiz.current_question_index + 1}/{len(self.current_quiz.questions)}"
progress_frame = tk.Frame(main_frame, bg=self.primary_color)
progress_frame.pack(fill="x", pady=(0, 20))
tk.Label(progress_frame, text=progress, font=self.header_font,
bg=self.primary_color, fg="white").pack(pady=10)
tk.Label(progress_frame, text=progress, font=self.header_font, bg=self.primary_color, fg="white").pack(pady=10)
# 显示题目
question_frame = tk.Frame(main_frame, bg="#ffffff")
question_frame.pack(pady=10)
tk.Label(question_frame, text="题目:", font=self.header_font,
bg="#ffffff").pack(pady=(10, 5))
tk.Label(question_frame, text="题目:", font=self.header_font, bg="#ffffff").pack(pady=(10, 5))
question_text = str(current_question)
tk.Label(question_frame, text=question_text,
font=("Arial", 16, "bold"), bg="#ffffff",
wraplength=500).pack(
tk.Label(question_frame, text=question_text, font=("Arial", 16, "bold"), bg="#ffffff", wraplength=500).pack(
pady=10)
# 获取固定选项
options = self.backend_service.get_current_options()
options = self.current_quiz.get_current_options()
# 显示选项
tk.Label(main_frame, text="请选择答案:", font=self.normal_font,
bg="#ffffff").pack(pady=(20, 10))
tk.Label(main_frame, text="请选择答案:", font=self.normal_font, bg="#ffffff").pack(pady=(20, 10))
self.answer_var = tk.StringVar()
# 设置默认选项为用户之前的选择(如果有)
current_answer = progress_info['current_answer']
current_answer = self.current_quiz.answers[self.current_quiz.current_question_index]
# print(current_answer)
# print(self.current_quiz.current_question_index)
# print(self.current_quiz.answers[:5]) 调试功能
if current_answer is not None:
self.answer_var.set(current_answer)
else:
@ -547,50 +479,84 @@ class MathQuizApp:
options_frame.pack(pady=10)
for i, option in enumerate(options):
tk.Radiobutton(options_frame,
text=f"{['A', 'B', 'C', 'D'][i]}. {option}",
variable=self.answer_var, value=option,
font=self.normal_font,
bg="#ffffff", selectcolor="#e0e0e0",
activebackground="#f0f0f0").pack(
anchor="w", padx=50, pady=5)
tk.Radiobutton(options_frame, text=f"{['A', 'B', 'C', 'D'][i]}. {option}",
variable=self.answer_var, value=option, font=self.normal_font,
bg="#ffffff", selectcolor="#e0e0e0", activebackground="#f0f0f0").pack(anchor="w", padx=50,
pady=5)
# 按钮框架
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(pady=20)
if progress_info['current_index'] > 0:
tk.Button(button_frame, text="上一题",
command=self.previous_question, bg="#cccccc",
fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(side="left", padx=10)
if self.current_quiz.current_question_index > 0:
tk.Button(button_frame, text="上一题", command=self.previous_question, bg="#cccccc", fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(side="left", padx=10)
tk.Button(button_frame, text="提交答案", command=self.submit_answer,
bg=self.success_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(side="left", padx=10)
if progress_info['current_index'] < progress_info['total_questions'] - 1:
tk.Button(button_frame, text="下一题",
command=self.next_question, bg=self.primary_color,
fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(side="left", padx=10)
tk.Button(main_frame, text="返回主菜单",
command=self.show_main_menu_frame, width=15,
bg=self.danger_color,
tk.Button(button_frame, text="提交答案", command=self.submit_answer, bg=self.success_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(side="left", padx=10)
if self.current_quiz.current_question_index < len(self.current_quiz.questions) - 1:
tk.Button(button_frame, text="下一题", command=self.next_question, bg=self.primary_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(side="left", padx=10)
tk.Button(main_frame, text="返回主菜单", command=self.show_main_menu_frame, width=15, bg=self.danger_color,
fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=10)
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=10)
self.show_frame(self.quiz_frame)
def generate_options(self, correct_answer) -> List[float]:
"""
为题目生成选项
@param correct_answer: 正确答案
@return: 选项列表
"""
# 生成4个选项其中一个是正确答案
options = {correct_answer}
# 添加一些干扰项
if isinstance(correct_answer, int):
while len(options) < 4:
# 生成不同长度的干扰项,避免正确答案总是最长的
if random.random() < 0.5:
# 生成1-2位数的干扰项
options.add(random.randint(0, 99))
else:
# 生成2-3位数的干扰项
options.add(random.randint(10, 999))
else:
# 浮点数情况
while len(options) < 4:
# 随机生成不同长度的浮点数选项
if random.random() < 0.33:
# 生成1位小数的数
options.add(round(random.uniform(0, 100), 1))
elif random.random() < 0.66:
# 生成2位小数的数
options.add(round(random.uniform(0, 100), 2))
else:
# 生成整数
options.add(random.randint(0, 100))
# 如果选项不足4个补充一些随机数
while len(options) < 4:
if random.random() < 0.5:
options.add(random.randint(0, 100))
else:
options.add(round(random.uniform(0, 100), random.choice([0, 1, 2])))
options_list = list(options)
random.shuffle(options_list)
return options_list[:4]
def submit_answer(self):
"""
提交答案
"""
if not self.current_quiz:
return
answer_str = self.answer_var.get()
if not answer_str: # 检查字符串是否为空
messagebox.showerror("错误", "请选择一个答案")
@ -599,12 +565,10 @@ class MathQuizApp:
try:
# 验证答案是否为有效数字
float(answer_str)
if not self.backend_service.answer_question(answer_str):
messagebox.showerror("错误", "提交答案失败")
return
self.current_quiz.answer_question(answer_str)
# 如果是最后一题,显示结果
if self.backend_service.is_quiz_finished():
if self.current_quiz.is_finished():
self.show_result_frame()
else:
messagebox.showinfo("提示", "答案已提交")
@ -615,22 +579,24 @@ class MathQuizApp:
"""
下一题
"""
if not self.current_quiz:
return
# 保存当前答案(如果有选择)
answer_str = self.answer_var.get()
if answer_str: # 检查字符串是否非空
try:
# 验证答案是否为有效数字
float(answer_str)
self.backend_service.answer_question(answer_str)
self.current_quiz.answer_question(answer_str)
except ValueError:
pass # 如果答案无效保持为None
if self.backend_service.next_question():
if self.current_quiz.next_question():
self.show_quiz_frame()
else:
# 已经是最后一题
progress_info = self.backend_service.get_quiz_progress()
if progress_info['current_answer'] is not None:
if self.current_quiz.answers[self.current_quiz.current_question_index] is not None:
# 如果最后一题已答题,显示结果
self.show_result_frame()
else:
@ -640,66 +606,83 @@ class MathQuizApp:
"""
上一题
"""
if not self.current_quiz:
return
# 保存当前答案(如果有选择)
answer_str = self.answer_var.get()
if answer_str: # 检查字符串是否非空
try:
# 验证答案是否为有效数字
float(answer_str)
self.backend_service.answer_question(answer_str)
self.current_quiz.answer_question(answer_str)
except ValueError:
pass # 如果答案无效保持为None
if self.backend_service.previous_question():
if self.current_quiz.previous_question():
self.show_quiz_frame()
def show_result_frame(self):
"""
显示结果界面
"""
if not self.current_quiz:
return
# 清除之前的内容
for widget in self.result_frame.winfo_children():
widget.destroy()
# 计算得分
score = self.backend_service.calculate_score()
grade_info = self.backend_service.get_score_grade(score)
score = self.current_quiz.calculate_score()
# 创建结果界面
main_frame = tk.Frame(self.result_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
main_frame = tk.Frame(self.result_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=30, padx=50, fill="both", expand=True)
title_frame = tk.Frame(main_frame, bg=self.primary_color)
title_frame.pack(fill="x", pady=(0, 20))
tk.Label(title_frame, text="测验结果", font=self.title_font,
bg=self.primary_color, fg="white").pack(pady=20)
tk.Label(title_frame, text="测验结果", font=self.title_font, bg=self.primary_color, fg="white").pack(pady=20)
result_frame = tk.Frame(main_frame, bg="#ffffff")
result_frame.pack(pady=20)
# 根据后端返回的颜色标识获取对应颜色
color_map = {
"success": self.success_color,
"warning": self.warning_color,
"danger": self.danger_color
}
score_color = color_map.get(grade_info["color"], self.dark_text)
# 根据得分显示不同颜色的分数
score_color = self.danger_color if score < 60 else self.warning_color if score < 80 else self.success_color
tk.Label(result_frame, text=f"您的得分: {score:.1f}%",
font=("Arial", 20, "bold"), fg=score_color,
tk.Label(result_frame, text=f"您的得分: {score:.1f}%", font=("Arial", 20, "bold"), fg=score_color,
bg="#ffffff").pack(pady=10)
tk.Label(result_frame, text=grade_info["comment"], font=self.header_font,
fg=score_color, bg="#ffffff").pack(pady=10)
# 根据得分显示评语
if score >= 90:
comment = "优秀! 继续保持!"
comment_color = self.success_color
elif score >= 80:
comment = "良好! 还可以做得更好!"
comment_color = self.success_color
elif score >= 60:
comment = "及格了,需要继续努力!"
comment_color = self.warning_color
else:
comment = "需要加强练习哦!"
comment_color = self.danger_color
tk.Label(result_frame, text=comment, font=self.header_font, fg=comment_color, bg="#ffffff").pack(pady=10)
# 显示答题详情
result_details = self.backend_service.get_quiz_result_details()
correct_count = result_details.get("correct_count", 0)
total_count = result_details.get("total_count", 0)
correct_count = 0
for q, a in zip(self.current_quiz.questions, self.current_quiz.answers):
if a is not None:
try:
# 将字符串答案转换为浮点数进行比较
if abs(q.answer - float(a)) < 1e-6:
correct_count += 1
except ValueError:
# 如果转换失败,说明答案无效,不计入正确答案
pass
tk.Label(result_frame, text=f"答对: {correct_count}/{total_count}",
font=self.normal_font, bg="#ffffff").pack(
total_count = len(self.current_quiz.questions)
tk.Label(result_frame, text=f"答对: {correct_count}/{total_count}", font=self.normal_font, bg="#ffffff").pack(
pady=5)
# 按钮
@ -707,19 +690,14 @@ class MathQuizApp:
button_frame.pack(pady=30)
level_names = {"elementary": "小学", "middle": "初中", "high": "高中"}
tk.Button(button_frame,
text=f"继续{level_names[self.current_level]}题目",
command=lambda: self.show_quiz_setup_frame(
self.current_level), width=20, bg=self.primary_color,
tk.Button(button_frame, text=f"继续{level_names[self.current_level]}题目",
command=lambda: self.show_quiz_setup_frame(self.current_level), width=20, bg=self.primary_color,
fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(side="left", padx=10)
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(side="left", padx=10)
tk.Button(button_frame, text="返回主菜单",
command=self.show_main_menu_frame, width=15, bg="#cccccc",
tk.Button(button_frame, text="返回主菜单", command=self.show_main_menu_frame, width=15, bg="#cccccc",
fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(side="left", padx=10)
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(side="left", padx=10)
self.show_frame(self.result_frame)
@ -732,60 +710,43 @@ class MathQuizApp:
widget.destroy()
# 创建修改密码界面
main_frame = tk.Frame(self.change_password_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
main_frame = tk.Frame(self.change_password_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=30, padx=50, fill="both", expand=True)
title_frame = tk.Frame(main_frame, bg=self.primary_color)
title_frame.pack(fill="x", pady=(0, 20))
tk.Label(title_frame, text="修改密码", font=self.title_font,
bg=self.primary_color, fg="white").pack(pady=20)
tk.Label(title_frame, text="修改密码", font=self.title_font, bg=self.primary_color, fg="white").pack(pady=20)
form_frame = tk.Frame(main_frame, bg="#ffffff")
form_frame.pack(pady=10)
tk.Label(form_frame, text="原密码:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.old_password_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
show="*", relief=tk.FLAT, bd=5,
tk.Label(form_frame, text="原密码:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.old_password_entry = tk.Entry(form_frame, width=30, font=self.normal_font, show="*", relief=tk.FLAT, bd=5,
bg="#f0f0f0")
self.old_password_entry.pack(pady=5)
tk.Label(form_frame, text="新密码:", font=self.normal_font,
bg="#ffffff", fg=self.primary_color).pack(
tk.Label(form_frame, text="新密码:", font=self.normal_font, bg="#ffffff", fg=self.primary_color).pack(
pady=(10, 5), anchor="w")
tk.Label(form_frame, text="6-10位必须包含大小写字母和数字",
font=("Arial", 10), bg="#ffffff",
tk.Label(form_frame, text="6-10位必须包含大小写字母和数字", font=("Arial", 10), bg="#ffffff",
fg="gray").pack(pady=5, anchor="w")
self.new_password_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
show="*", relief=tk.FLAT, bd=5,
self.new_password_entry = tk.Entry(form_frame, width=30, font=self.normal_font, show="*", relief=tk.FLAT, bd=5,
bg="#f0f0f0")
self.new_password_entry.pack(pady=5)
tk.Label(form_frame, text="确认新密码:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.confirm_new_password_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
show="*",
relief=tk.FLAT, bd=5,
bg="#f0f0f0")
tk.Label(form_frame, text="确认新密码:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.confirm_new_password_entry = tk.Entry(form_frame, width=30, font=self.normal_font, show="*",
relief=tk.FLAT, bd=5, bg="#f0f0f0")
self.confirm_new_password_entry.pack(pady=5)
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(pady=20)
tk.Button(button_frame, text="修改密码", command=self.change_password,
width=20, bg=self.success_color,
tk.Button(button_frame, text="修改密码", command=self.change_password, width=20, bg=self.success_color,
fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="返回主菜单",
command=self.show_main_menu_frame, width=20, bg="#cccccc",
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=10)
tk.Button(button_frame, text="返回主菜单", command=self.show_main_menu_frame, width=20, bg="#cccccc",
fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10, pady=5).pack(pady=5)
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.change_password_frame)
@ -805,16 +766,16 @@ class MathQuizApp:
messagebox.showerror("错误", "新密码和确认密码不一致")
return
if self.backend_service.change_password(old_password, new_password):
if self.user_manager.change_password(old_password, new_password):
messagebox.showinfo("成功", "密码修改成功")
self.show_main_menu_frame()
# 错误信息在backend_service.change_password中已经显示
# 错误信息在user_manager.change_password中已经显示
def logout(self):
"""
退出登录
"""
self.backend_service.logout()
self.user_manager.logout()
self.show_login_frame()
def show_delete_account_frame(self):
@ -826,42 +787,31 @@ class MathQuizApp:
widget.destroy()
# 创建注销账户界面
main_frame = tk.Frame(self.delete_account_frame, bg="#ffffff",
relief=tk.RAISED, bd=2)
main_frame = tk.Frame(self.delete_account_frame, bg="#ffffff", relief=tk.RAISED, bd=2)
main_frame.pack(pady=30, padx=50, fill="both", expand=True)
title_frame = tk.Frame(main_frame, bg=self.danger_color)
title_frame.pack(fill="x", pady=(0, 20))
tk.Label(title_frame, text="注销账户", font=self.title_font,
bg=self.danger_color, fg="white").pack(pady=20)
tk.Label(title_frame, text="注销账户", font=self.title_font, bg=self.danger_color, fg="white").pack(pady=20)
warning_frame = tk.Frame(main_frame, bg="#ffffff")
warning_frame.pack(pady=10)
tk.Label(warning_frame,
text="警告:此操作将永久删除您的账户和所有数据!",
font=self.normal_font,
tk.Label(warning_frame, text="警告:此操作将永久删除您的账户和所有数据!", font=self.normal_font,
fg=self.danger_color, bg="#ffffff").pack(pady=5)
tk.Label(warning_frame, text="请确认您的邮箱和密码:",
font=self.normal_font, fg=self.danger_color,
tk.Label(warning_frame, text="请确认您的邮箱和密码:", font=self.normal_font, fg=self.danger_color,
bg="#ffffff").pack(pady=5)
form_frame = tk.Frame(main_frame, bg="#ffffff")
form_frame.pack(pady=10)
tk.Label(form_frame, text="邮箱:", font=self.normal_font,
bg="#ffffff").pack(pady=10, anchor="w")
self.delete_email_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
relief=tk.FLAT, bd=5,
tk.Label(form_frame, text="邮箱:", font=self.normal_font, bg="#ffffff").pack(pady=10, anchor="w")
self.delete_email_entry = tk.Entry(form_frame, width=30, font=self.normal_font, relief=tk.FLAT, bd=5,
bg="#f0f0f0")
self.delete_email_entry.pack(pady=5)
tk.Label(form_frame, text="密码:", font=self.normal_font,
bg="#ffffff").pack(pady=5, anchor="w")
self.delete_password_entry = tk.Entry(form_frame, width=30,
font=self.normal_font,
show="*", relief=tk.FLAT,
tk.Label(form_frame, text="密码:", font=self.normal_font, bg="#ffffff").pack(pady=5, anchor="w")
self.delete_password_entry = tk.Entry(form_frame, width=30, font=self.normal_font, show="*", relief=tk.FLAT,
bd=5, bg="#f0f0f0")
self.delete_password_entry.pack(pady=5)
@ -869,16 +819,11 @@ class MathQuizApp:
button_frame = tk.Frame(main_frame, bg="#ffffff")
button_frame.pack(pady=20)
tk.Button(button_frame, text="确认注销",
command=self.confirm_delete_account,
width=15, bg=self.danger_color, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10,
tk.Button(button_frame, text="确认注销", command=self.confirm_delete_account,
width=15, bg=self.danger_color, fg="white", font=self.button_font, relief=tk.FLAT, bd=0, padx=10,
pady=5).pack(side="left", padx=10)
tk.Button(button_frame, text="取消", command=self.show_login_frame,
width=15, bg="#cccccc", fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0,
padx=10,
width=15, bg="#cccccc", fg=self.dark_text, font=self.button_font, relief=tk.FLAT, bd=0, padx=10,
pady=5).pack(side="left", padx=10)
self.show_frame(self.delete_account_frame)
@ -896,14 +841,15 @@ class MathQuizApp:
# 确认操作
if messagebox.askyesno("确认注销", "确定要注销账户吗?此操作无法撤销!"):
if self.backend_service.delete_account(email, password):
messagebox.showinfo("成功",
"账户已注销,您可以使用该邮箱重新注册")
if self.user_manager.delete_account(email, password):
messagebox.showinfo("成功", "账户已注销,您可以使用该邮箱重新注册")
self.show_login_frame()
# 错误信息在backend_service.delete_account中已经显示
# 错误信息在user_manager.delete_account中已经显示
def delete_account(self):
"""
注销账户从主菜单调用的旧方法保持兼容性
"""
self.show_delete_account_frame()
self.show_delete_account_frame()

@ -14,7 +14,7 @@ from typing import List, Set, Optional
class Expression:
"""
数学表达式类辅助类
数学表达式类
"""
def __init__(self, expression_str: str, answer: float):
@ -97,16 +97,42 @@ class ElementaryQuestionGenerator(QuestionGenerator):
"""
max_attempts = 100
for _ in range(max_attempts):
expression_str = self._generate_elementary_expression()
# 随机生成操作数数量2-5个
num_operands = random.randint(2, 5)
operands = [random.randint(1, 50) for _ in range(num_operands)]
operators = [random.choice(['+', '-', '*']) if random.random() < 0.8 else '/'
for _ in range(num_operands - 1)] # 减少减法和除法概率以避免负数
# 随机添加括号
expression_parts = []
for i in range(num_operands):
expression_parts.append(str(operands[i]))
if i < len(operators):
expression_parts.append(operators[i])
# 随机添加括号
if num_operands >= 3 and random.random() < 0.3:
# 在随机位置添加括号
open_pos = random.randint(0, len(expression_parts) - 3)
# 确保括号内至少有两个操作数
close_pos = min(open_pos + 2 + random.randint(1, 2) * 2, len(expression_parts))
# 确保括号位置是操作数位置
if open_pos % 2 == 0 and close_pos % 2 == 0:
expression_parts.insert(open_pos, '(')
expression_parts.insert(close_pos + 1, ')')
expression_str = ''.join(expression_parts)
# 验证表达式是否有效
try:
# 替换除法符号以便计算
eval_expr = expression_str.replace('/', '/').replace('*', '*')
result = eval(eval_expr)
# 确保结果是非负数且是合理的(整数或有限小数)
if (isinstance(result, (int, float)) and
result >= 0 and
abs(result) < 1000):
if isinstance(result, (int, float)) and result >= 0 and abs(result) < 1000:
# 格式化结果,保留合适的小数位数
if isinstance(result, float) and result.is_integer():
result = int(result)
@ -115,8 +141,7 @@ class ElementaryQuestionGenerator(QuestionGenerator):
if str(expr) not in self.generated_questions:
self.generated_questions.add(str(expr))
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = expression_str.replace('*', '×')
readable_expr_str = readable_expr_str.replace('/', '÷')
readable_expr_str = expression_str.replace('*', '×').replace('/', '÷')
readable_expr = Expression(readable_expr_str, result)
return readable_expr
except:
@ -128,65 +153,6 @@ class ElementaryQuestionGenerator(QuestionGenerator):
readable_expr = Expression("1+1", 2)
return readable_expr
def _generate_elementary_expression(self) -> str:
"""
生成小学题目表达式字符串
@return: 表达式字符串
"""
# 随机生成操作数数量2-5个
num_operands = random.randint(2, 5)
# 修改操作数范围从1-50到1-100
operands = [random.randint(1, 100) for _ in range(num_operands)]
operators = [random.choice(['+', '-', '*']) if random.random() < 0.8 else '/'
for _ in range(num_operands - 1)] # 减少减法和除法概率以避免负数
# 构建表达式部分
expression_parts = self._build_expression_parts(operands, operators)
# 随机添加括号
expression_parts = self._add_parentheses(expression_parts, num_operands)
return ''.join(expression_parts)
def _build_expression_parts(self, operands: List[int],
operators: List[str]) -> List[str]:
"""
构建表达式部分
@param operands: 操作数列表
@param operators: 操作符列表
@return: 表达式部分列表
"""
expression_parts = []
for i in range(len(operands)):
expression_parts.append(str(operands[i]))
if i < len(operators):
expression_parts.append(operators[i])
return expression_parts
def _add_parentheses(self, expression_parts: List[str],
num_operands: int) -> List[str]:
"""
随机添加括号到表达式
@param expression_parts: 表达式部分列表
@param num_operands: 操作数数量
@return: 添加括号后的表达式部分列表
"""
if num_operands >= 3 and random.random() < 0.3:
# 在随机位置添加括号
open_pos = random.randint(0, len(expression_parts) - 3)
# 确保括号内至少有两个操作数
close_pos = min(open_pos + 2 + random.randint(1, 2) * 2,
len(expression_parts))
# 确保括号位置是操作数位置
if open_pos % 2 == 0 and close_pos % 2 == 0:
expression_parts.insert(open_pos, '(')
expression_parts.insert(close_pos + 1, ')')
return expression_parts
class MiddleQuestionGenerator(QuestionGenerator):
"""
@ -201,161 +167,93 @@ class MiddleQuestionGenerator(QuestionGenerator):
"""
max_attempts = 100
for _ in range(max_attempts):
expression_str = self._generate_middle_expression()
expression_parts = []
# 确保至少有一个平方或开根号
has_square_or_sqrt = True # 确保至少有一个平方或开根号
# 生成操作数数量2-5个
num_operands = random.randint(2, 5)
# 标记是否已添加特殊操作
special_added = False
for i in range(num_operands):
# 确保至少添加一个平方或开根号
if not special_added and i == num_operands - 1:
# 如果还没添加特殊操作,强制在最后一个操作数添加
choice = random.choice([0, 1]) # 0为平方1为开根号
if choice == 0:
# 添加平方
base = random.randint(1, 12)
expression_parts.append(f"{base}²")
else:
# 添加开根号(确保至少有一个开根号)
square = random.randint(1, 12)
value = square * square
expression_parts.append(f"{value}")
special_added = True
else:
rand_val = random.random()
# 修改这里的概率,增加开根号和平方的出现频率
if not special_added and rand_val < 0.7: # 提高特殊操作概率从0.6到0.7
# 添加特殊操作(平方或开根号)
choice = random.choice([0, 1]) # 平方和开根号的概率相等
if choice == 0:
# 添加平方
base = random.randint(1, 12)
expression_parts.append(f"{base}²")
else:
# 添加开根号
square = random.randint(1, 12)
value = square * square
expression_parts.append(f"{value}")
special_added = True
else:
# 普通操作数
expression_parts.append(str(random.randint(1, 50)))
# 添加运算符(除了最后一个操作数)
if i < num_operands - 1:
expression_parts.append(random.choice(['+', '-', '*', '/']))
expression_str = ''.join(expression_parts)
# 处理可能的语法问题
expression_str = self.fix_expression_syntax(expression_str)
# 计算结果
try:
# 替换表达式中的函数以便计算
eval_expr = self._prepare_middle_expression_for_eval(expression_str)
eval_expr = expression_str.replace('²', '**2').replace('', 'math.sqrt')
result = eval(eval_expr)
# 确保结果是合理的
if (isinstance(result, (int, float)) and
abs(result) < 1000 and
not math.isnan(result)):
if isinstance(result, (int, float)) and abs(result) < 1000 and not math.isnan(result):
# 格式化结果
if isinstance(result, float) and abs(result - round(result)) < 1e-10:
result = int(round(result))
expr = Expression(expression_str, result)
# 检查是否已生成过相同题目
if str(expr) not in self.generated_questions:
self.generated_questions.add(str(expr))
readable_expr_str = expression_str.replace('*', '×')
readable_expr_str = readable_expr_str.replace('/', '÷')
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = expression_str.replace('*', '×').replace('/', '÷')
readable_expr = Expression(readable_expr_str, result)
return readable_expr
except:
continue
# 如果无法生成有效题目,返回默认题目
expr = Expression("√4+2²", 6)
self.generated_questions.add(str(expr))
readable_expr = Expression("√4+2²", 6)
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = "√4+2²".replace('*', '×').replace('/', '÷')
readable_expr = Expression(readable_expr_str, 6)
return readable_expr
def _generate_middle_expression(self) -> str:
"""
生成初中题目表达式字符串
@return: 表达式字符串
"""
expression_parts = []
# 生成操作数数量2-5个
num_operands = random.randint(2, 5)
# 标记是否已添加特殊操作
special_added = False
# 生成表达式部分
expression_parts, special_added = self._build_middle_expression_parts(
expression_parts, num_operands, special_added)
return ''.join(expression_parts)
def _build_middle_expression_parts(self, expression_parts: List[str],
num_operands: int,
special_added: bool) -> tuple:
"""
构建初中表达式部分
@param expression_parts: 表达式部分列表
@param num_operands: 操作数数量
@param special_added: 是否已添加特殊操作标记
@return: (表达式部分列表, 是否已添加特殊操作标记)
"""
for i in range(num_operands):
# 确保至少添加一个平方或开根号
if not special_added and i == num_operands - 1:
# 如果还没添加特殊操作,强制在最后一个操作数添加
expression_parts, special_added = self._add_forced_special_operation(
expression_parts)
else:
expression_parts, special_added = self._add_middle_operand_or_operation(
expression_parts, special_added, i, num_operands)
# 添加运算符(除了最后一个操作数)
if i < num_operands - 1:
expression_parts.append(random.choice(['+', '-', '*', '/']))
return expression_parts, special_added
def _add_forced_special_operation(self, expression_parts: List[str]) -> tuple:
"""
强制添加特殊操作平方或开根号
@param expression_parts: 表达式部分列表
@return: (表达式部分列表, 是否已添加特殊操作标记)
"""
choice = random.choice([0, 1]) # 0为平方1为开根号
if choice == 0:
# 添加平方
base = random.randint(1, 100)
expression_parts.append(f"{base}²")
else:
# 添加开根号(确保至少有一个开根号)
square = random.randint(1, 10)
value = square * square
expression_parts.append(f"{value}")
return expression_parts, True
def _add_middle_operand_or_operation(self, expression_parts: List[str],
special_added: bool, i: int,
num_operands: int) -> tuple:
"""
添加初中操作数或操作
@param expression_parts: 表达式部分列表
@param special_added: 是否已添加特殊操作标记
@param i: 当前索引
@param num_operands: 操作数总数
@return: (表达式部分列表, 是否已添加特殊操作标记)
"""
rand_val = random.random()
# 修改这里的概率,增加开根号和平方的出现频率
if not special_added and rand_val < 0.6:
# 添加特殊操作(平方或开根号)
expression_parts, special_added = self._add_middle_special_operation()
else:
# 普通操作数
expression_parts.append(str(random.randint(1, 100)))
return expression_parts, special_added
return expression_parts, special_added
def _add_middle_special_operation(self) -> tuple:
"""
添加初中特殊操作平方或开根号
@return: (表达式部分列表, 是否已添加特殊操作标记)
"""
choice = random.choice([0, 1]) # 平方和开根号的概率相等
expression_parts = []
if choice == 0:
# 添加平方
base = random.randint(1, 100)
expression_parts.append(f"{base}²")
else:
# 添加开根号
square = random.randint(1, 10)
value = square * square
expression_parts.append(f"{value}")
return expression_parts, True
def _prepare_middle_expression_for_eval(self, expression_str: str) -> str:
"""
准备初中题目表达式用于eval计算
@param expression_str: 原始表达式字符串
@return: 可用于eval计算的表达式字符串
"""
# 替换表达式中的函数以便计算
eval_expr = expression_str.replace('²', '**2')
# 使用正则表达式正确处理平方根
eval_expr = re.sub(r'√(\d+)',
r'math.sqrt(\1)',
eval_expr)
return eval_expr
def fix_expression_syntax(self, expression: str) -> str:
"""
修复表达式语法问题
@ -379,34 +277,120 @@ class HighQuestionGenerator(QuestionGenerator):
def generate_question(self) -> Expression:
"""
生成高中题目
@return: 数学表达式
"""
max_attempts = 100
for _ in range(max_attempts):
expression_str = self._generate_high_expression()
expression_parts = []
# 确保至少有一个三角函数
has_trig_function = True # 确保至少有一个三角函数
# 生成操作数数量2-4个
num_operands = random.randint(2, 4)
# 标记是否已添加三角函数
trig_added = False
for i in range(num_operands):
# 确保至少添加一个三角函数
if not trig_added and i == num_operands - 1:
# 如果还没添加三角函数,强制在最后一个操作数添加
choice = random.randint(0, 2)
if choice == 0:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"sin({angle}°)")
elif choice == 1:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
else:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"tan({angle}°)")
trig_added = True
else:
# 其他操作数
rand_val = random.random()
if not trig_added and rand_val < 0.4:
# 添加三角函数
func_choice = random.randint(0, 2)
if func_choice == 0:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"sin({angle}°)")
elif func_choice == 1:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
else:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"tan({angle}°)")
trig_added = True
elif rand_val < 0.65:
# 普通数字
expression_parts.append(str(random.randint(1, 20)))
elif rand_val < 0.8:
# 平方
base = random.randint(1, 10)
expression_parts.append(f"{base}²")
else:
# 开根号
square = random.randint(1, 10)
value = square * square
expression_parts.append(f"{value}")
# 添加运算符(除了最后一个操作数)
if i < num_operands - 1:
expression_parts.append(random.choice(['+', '-', '*', '/']))
expression_str = ''.join(expression_parts)
# 处理可能的语法问题
expression_str = self.fix_expression_syntax(expression_str)
# 计算结果
try:
# 替换表达式中的函数以便计算
eval_expr = self._prepare_high_expression_for_eval(expression_str)
eval_expr = expression_str.replace('²', '**2').replace('', 'math.sqrt')
# 修复:确保正确的替换顺序,先替换角度制三角函数
eval_expr = re.sub(r'sin\((\d+)°\)', r'math.sin(math.radians(\1))', eval_expr)
eval_expr = re.sub(r'cos\((\d+)°\)', r'math.cos(math.radians(\1))', eval_expr)
eval_expr = re.sub(r'tan\((\d+)°\)', r'math.tan(math.radians(\1))', eval_expr)
result = eval(eval_expr)
# 确保结果是合理的
if (isinstance(result, (int, float)) and
abs(result) < 1000 and
not math.isnan(result) and
not math.isinf(result)):
if isinstance(result, (int, float)) and abs(result) < 1000 and not math.isnan(result) and not math.isinf(result):
# 格式化结果,保留两位小数
result = self._format_high_result(result)
if isinstance(result, float):
# 特殊处理常见的三角函数值,使其更加准确
if abs(result - 0.5) < 1e-10: # sin(30°) = 0.5
result = 0.5
elif abs(result - 0.7071067811865476) < 1e-10: # sin(45°) = cos(45°) ≈ 0.707
result = round(result, 2)
elif abs(result - 0.8660254037844386) < 1e-10: # sin(60°) ≈ 0.866
result = round(result, 2)
elif abs(result - 0.5773502691896257) < 1e-10: # tan(30°) ≈ 0.577
result = round(result, 2)
elif abs(result - 1.7320508075688772) < 1e-10: # tan(60°) ≈ 1.732
result = round(result, 2)
elif abs(result - 1.0) < 1e-10: # tan(45°) = 1, sin(90°) = 1
result = 1.0
else:
# 保留两位小数
result = round(result, 2)
# 整数保持不变
expr = Expression(expression_str, result)
# 检查是否已生成过相同题目
if str(expr) not in self.generated_questions:
self.generated_questions.add(str(expr))
readable_expr_str = expression_str.replace('*', '×')
readable_expr_str = readable_expr_str.replace('/', '÷')
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = expression_str.replace('*', '×').replace('/', '÷')
readable_expr = Expression(readable_expr_str, result)
return readable_expr
except:
continue
# 如果无法生成有效题目,返回默认题目
expr = Expression("sin(30°)", 0.5)
self.generated_questions.add(str(expr))
readable_expr = Expression("sin(30°)", 0.5)
@ -415,189 +399,6 @@ class HighQuestionGenerator(QuestionGenerator):
readable_expr = Expression(readable_expr_str, 0.5)
return readable_expr
def _generate_high_expression(self) -> str:
"""
生成高中题目表达式字符串
@return: 表达式字符串
"""
expression_parts = []
# 生成操作数数量2-4个
num_operands = random.randint(2, 5)
# 标记是否已添加三角函数
trig_added = False
# 生成表达式部分
expression_parts, trig_added = self._build_high_expression_parts(
expression_parts, num_operands, trig_added)
return ''.join(expression_parts)
def _build_high_expression_parts(self, expression_parts: List[str],
num_operands: int,
trig_added: bool) -> tuple:
"""
构建高中表达式部分
@param expression_parts: 表达式部分列表
@param num_operands: 操作数数量
@param trig_added: 是否已添加三角函数标记
@return: (表达式部分列表, 是否已添加三角函数标记)
"""
for i in range(num_operands):
# 确保至少添加一个三角函数
if not trig_added and i == num_operands - 1:
# 如果还没添加三角函数,强制在最后一个操作数添加
expression_parts, trig_added = self._add_forced_trig_function(
expression_parts)
else:
# 其他操作数
expression_parts, trig_added = self._add_high_operand_or_operation(
expression_parts, trig_added)
# 添加运算符(除了最后一个操作数)
if i < num_operands - 1:
expression_parts.append(random.choice(['+', '-', '*', '/']))
return expression_parts, trig_added
def _add_forced_trig_function(self, expression_parts: List[str]) -> tuple:
"""
强制添加三角函数
@param expression_parts: 表达式部分列表
@return: (表达式部分列表, 是否已添加三角函数标记)
"""
choice = random.randint(0, 2)
if choice == 0:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"sin({angle}°)")
elif choice == 1:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
else:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"tan({angle}°)")
return expression_parts, True
def _add_high_operand_or_operation(self, expression_parts: List[str],
trig_added: bool) -> tuple:
"""
添加高中操作数或操作
@param expression_parts: 表达式部分列表
@param trig_added: 是否已添加三角函数标记
@return: (表达式部分列表, 是否已添加三角函数标记)
"""
rand_val = random.random()
if not trig_added and rand_val < 0.4:
# 添加三角函数
return self._add_trig_function()
elif rand_val < 0.65:
# 普通数字
expression_parts.append(str(random.randint(1, 20)))
return expression_parts, trig_added
elif rand_val < 0.8:
# 平方
base = random.randint(1, 10)
expression_parts.append(f"{base}²")
return expression_parts, trig_added
else:
# 开根号
square = random.randint(1, 10)
value = square * square
expression_parts.append(f"{value}")
return expression_parts, trig_added
def _add_trig_function(self) -> tuple:
"""
添加三角函数
@return: (表达式部分列表, 是否已添加三角函数标记)
"""
expression_parts = []
func_choice = random.randint(0, 2)
if func_choice == 0:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"sin({angle}°)")
elif func_choice == 1:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
else:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"tan({angle}°)")
return expression_parts, True
def _prepare_high_expression_for_eval(self, expression_str: str) -> str:
"""
准备高中题目表达式用于eval计算
@param expression_str: 原始表达式字符串
@return: 可用于eval计算的表达式字符串
"""
eval_expr = self._replace_powers_and_roots(expression_str)
# 修复:确保正确的替换顺序,先替换角度制三角函数
eval_expr = self._replace_trig_functions(eval_expr)
return eval_expr
def _replace_powers_and_roots(self, expression_str: str) -> str:
"""
替换幂运算和开根号符号
@param expression_str: 原始表达式字符串
@return: 替换后的表达式字符串
"""
eval_expr = expression_str.replace('²', '**2')
eval_expr = eval_expr.replace('', 'math.sqrt')
return eval_expr
def _replace_trig_functions(self, eval_expr: str) -> str:
"""
替换三角函数
@param eval_expr: 表达式字符串
@return: 替换三角函数后的表达式字符串
"""
eval_expr = re.sub(r'sin\((\d+)°\)',
r'math.sin(math.radians(\1))',
eval_expr)
eval_expr = re.sub(r'cos\((\d+)°\)',
r'math.cos(math.radians(\1))',
eval_expr)
eval_expr = re.sub(r'tan\((\d+)°\)',
r'math.tan(math.radians(\1))',
eval_expr)
return eval_expr
def _format_high_result(self, result: float) -> float:
"""
格式化高中题目计算结果
@param result: 计算结果
@return: 格式化后的结果
"""
if isinstance(result, float):
# 特殊处理常见的三角函数值,使其更加准确
if abs(result - 0.5) < 1e-10: # sin(30°) = 0.5
result = 0.5
elif abs(result - 0.7071067811865476) < 1e-10: # sin(45°) = cos(45°) ≈ 0.707
result = round(result, 2)
elif abs(result - 0.8660254037844386) < 1e-10: # sin(60°) ≈ 0.866
result = round(result, 2)
elif abs(result - 0.5773502691896257) < 1e-10: # tan(30°) ≈ 0.577
result = round(result, 2)
elif abs(result - 1.7320508075688772) < 1e-10: # tan(60°) ≈ 1.732
result = round(result, 2)
elif abs(result - 1.0) < 1e-10: # tan(45°) = 1, sin(90°) = 1
result = 1.0
else:
# 保留两位小数
result = round(result, 2)
# 整数保持不变
return result
def fix_expression_syntax(self, expression: str) -> str:
"""
修复表达式语法问题
@ -606,22 +407,10 @@ class HighQuestionGenerator(QuestionGenerator):
@return: 修复后的表达式
"""
# 确保函数调用之间有运算符
expression = re.sub(r'(\d)([sincostan√])',
r'\1*\2',
expression)
expression = re.sub(r'(²)([sincostan√])',
r'\1*\2',
expression)
expression = re.sub(r'(sqrt\(\d+\))(\d)',
r'\1*\2',
expression)
expression = re.sub(r'(\))(\d)',
r'\1*\2',
expression)
expression = re.sub(r'(\d)(\()',
r'\1*\2',
expression)
expression = re.sub(r'°\)(\d)',
r'°)*\1',
expression)
return expression
expression = re.sub(r'(\d)([sincostan√])', r'\1*\2', expression)
expression = re.sub(r'(²)([sincostan√])', r'\1*\2', expression)
expression = re.sub(r'(sqrt\(\d+\))(\d)', r'\1*\2', expression)
expression = re.sub(r'(\))(\d)', r'\1*\2', expression)
expression = re.sub(r'(\d)(\()', r'\1*\2', expression)
expression = re.sub(r'°\)(\d)', r'°)*\1', expression)
return expression

@ -22,7 +22,7 @@ class Quiz:
"""
self.questions = questions
self.answers: List[Optional[str]] = [None] * len(questions) # 改为字符串类型存储
self.options: List[List[str]] = self._generate_options_for_questions()
self.options: List[List[str]] = self._generate_options_for_questions() # 为每道题生成固定选项
self.current_question_index = 0
self.score = 0
@ -79,8 +79,7 @@ class Quiz:
if random.random() < 0.5:
options.add(random.randint(0, 100))
else:
options.add(round(random.uniform(0, 100),
random.choice([0, 1, 2])))
options.add(round(random.uniform(0, 100), random.choice([0, 1, 2])))
# 将所有选项转换为字符串
options_list = [str(opt) for opt in options]
@ -155,10 +154,7 @@ class Quiz:
# 如果转换失败,说明答案无效,不计入正确答案
pass
if self.questions:
self.score = (correct_count / len(self.questions)) * 100
else:
self.score = 0
self.score = (correct_count / len(self.questions)) * 100 if self.questions else 0
return self.score
def is_finished(self) -> bool:
@ -167,5 +163,4 @@ class Quiz:
@return: 是否已完成
"""
return (self.current_question_index == len(self.questions) - 1 and
self.answers[self.current_question_index] is not None)
return self.current_question_index == len(self.questions) - 1 and self.answers[self.current_question_index] is not None

@ -11,7 +11,6 @@ import re
import random
import hashlib
import smtplib
from abc import ABC, abstractmethod
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Dict, Optional
@ -23,8 +22,7 @@ class User:
用户类存储用户信息
"""
def __init__(self, email: str, username: str = "",
password_hash: str = "", registration_code: str = ""):
def __init__(self, email: str, username: str = "", password_hash: str = "", registration_code: str = ""):
"""
初始化用户对象
@ -40,48 +38,7 @@ class User:
self.is_registered = bool(password_hash)
class AbstractUserManager(ABC):
"""
抽象用户管理类定义用户管理的接口
"""
@abstractmethod
def register_user(self, email: str, username: str) -> bool:
"""注册新用户"""
pass
@abstractmethod
def verify_registration_code(self, email: str, code: str) -> bool:
"""验证注册码"""
pass
@abstractmethod
def set_password(self, email: str, password: str) -> bool:
"""设置用户密码"""
pass
@abstractmethod
def login(self, username: str, password: str) -> bool:
"""用户登录"""
pass
@abstractmethod
def change_password(self, old_password: str, new_password: str) -> bool:
"""修改用户密码"""
pass
@abstractmethod
def logout(self) -> None:
"""用户登出"""
pass
@abstractmethod
def delete_account(self, email: str, password: str) -> bool:
"""注销账户"""
pass
class UserManager(AbstractUserManager):
class UserManager:
"""
用户管理类负责处理用户注册登录和密码管理
"""
@ -115,11 +72,9 @@ class UserManager(AbstractUserManager):
email=email,
username=user_data.get('username', ''),
password_hash=user_data.get('password_hash', ''),
registration_code=user_data.get(
'registration_code', '')
registration_code=user_data.get('registration_code', '')
)
user.is_registered = user_data.get('is_registered',
False)
user.is_registered = user_data.get('is_registered', False)
self.users[email] = user
except (json.JSONDecodeError, FileNotFoundError):
self.users = {}
@ -199,69 +154,13 @@ class UserManager(AbstractUserManager):
return has_lower and has_upper and has_digit
def _create_registration_email(self, email: str, code: str
) -> MIMEMultipart:
"""
创建注册邮件内容
@param email: 接收邮箱
@param code: 注册码
@return: 邮件对象
"""
message = MIMEMultipart()
message["From"] = self.sender_email
message["To"] = email
message["Subject"] = "数学练习系统注册码"
body = f"""
您好
欢迎使用数学练习系统
您的注册码是: {code}
请在注册界面输入此注册码完成注册
如果您没有请求此注册码请忽略此邮件
祝学习愉快
数学练习系统团队
"""
message.attach(MIMEText(body, "plain", "utf-8"))
return message
def send_registration_code_via_email(self, email: str, code: str) -> bool:
"""
通过电子邮件发送注册码
@param email: 接收邮箱
@param code: 注册码
@return: 是否发送成功
"""
try:
message = self._create_registration_email(email, code)
# 使用SMTP_SSL连接QQ邮箱服务器
server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
server.login(self.sender_email, self.sender_password)
text = message.as_string()
server.sendmail(self.sender_email, email, text)
server.quit()
return True
except Exception as e:
print(f"邮件发送失败: {str(e)}")
return False
def _check_user_registration_eligibility(self, email: str,
username: str) -> bool:
def register_user(self, email: str, username: str) -> bool:
"""
检查用户是否符合注册条件
注册新用户发送注册码
@param email: 用户邮箱
@param username: 用户名
@return: 是否符合注册条件
@return: 是否成功发送注册码
"""
if not self.is_valid_email(email):
messagebox.showerror("错误", "邮箱格式不正确")
@ -281,23 +180,9 @@ class UserManager(AbstractUserManager):
if user.username == username and user.is_registered:
messagebox.showerror("错误", "该用户名已存在")
return False
return True
def register_user(self, email: str, username: str) -> bool:
"""
注册新用户发送注册码
@param email: 用户邮箱
@param username: 用户名
@return: 是否成功发送注册码
"""
if not self._check_user_registration_eligibility(email, username):
return False
registration_code = self.generate_registration_code()
user = User(email=email, username=username,
registration_code=registration_code)
user = User(email=email, username=username, registration_code=registration_code)
self.users[email] = user
# 尝试发送邮件
@ -306,23 +191,65 @@ class UserManager(AbstractUserManager):
messagebox.showinfo("成功", "注册码已发送到您的邮箱,请查收")
return True
else:
messagebox.showerror(
"错误",
"无法发送注册码到邮箱,请检查网络连接或邮箱地址")
messagebox.showerror("错误", "无法发送注册码到邮箱,请检查网络连接或邮箱地址")
# 即使邮件发送失败,也保存用户信息以便重试
self.save_users()
return False
def send_registration_code_via_email(self, email: str, code: str) -> bool:
"""
通过电子邮件发送注册码
@param email: 接收邮箱
@param code: 注册码
@return: 是否发送成功
"""
try:
# 创建邮件内容
message = MIMEMultipart()
message["From"] = self.sender_email
message["To"] = email
message["Subject"] = "数学练习系统注册码"
body = f"""
您好
欢迎使用数学练习系统
您的注册码是: {code}
请在注册界面输入此注册码完成注册
如果您没有请求此注册码请忽略此邮件
祝学习愉快
数学练习系统团队
"""
message.attach(MIMEText(body, "plain", "utf-8"))
# 使用SMTP_SSL连接QQ邮箱服务器
server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
server.login(self.sender_email, self.sender_password)
text = message.as_string()
server.sendmail(self.sender_email, email, text)
server.quit()
return True
except Exception as e:
print(f"邮件发送失败: {str(e)}")
return False
def verify_registration_code(self, email: str, code: str) -> bool:
"""
验证注册码
@param email: 用户邮箱
@param code: 注册码
@param code: 用户输入的注册码
@return: 注册码是否正确
"""
if email not in self.users:
messagebox.showerror("错误", "用户不存在")
messagebox.showerror("错误", "请先获取注册码")
return False
user = self.users[email]
@ -341,9 +268,7 @@ class UserManager(AbstractUserManager):
@return: 是否设置成功
"""
if not self.is_valid_password(password):
messagebox.showerror(
"错误",
"密码必须为6-10位且包含大小写字母和数字")
messagebox.showerror("错误", "密码必须为6-10位且包含大小写字母和数字")
return False
if email not in self.users:
@ -354,21 +279,8 @@ class UserManager(AbstractUserManager):
user.password_hash = self.hash_password(password)
user.is_registered = True
self.save_users()
messagebox.showinfo("成功", "密码设置成功,请登录")
return True
def _find_user_by_username(self, username: str) -> Optional[User]:
"""
根据用户名查找用户
@param username: 用户名
@return: 用户对象或None
"""
for u in self.users.values():
if u.username == username:
return u
return None
def login(self, username: str, password: str) -> bool:
"""
用户登录
@ -377,7 +289,12 @@ class UserManager(AbstractUserManager):
@param password: 用户密码
@return: 是否登录成功
"""
user = self._find_user_by_username(username)
# 根据用户名查找用户
user = None
for u in self.users.values():
if u.username == username:
user = u
break
if user is None:
messagebox.showerror("错误", "用户不存在")
@ -411,9 +328,7 @@ class UserManager(AbstractUserManager):
return False
if not self.is_valid_password(new_password):
messagebox.showerror(
"错误",
"新密码必须为6-10位且包含大小写字母和数字")
messagebox.showerror("错误", "新密码必须为6-10位且包含大小写字母和数字")
return False
self.current_user.password_hash = self.hash_password(new_password)
@ -462,4 +377,4 @@ class UserManager(AbstractUserManager):
self.current_user = None
self.save_users()
return True
return True

@ -1,14 +1,14 @@
{
"1426688201@qq.com": {
"username": "111",
"password_hash": "c4318372f98f4c46ed3a32c16ee4d7a76c832886d887631c0294b3314f34edf1",
"registration_code": "939598",
"is_registered": true
},
"3154420541@qq.com": {
"username": "123",
"password_hash": "b17e1e0450dac425ea318253f6f852972d69731d6c7499c001468b695b6da219",
"registration_code": "926322",
"is_registered": true
},
"1426688201@qq.com": {
"username": "111",
"password_hash": "c4318372f98f4c46ed3a32c16ee4d7a76c832886d887631c0294b3314f34edf1",
"registration_code": "769790",
"is_registered": true
}
}
Loading…
Cancel
Save