更新1 #4

Merged
hnu202326010111 merged 0 commits from pengyunhao_branch into develop 5 months ago

@ -0,0 +1,23 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
数学练习系统 - 面向小学初中和高中学生的数学题目练习应用
支持用户注册登录密码管理题目生成和答题功能
"""
import tkinter as tk
from main_app import MathQuizApp
def main():
"""
主函数
"""
root = tk.Tk()
app = MathQuizApp(root)
root.mainloop()
if __name__ == "__main__":
main()

@ -0,0 +1,775 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
主应用模块
"""
import tkinter as tk
from tkinter import messagebox, ttk
import random
from typing import List, Optional
from user_manager import UserManager
from question_bank import QuestionBank
from quiz import Quiz
from question_generator import Expression
class MathQuizApp:
"""
数学测验应用程序主类
"""
def __init__(self, root: tk.Tk):
"""
初始化应用程序
@param root: Tk根窗口
"""
self.root = root
self.root.title("数学练习系统")
self.root.geometry("700x600")
self.root.resizable(True, True)
self.root.configure(bg="#f0f0f0")
# 初始化系统组件
self.user_manager = UserManager()
self.question_bank = QuestionBank()
# 设置样式
self.setup_styles()
# 创建不同的界面框架
self.login_frame = tk.Frame(self.root, bg="#f0f0f0")
self.register_frame = tk.Frame(self.root, bg="#f0f0f0")
self.set_password_frame = tk.Frame(self.root, bg="#f0f0f0")
self.main_menu_frame = tk.Frame(self.root, bg="#f0f0f0")
self.quiz_setup_frame = tk.Frame(self.root, bg="#f0f0f0")
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.current_quiz: Optional[Quiz] = None
self.current_level = ""
self.question_count = 0
# 创建界面
self.create_widgets()
self.show_login_frame()
def setup_styles(self):
"""
设置界面样式
"""
self.title_font = ("Arial", 20, "bold")
self.header_font = ("Arial", 16, "bold")
self.normal_font = ("Arial", 12)
self.button_font = ("Arial", 11, "bold")
# 定义颜色方案
self.primary_color = "#4a6fa5"
self.secondary_color = "#6b8cbc"
self.accent_color = "#ff6b6b"
self.success_color = "#4caf50"
self.warning_color = "#ffc107"
self.danger_color = "#f44336"
self.light_bg = "#f8f9fa"
self.dark_text = "#333333"
def create_widgets(self):
"""
创建界面组件
"""
# 创建不同的界面框架
self.login_frame = tk.Frame(self.root, bg="#f0f0f0")
self.register_frame = tk.Frame(self.root, bg="#f0f0f0")
self.set_password_frame = tk.Frame(self.root, bg="#f0f0f0")
self.main_menu_frame = tk.Frame(self.root, bg="#f0f0f0")
self.quiz_setup_frame = tk.Frame(self.root, bg="#f0f0f0")
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") # 添加删除账户框架
def show_frame(self, frame: tk.Frame):
"""
显示指定的界面框架
@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]:
f.pack_forget()
# 显示指定框架
frame.pack(fill="both", expand=True)
def show_login_frame(self):
"""
显示登录界面
"""
# 清除之前的内容
for widget in self.login_frame.winfo_children():
widget.destroy()
# 创建登录界面
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)
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, 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, 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, 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, fg="white",
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.login_frame)
def login(self):
"""
处理登录逻辑
"""
username = self.login_email_entry.get().strip()
password = self.login_password_entry.get()
if not username or not password:
messagebox.showerror("错误", "请输入用户名和密码")
return
if self.user_manager.login(username, password):
messagebox.showinfo("成功", "登录成功")
self.show_main_menu_frame()
# 错误信息在user_manager.login中已经显示
def show_register_frame(self):
"""
显示注册界面
"""
# 清除之前的内容
for widget in self.register_frame.winfo_children():
widget.destroy()
# 创建注册界面
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)
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, 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, 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, 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, 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, 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", fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.register_frame)
def send_registration_code(self):
"""
发送注册码
"""
username = self.register_username_entry.get().strip()
email = self.register_email_entry.get().strip()
if not username or not email:
messagebox.showerror("错误", "请输入用户名和邮箱")
return
if self.user_manager.register_user(email, username):
messagebox.showinfo("成功", "注册码已发送,请查收")
def verify_registration_code(self):
"""
验证注册码
"""
email = self.register_email_entry.get().strip()
code = self.registration_code_entry.get().strip()
if not email or not code:
messagebox.showerror("错误", "请输入邮箱和注册码")
return
if self.user_manager.verify_registration_code(email, code):
messagebox.showinfo("成功", "注册码验证成功,请设置密码")
self.show_set_password_frame(email)
def show_set_password_frame(self, email: str):
"""
显示设置密码界面
@param email: 用户邮箱
"""
# 清除之前的内容
for widget in self.set_password_frame.winfo_children():
widget.destroy()
# 创建设置密码界面
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)
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="密码:", 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", 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, 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, 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, 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", fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.set_password_frame)
def set_password(self, email: str):
"""
设置密码
@param email: 用户邮箱
"""
password = self.set_password_entry.get()
confirm_password = self.confirm_password_entry.get()
if not password or not confirm_password:
messagebox.showerror("错误", "请输入密码和确认密码")
return
if password != confirm_password:
messagebox.showerror("错误", "两次输入的密码不一致")
return
if self.user_manager.set_password(email, password):
messagebox.showinfo("成功", "密码设置成功,请登录")
self.show_login_frame()
def show_main_menu_frame(self):
"""
显示主菜单界面
"""
# 清除之前的内容
for widget in self.main_menu_frame.winfo_children():
widget.destroy()
# 创建主菜单界面
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)
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(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(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(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, 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)
self.show_frame(self.main_menu_frame)
def show_quiz_setup_frame(self, level: str):
"""
显示测验设置界面
@param level: 题目难度
"""
self.current_level = level
# 清除之前的内容
for widget in self.quiz_setup_frame.winfo_children():
widget.destroy()
# 创建测验设置界面
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"}
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], fg="white").pack(pady=20)
form_frame = tk.Frame(main_frame, bg="#ffffff")
form_frame.pack(pady=20)
tk.Label(form_frame, text="请输入题目数量 (1-20):", 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", fg=self.dark_text,
font=self.button_font, relief=tk.FLAT, bd=0, padx=10, pady=5).pack(pady=5)
self.show_frame(self.quiz_setup_frame)
def start_quiz(self):
"""
开始测验
"""
try:
count = int(self.question_count_entry.get())
if not (1 <= count <= 20):
messagebox.showerror("错误", "题目数量必须在1-20之间")
return
except ValueError:
messagebox.showerror("错误", "请输入有效的数字")
return
self.question_count = count
# 生成题目
try:
questions = self.question_bank.generate_questions(self.current_level, self.question_count)
self.current_quiz = Quiz(questions)
self.show_quiz_frame()
except Exception as e:
messagebox.showerror("错误", f"生成题目时出错: {str(e)}")
def show_quiz_frame(self):
"""
显示答题界面
"""
if not self.current_quiz:
return
# 清除之前的内容
for widget in self.quiz_frame.winfo_children():
widget.destroy()
# 创建答题界面
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 = 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)
# 显示题目
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))
question_text = str(current_question)
tk.Label(question_frame, text=question_text, font=("Arial", 16, "bold"), bg="#ffffff", wraplength=500).pack(pady=10)
# 计算选项
options = self.generate_options(current_question.answer)
# 显示选项
tk.Label(main_frame, text="请选择答案:", font=self.normal_font, bg="#ffffff").pack(pady=(20, 10))
self.answer_var = tk.StringVar()
self.answer_var.set(" ") # 明确设置初始值为空字符串,确保不选中任何选项
options_frame = tk.Frame(main_frame, bg="#ffffff")
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=str(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 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 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)
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:
offset = random.randint(-10, 10)
if offset != 0:
options.add(correct_answer + offset)
else:
# 浮点数情况
while len(options) < 4:
offset = random.uniform(-10, 10)
if abs(offset) > 0.1: # 避免太接近正确答案
options.add(round(correct_answer + offset, 2))
# 如果选项不足4个补充一些随机数
while len(options) < 4:
options.add(round(random.uniform(correct_answer - 20, correct_answer + 20), 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("错误", "请选择一个答案")
return
try:
answer = float(answer_str)
self.current_quiz.answer_question(answer)
# 如果是最后一题,显示结果
if self.current_quiz.is_finished():
self.show_result_frame()
else:
messagebox.showinfo("提示", "答案已提交")
except ValueError:
messagebox.showerror("错误", "请选择有效答案")
def next_question(self):
"""
下一题
"""
if not self.current_quiz:
return
# 保存当前答案(如果有选择)
answer_str = self.answer_var.get()
if answer_str:
try:
answer = float(answer_str)
self.current_quiz.answer_question(answer)
except ValueError:
pass # 如果答案无效保持为None
if self.current_quiz.next_question():
self.show_quiz_frame()
else:
# 已经是最后一题
if self.current_quiz.answers[self.current_quiz.current_question_index] is not None:
# 如果最后一题已答题,显示结果
self.show_result_frame()
else:
messagebox.showinfo("提示", "已经是最后一题")
def previous_question(self):
"""
上一题
"""
if not self.current_quiz:
return
# 保存当前答案(如果有选择)
answer_str = self.answer_var.get()
if answer_str:
try:
answer = float(answer_str)
self.current_quiz.answer_question(answer)
except ValueError:
pass # 如果答案无效保持为None
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.current_quiz.calculate_score()
# 创建结果界面
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)
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
tk.Label(result_frame, text=f"您的得分: {score:.1f}%", font=("Arial", 20, "bold"), 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)
# 显示答题详情
correct_count = sum(1 for q, a in zip(self.current_quiz.questions, self.current_quiz.answers)
if a is not None and abs(q.answer - a) < 1e-6)
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)
# 按钮
button_frame = tk.Frame(main_frame, bg="#ffffff")
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, 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_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)
self.show_frame(self.result_frame)
def show_change_password_frame(self):
"""
显示修改密码界面
"""
# 清除之前的内容
for widget in self.change_password_frame.winfo_children():
widget.destroy()
# 创建修改密码界面
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)
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, 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(pady=(10, 5), anchor="w")
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, 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")
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, 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)
self.show_frame(self.change_password_frame)
def change_password(self):
"""
修改密码
"""
old_password = self.old_password_entry.get()
new_password = self.new_password_entry.get()
confirm_new_password = self.confirm_new_password_entry.get()
if not old_password or not new_password or not confirm_new_password:
messagebox.showerror("错误", "请填写所有字段")
return
if new_password != confirm_new_password:
messagebox.showerror("错误", "新密码和确认密码不一致")
return
if self.user_manager.change_password(old_password, new_password):
messagebox.showinfo("成功", "密码修改成功")
self.show_main_menu_frame()
# 错误信息在user_manager.change_password中已经显示
def logout(self):
"""
退出登录
"""
self.user_manager.logout()
self.show_login_frame()
def show_delete_account_frame(self):
"""
显示注销账户界面
"""
# 清除之前的内容
for widget in self.delete_account_frame.winfo_children():
widget.destroy()
# 创建注销账户界面
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)
warning_frame = tk.Frame(main_frame, bg="#ffffff")
warning_frame.pack(pady=10)
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, 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, 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, bd=5, bg="#f0f0f0")
self.delete_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.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, pady=5).pack(side="left", padx=10)
self.show_frame(self.delete_account_frame)
def confirm_delete_account(self):
"""
确认并执行账户删除操作
"""
email = self.delete_email_entry.get().strip()
password = self.delete_password_entry.get()
if not email or not password:
messagebox.showerror("错误", "请输入邮箱和密码")
return
# 确认操作
if messagebox.askyesno("确认注销", "确定要注销账户吗?此操作无法撤销!"):
if self.user_manager.delete_account(email, password):
messagebox.showinfo("成功", "账户已注销,您可以使用该邮箱重新注册")
self.show_login_frame()
# 错误信息在user_manager.delete_account中已经显示
def delete_account(self):
"""
注销账户从主菜单调用的旧方法保持兼容性
"""
self.show_delete_account_frame()

@ -0,0 +1,45 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
题库模块
"""
from typing import List, Dict
from question_generator import Expression, ElementaryQuestionGenerator, MiddleQuestionGenerator, HighQuestionGenerator
class QuestionBank:
"""
题库类管理不同难度的题目生成器
"""
def __init__(self):
"""
初始化题库
"""
self.generators = {
"elementary": ElementaryQuestionGenerator(),
"middle": MiddleQuestionGenerator(),
"high": HighQuestionGenerator()
}
def generate_questions(self, level: str, count: int) -> List[Expression]:
"""
生成指定数量和难度的题目
@param level: 题目难度elementary, middle, high
@param count: 题目数量
@return: 题目列表
"""
if level not in self.generators:
raise ValueError("无效的题目难度")
generator = self.generators[level]
questions = []
for _ in range(count):
question = generator.generate_question()
questions.append(question)
return questions

