diff --git a/doc/README.md b/doc/README.md index e6108d0..3a931dd 100644 --- a/doc/README.md +++ b/doc/README.md @@ -42,7 +42,7 @@ text └── README.md # 项目说明文档 安装与运行 环境要求 -• Python 3.11.9 或更高版本 +• Python 3.7 或更高版本 • 网络连接(用于邮箱验证码发送) 运行步骤 1. 下载项目文件到本地 diff --git a/src/__pycache__/backend_service.cpython-311.pyc b/src/__pycache__/backend_service.cpython-311.pyc deleted file mode 100644 index fa4694d..0000000 Binary files a/src/__pycache__/backend_service.cpython-311.pyc and /dev/null differ diff --git a/src/__pycache__/main_app.cpython-311.pyc b/src/__pycache__/main_app.cpython-311.pyc index 7db10b0..e3f5ecf 100644 Binary files a/src/__pycache__/main_app.cpython-311.pyc and b/src/__pycache__/main_app.cpython-311.pyc differ diff --git a/src/__pycache__/question_bank.cpython-311.pyc b/src/__pycache__/question_bank.cpython-311.pyc index 68d5b96..bd940b3 100644 Binary files a/src/__pycache__/question_bank.cpython-311.pyc and b/src/__pycache__/question_bank.cpython-311.pyc differ diff --git a/src/__pycache__/question_generator.cpython-311.pyc b/src/__pycache__/question_generator.cpython-311.pyc index 1af138a..d0555e0 100644 Binary files a/src/__pycache__/question_generator.cpython-311.pyc and b/src/__pycache__/question_generator.cpython-311.pyc differ diff --git a/src/__pycache__/quiz.cpython-311.pyc b/src/__pycache__/quiz.cpython-311.pyc index 7e32e5b..7fb1b35 100644 Binary files a/src/__pycache__/quiz.cpython-311.pyc and b/src/__pycache__/quiz.cpython-311.pyc differ diff --git a/src/__pycache__/user_manager.cpython-311.pyc b/src/__pycache__/user_manager.cpython-311.pyc index e5767b9..04ceb36 100644 Binary files a/src/__pycache__/user_manager.cpython-311.pyc and b/src/__pycache__/user_manager.cpython-311.pyc differ diff --git a/src/backend_service.py b/src/backend_service.py deleted file mode 100644 index f8e2f43..0000000 --- a/src/backend_service.py +++ /dev/null @@ -1,238 +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_current_username(self) -> str: - """ - 获取当前登录用户的用户名 - - @return: 用户名 - """ - if self.user_manager.current_user: - return self.user_manager.current_user.username - return "未知用户" diff --git a/src/main_app.py b/src/main_app.py index 644a768..238d6a9 100644 --- a/src/main_app.py +++ b/src/main_app.py @@ -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,50 +606,51 @@ 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() + 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) # 根据得分显示不同颜色的分数 - score_color = (self.danger_color if score < 60 else - self.warning_color if score < 80 else - self.success_color) + 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) # 根据得分显示评语 @@ -700,16 +667,22 @@ class MathQuizApp: comment = "需要加强练习哦!" comment_color = self.danger_color - tk.Label(result_frame, text=comment, font=self.header_font, - fg=comment_color, bg="#ffffff").pack(pady=10) + 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) # 按钮 @@ -717,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) @@ -742,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) @@ -815,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): @@ -836,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) @@ -879,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) @@ -906,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() \ No newline at end of file + self.show_delete_account_frame() + + diff --git a/src/question_generator.py b/src/question_generator.py index 54bdca4..8d33c26 100644 --- a/src/question_generator.py +++ b/src/question_generator.py @@ -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 \ No newline at end of file + 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 diff --git a/src/quiz.py b/src/quiz.py index 6500f32..cf09164 100644 --- a/src/quiz.py +++ b/src/quiz.py @@ -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) \ No newline at end of file + return self.current_question_index == len(self.questions) - 1 and self.answers[self.current_question_index] is not None \ No newline at end of file diff --git a/src/user_manager.py b/src/user_manager.py index e1ce150..198fb7d 100644 --- a/src/user_manager.py +++ b/src/user_manager.py @@ -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 \ No newline at end of file + return True diff --git a/src/users.json b/src/users.json index 91d824c..672adb6 100644 --- a/src/users.json +++ b/src/users.json @@ -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 } } \ No newline at end of file