@ -0,0 +1,408 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
题目生成器模块
"""
import random
import math
import re
from abc import ABC, abstractmethod
from typing import List, Set, Optional
class Expression:
"""
数学表达式类
"""
def __init__(self, expression_str: str, answer: float):
"""
初始化表达式
@param expression_str: 表达式字符串
@param answer: 表达式答案
"""
self.expression_str = expression_str
self.answer = answer
def __str__(self) -> str:
"""
返回表达式的字符串表示
@return: 表达式字符串
"""
return self.expression_str
def __eq__(self, other) -> bool:
"""
比较两个表达式是否相等
@param other: 另一个表达式
@return: 是否相等
"""
if isinstance(other, Expression):
return self.expression_str == other.expression_str
return False
def __hash__(self) -> int:
"""
计算表达式的哈希值
@return: 哈希值
"""
return hash(self.expression_str)
class QuestionGenerator(ABC):
"""
题目生成器抽象基类
"""
def __init__(self):
"""
初始化题目生成器
"""
self.generated_questions: Set[str] = set()
self.load_existing_questions()
def load_existing_questions(self) -> None:
"""
加载已存在的题目用于查重
"""
# 在实际应用中,可以从文件或其他存储中加载已存在的题目
pass
@abstractmethod
def generate_question(self) -> Expression:
"""
生成题目抽象方法
@return: 数学表达式
"""
pass
class ElementaryQuestionGenerator(QuestionGenerator):
"""
小学题目生成器+, -, *, /, 括号
"""
def generate_question(self) -> Expression:
"""
生成小学题目
@return: 数学表达式
"""
max_attempts = 100
for _ in range(max_attempts):
# 随机生成操作数数量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, float) and result.is_integer():
result = int(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('*', '×').replace('/', '÷')
readable_expr = Expression(readable_expr_str, result)
return readable_expr
except:
continue
# 如果无法生成有效题目,返回默认题目
expr = Expression("1+1", 2)
self.generated_questions.add(str(expr))
readable_expr = Expression("1+1", 2)
return readable_expr
class MiddleQuestionGenerator(QuestionGenerator):
"""
初中题目生成器包含平方或开根号
"""
def generate_question(self) -> Expression:
"""
生成初中题目
@return: 数学表达式
"""
max_attempts = 100
for _ in range(max_attempts):
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]) # 提高开根号概率
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.6: # 提高特殊操作概率从0.4到0.6
# 添加特殊操作(平方或开根号)
choice = random.choice([0, 1]) if random.random() < 0.6 else 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 = 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, 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('*', '×').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 fix_expression_syntax(self, expression: str) -> str:
"""
修复表达式语法问题
@param expression: 原始表达式
@return: 修复后的表达式
"""
# 确保函数调用之间有运算符
expression = re.sub(r'(\d)([√])', r'\1*\2', expression)
expression = re.sub(r'(²)([√])', 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
class HighQuestionGenerator(QuestionGenerator):
"""
高中题目生成器包含三角函数
"""
def generate_question(self) -> Expression:
"""
生成高中题目
@return: 数学表达式
"""
max_attempts = 100
for _ in range(max_attempts):
expression_parts = []
# 确保至少有一个三角函数
has_trig_function = random.random() < 0.8
# 生成操作数数量2-4个
num_operands = random.randint(2, 4)
for i in range(num_operands):
if has_trig_function and i == 0:
# 第一个操作数有较高概率是三角函数
if random.random() < 0.33:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"sin({angle}°)")
elif random.random() < 0.5:
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}°)")
else:
# 其他操作数
rand_val = random.random()
if rand_val < 0.1 and has_trig_function:
# 添加三角函数
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"sin({angle}°)")
elif rand_val < 0.2 and has_trig_function:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
elif rand_val < 0.3 and has_trig_function:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"tan({angle}°)")
elif rand_val < 0.55:
# 普通数字
expression_parts.append(str(random.randint(1, 20)))
elif rand_val < 0.7:
# 平方
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 = 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, float) and abs(result - round(result, 10)) < 1e-10:
result = int(round(result))
# 特殊处理常见的三角函数值,使其更加准确
elif 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, 10)
elif abs(result - 0.8660254037844386) < 1e-10: # sin(60°) ≈ 0.866
result = round(result, 10)
elif abs(result - 0.5773502691896257) < 1e-10: # tan(30°) ≈ 0.577
result = round(result, 10)
elif abs(result - 1.7320508075688772) < 1e-10: # tan(60°) ≈ 1.732
result = round(result, 10)
elif abs(result - 1.0) < 1e-10: # tan(45°) = 1, sin(90°) = 1
result = 1.0
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('*', '×').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)
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = "sin(30°)".replace('*', '×').replace('/', '÷')
readable_expr = Expression(readable_expr_str, 0.5)
return readable_expr
def fix_expression_syntax(self, expression: str) -> str:
"""
修复表达式语法问题
@param expression: 原始表达式
@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

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测验模块
"""
from typing import List, Optional
from question_generator import Expression
class Quiz:
"""
测验类管理一次答题过程
"""
def __init__(self, questions: List[Expression]):
"""
初始化测验
@param questions: 题目列表
"""
self.questions = questions
self.answers: List[Optional[float]] = [None] * len(questions)
self.current_question_index = 0
self.score = 0
def answer_question(self, answer: float) -> None:
"""
回答当前题目
@param answer: 用户答案
"""
if 0 <= self.current_question_index < len(self.questions):
self.answers[self.current_question_index] = answer
def next_question(self) -> bool:
"""
跳转到下一题
@return: 是否有下一题
"""
if self.current_question_index < len(self.questions) - 1:
self.current_question_index += 1
return True
return False
def previous_question(self) -> bool:
"""
返回上一题
@return: 是否有上一题
"""
if self.current_question_index > 0:
self.current_question_index -= 1
return True
return False
def get_current_question(self) -> Optional[Expression]:
"""
获取当前题目
@return: 当前题目
"""
if 0 <= self.current_question_index < len(self.questions):
return self.questions[self.current_question_index]
return None
def calculate_score(self) -> float:
"""
计算得分
@return: 得分百分比
"""
correct_count = 0
for i, (question, answer) in enumerate(zip(self.questions, self.answers)):
if answer is not None:
# 允许一定的浮点数误差
if abs(question.answer - answer) < 1e-6:
correct_count += 1
self.score = (correct_count / len(self.questions)) * 100 if self.questions else 0
return self.score
def is_finished(self) -> bool:
"""
检查测验是否已完成
@return: 是否已完成
"""
return self.current_question_index == len(self.questions) - 1 and self.answers[self.current_question_index] is not None

@ -0,0 +1,381 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
用户管理模块
"""
import json
import os
import re
import random
import hashlib
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Dict, Optional
from tkinter import messagebox
class User:
"""
用户类存储用户信息
"""
def __init__(self, email: str, username: str = "", password_hash: str = "", registration_code: str = ""):
"""
初始化用户对象
@param email: 用户邮箱
@param username: 用户名
@param password_hash: 密码哈希值
@param registration_code: 注册码
"""
self.email = email
self.username = username
self.password_hash = password_hash
self.registration_code = registration_code
self.is_registered = bool(password_hash)
class UserManager:
"""
用户管理类负责处理用户注册登录和密码管理
"""
def __init__(self, data_file: str = "users.json"):
"""
初始化用户管理器
@param data_file: 存储用户数据的文件路径
"""
self.data_file = data_file
self.users: Dict[str, User] = {}
self.current_user: Optional[User] = None
# 邮件配置
self.smtp_server = "smtp.qq.com" # 可根据需要修改SMTP服务器
self.smtp_port = 465
self.sender_email = "3257534544@qq.com" # 需要替换为实际邮箱
self.sender_password = "pmfyurbkwfpkdbed" # 需要替换为实际应用密码
self.load_users()
def load_users(self) -> None:
"""
从文件加载用户数据
"""
if os.path.exists(self.data_file):
try:
with open(self.data_file, 'r', encoding='utf-8') as f:
data = json.load(f)
for email, user_data in data.items():
user = User(
email=email,
username=user_data.get('username', ''),
password_hash=user_data.get('password_hash', ''),
registration_code=user_data.get('registration_code', '')
)
user.is_registered = user_data.get('is_registered', False)
self.users[email] = user
except (json.JSONDecodeError, FileNotFoundError):
self.users = {}
def save_users(self) -> None:
"""
保存用户数据到文件
"""
data = {}
for email, user in self.users.items():
data[email] = {
'username': user.username,
'password_hash': user.password_hash,
'registration_code': user.registration_code,
'is_registered': user.is_registered
}
try:
with open(self.data_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except IOError as e:
messagebox.showerror("错误", f"保存用户数据失败: {str(e)}")
def is_valid_email(self, email: str) -> bool:
"""
验证邮箱格式
@param email: 邮箱地址
@return: 邮箱格式是否有效
"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
def is_valid_username(self, username: str) -> bool:
"""
验证用户名是否符合要求
@param username: 用户名
@return: 用户名是否有效
"""
# 用户名长度应在3-20个字符之间
if not (3 <= len(username) <= 20):
return False
# 用户名只能包含字母、数字和下划线
pattern = r'^[a-zA-Z0-9_]+$'
return re.match(pattern, username) is not None
def generate_registration_code(self) -> str:
"""
生成注册码
@return: 6位数字注册码
"""
return str(random.randint(100000, 999999))
def hash_password(self, password: str) -> str:
"""
对密码进行哈希处理
@param password: 原始密码
@return: 哈希后的密码
"""
return hashlib.sha256(password.encode('utf-8')).hexdigest()
def is_valid_password(self, password: str) -> bool:
"""
验证密码是否符合要求
@param password: 密码
@return: 密码是否有效
"""
if not (6 <= len(password) <= 10):
return False
has_lower = any(c.islower() for c in password)
has_upper = any(c.isupper() for c in password)
has_digit = any(c.isdigit() for c in password)
return has_lower and has_upper and has_digit
def register_user(self, email: str, username: str) -> bool:
"""
注册新用户发送注册码
@param email: 用户邮箱
@param username: 用户名
@return: 是否成功发送注册码
"""
if not self.is_valid_email(email):
messagebox.showerror("错误", "邮箱格式不正确")
return False
if not self.is_valid_username(username):
messagebox.showerror("错误", "用户名应为3-20位只能包含字母、数字和下划线")
return False
# 检查邮箱是否已被注册
if email in self.users and self.users[email].is_registered:
messagebox.showerror("错误", "该邮箱已注册")
return False
# 检查用户名是否已被使用
for user in self.users.values():
if user.username == username and user.is_registered:
messagebox.showerror("错误", "该用户名已存在")
return False
registration_code = self.generate_registration_code()
user = User(email=email, username=username, registration_code=registration_code)
self.users[email] = user
# 尝试发送邮件
if self.send_registration_code_via_email(email, registration_code):
self.save_users()
messagebox.showinfo("成功", "注册码已发送到您的邮箱,请查收")
return True
else:
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: 用户输入的注册码
@return: 注册码是否正确
"""
if email not in self.users:
messagebox.showerror("错误", "请先获取注册码")
return False
user = self.users[email]
if user.registration_code != code:
messagebox.showerror("错误", "注册码不正确")
return False
return True
def set_password(self, email: str, password: str) -> bool:
"""
设置用户密码
@param email: 用户邮箱
@param password: 用户密码
@return: 是否设置成功
"""
if not self.is_valid_password(password):
messagebox.showerror("错误", "密码必须为6-10位且包含大小写字母和数字")
return False
if email not in self.users:
messagebox.showerror("错误", "用户不存在")
return False
user = self.users[email]
user.password_hash = self.hash_password(password)
user.is_registered = True
self.save_users()
return True
def login(self, username: str, password: str) -> bool:
"""
用户登录
@param username: 用户名
@param password: 用户密码
@return: 是否登录成功
"""
# 根据用户名查找用户
user = None
for u in self.users.values():
if u.username == username:
user = u
break
if user is None:
messagebox.showerror("错误", "用户不存在")
return False
if not user.is_registered:
messagebox.showerror("错误", "请先完成注册")
return False
if user.password_hash != self.hash_password(password):
messagebox.showerror("错误", "密码不正确")
return False
self.current_user = user
return True
def change_password(self, old_password: str, new_password: str) -> bool:
"""
修改用户密码
@param old_password: 原密码
@param new_password: 新密码
@return: 是否修改成功
"""
if not self.current_user:
messagebox.showerror("错误", "用户未登录")
return False
if self.current_user.password_hash != self.hash_password(old_password):
messagebox.showerror("错误", "原密码不正确")
return False
if not self.is_valid_password(new_password):
messagebox.showerror("错误", "新密码必须为6-10位且包含大小写字母和数字")
return False
self.current_user.password_hash = self.hash_password(new_password)
self.save_users()
return True
def logout(self) -> None:
"""
用户登出
"""
self.current_user = None
def delete_account(self, email: str, password: str) -> bool:
"""
注销账户
@param email: 用户邮箱
@param password: 用户密码
@return: 是否注销成功
"""
if not self.is_valid_email(email):
messagebox.showerror("错误", "邮箱格式不正确")
return False
# 检查用户是否存在
if email not in self.users:
messagebox.showerror("错误", "该邮箱未注册")
return False
user = self.users[email]
# 检查用户是否已完成注册
if not user.is_registered:
messagebox.showerror("错误", "该账户未完成注册")
return False
# 验证密码
if user.password_hash != self.hash_password(password):
messagebox.showerror("错误", "密码不正确")
return False
# 删除用户
del self.users[email]
# 如果当前用户是被删除的用户,则登出
if self.current_user and self.current_user.email == email:
self.current_user = None
self.save_users()
return True

@ -0,0 +1,8 @@
{
"1426688201@qq.com": {
"username": "111",
"password_hash": "c4318372f98f4c46ed3a32c16ee4d7a76c832886d887631c0294b3314f34edf1",
"registration_code": "939598",
"is_registered": true
}
}
Loading…
Cancel
Save