You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
math_test/gui.py

590 lines
23 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
数学学习软件 - GUI界面模块
使用Tkinter实现桌面应用界面
"""
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
from typing import List, Dict, Optional
from data_manager import DataManager, QuestionGenerator, ScoreCalculator
from email_config_gui import EmailConfigWindow
class MathLearningApp:
"""数学学习软件主应用类"""
def __init__(self):
self.root = tk.Tk()
self.root.title("数学学习软件")
self.root.geometry("800x600")
self.root.resizable(False, False)
# 初始化数据管理器
self.data_manager = DataManager()
self.question_generator = QuestionGenerator()
self.score_calculator = ScoreCalculator()
# 当前测试相关变量
self.current_questions = []
self.current_question_index = 0
self.user_answers = []
self.selected_level = ""
# 创建主框架
self.main_frame = ttk.Frame(self.root, padding="20")
self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
self.main_frame.columnconfigure(0, weight=1)
# 显示欢迎界面
self.show_welcome_screen()
def clear_frame(self):
"""清空主框架"""
for widget in self.main_frame.winfo_children():
widget.destroy()
def show_welcome_screen(self):
"""显示欢迎界面"""
self.clear_frame()
# 标题
title_label = ttk.Label(self.main_frame, text="数学学习软件",
font=("Arial", 24, "bold"))
title_label.grid(row=0, column=0, pady=30)
# 按钮框架
button_frame = ttk.Frame(self.main_frame)
button_frame.grid(row=1, column=0, pady=20)
# 注册按钮
register_btn = ttk.Button(button_frame, text="用户注册",
command=self.show_register_screen, width=15)
register_btn.grid(row=0, column=0, padx=10, pady=10)
# 登录按钮
login_btn = ttk.Button(button_frame, text="用户登录",
command=self.show_login_screen, width=15)
login_btn.grid(row=0, column=1, padx=10, pady=10)
# 邮箱配置按钮
config_btn = ttk.Button(button_frame, text="邮箱配置",
command=self.show_email_config, width=15)
config_btn.grid(row=1, column=0, columnspan=2, pady=5)
# 退出按钮
exit_btn = ttk.Button(button_frame, text="退出程序",
command=self.root.quit, width=15)
exit_btn.grid(row=2, column=0, columnspan=2, pady=10)
def show_register_screen(self):
"""显示注册界面"""
self.clear_frame()
# 标题
title_label = ttk.Label(self.main_frame, text="用户注册",
font=("Arial", 18, "bold"))
title_label.grid(row=0, column=0, pady=20)
# 输入框架
input_frame = ttk.Frame(self.main_frame)
input_frame.grid(row=1, column=0, pady=10)
# 用户名输入
ttk.Label(input_frame, text="用户名:").grid(row=0, column=0, padx=5, pady=5)
self.username_entry = ttk.Entry(input_frame, width=30)
self.username_entry.grid(row=0, column=1, padx=5, pady=5)
# 邮箱输入
ttk.Label(input_frame, text="邮箱地址:").grid(row=1, column=0, padx=5, pady=5)
self.email_entry = ttk.Entry(input_frame, width=30)
self.email_entry.grid(row=1, column=1, padx=5, pady=5)
# 提示信息
info_label = ttk.Label(self.main_frame,
text="注册码将发送到您的邮箱,请确保邮箱地址正确",
font=("Arial", 10))
info_label.grid(row=2, column=0, pady=10)
# 注册按钮
register_btn = ttk.Button(self.main_frame, text="发送注册码",
command=self.handle_register)
register_btn.grid(row=3, column=0, pady=20)
# 返回按钮
back_btn = ttk.Button(self.main_frame, text="返回",
command=self.show_welcome_screen)
back_btn.grid(row=4, column=0, pady=10)
def handle_register(self):
"""处理用户注册"""
username = self.username_entry.get().strip()
email = self.email_entry.get().strip()
if not username:
messagebox.showerror("错误", "请输入用户名")
return
if not email:
messagebox.showerror("错误", "请输入邮箱地址")
return
# 用户名格式验证
if len(username) < 2 or len(username) > 20:
messagebox.showerror("错误", "用户名长度应在2-20个字符之间")
return
# 简单的邮箱格式验证
if "@" not in email or "." not in email:
messagebox.showerror("错误", "请输入有效的邮箱地址")
return
try:
# 显示发送中的提示
messagebox.showinfo("发送中", "正在发送验证码到您的邮箱,请稍候...")
success = self.data_manager.register_user(email, username)
if success:
messagebox.showinfo("发送成功",
f"验证码已发送到 {email}\n请查收邮件并输入验证码")
self.show_verification_screen(email)
else:
messagebox.showerror("发送失败", "验证码发送失败,请稍后重试")
except ValueError as e:
messagebox.showerror("注册失败", str(e))
def show_verification_screen(self, email: str):
"""显示验证码输入界面"""
self.clear_frame()
# 标题
title_label = ttk.Label(self.main_frame, text="验证注册码",
font=("Arial", 18, "bold"))
title_label.grid(row=0, column=0, pady=20)
# 提示信息
info_label = ttk.Label(self.main_frame, text=f"验证码已发送到 {email}")
info_label.grid(row=1, column=0, pady=5)
info_label2 = ttk.Label(self.main_frame, text="请查收邮件并输入6位数字验证码",
font=("Arial", 10))
info_label2.grid(row=2, column=0, pady=5)
# 验证码输入
code_frame = ttk.Frame(self.main_frame)
code_frame.grid(row=3, column=0, pady=10)
ttk.Label(code_frame, text="注册码:").grid(row=0, column=0, padx=5)
self.code_entry = ttk.Entry(code_frame, width=20)
self.code_entry.grid(row=0, column=1, padx=5)
# 验证按钮
verify_btn = ttk.Button(self.main_frame, text="验证",
command=lambda: self.handle_verification(email))
verify_btn.grid(row=4, column=0, pady=20)
# 返回按钮
back_btn = ttk.Button(self.main_frame, text="返回",
command=self.show_register_screen)
back_btn.grid(row=5, column=0, pady=10)
def handle_verification(self, email: str):
"""处理验证码验证"""
code = self.code_entry.get().strip()
if not code:
messagebox.showerror("错误", "请输入注册码")
return
if self.data_manager.verify_registration(email, code):
messagebox.showinfo("验证成功", "注册码验证成功!")
self.show_password_setup_screen(email)
else:
messagebox.showerror("验证失败", "注册码错误,请重新输入")
def show_password_setup_screen(self, email: str):
"""显示密码设置界面"""
self.clear_frame()
# 标题
title_label = ttk.Label(self.main_frame, text="设置密码",
font=("Arial", 18, "bold"))
title_label.grid(row=0, column=0, pady=20)
# 密码要求说明
info_label = ttk.Label(self.main_frame,
text="密码要求6-10位必须包含大小写字母和数字")
info_label.grid(row=1, column=0, pady=10)
# 密码输入框架
password_frame = ttk.Frame(self.main_frame)
password_frame.grid(row=2, column=0, pady=20)
# 第一次密码输入
ttk.Label(password_frame, text="输入密码:").grid(row=0, column=0, padx=5, pady=5)
self.password1_entry = ttk.Entry(password_frame, show="*", width=20)
self.password1_entry.grid(row=0, column=1, padx=5, pady=5)
# 第二次密码输入
ttk.Label(password_frame, text="确认密码:").grid(row=1, column=0, padx=5, pady=5)
self.password2_entry = ttk.Entry(password_frame, show="*", width=20)
self.password2_entry.grid(row=1, column=1, padx=5, pady=5)
# 设置按钮
set_btn = ttk.Button(self.main_frame, text="设置密码",
command=lambda: self.handle_password_setup(email))
set_btn.grid(row=3, column=0, pady=20)
# 返回按钮
back_btn = ttk.Button(self.main_frame, text="返回",
command=self.show_welcome_screen)
back_btn.grid(row=4, column=0, pady=10)
def handle_password_setup(self, email: str):
"""处理密码设置"""
password1 = self.password1_entry.get()
password2 = self.password2_entry.get()
if not password1 or not password2:
messagebox.showerror("错误", "请输入密码")
return
if password1 != password2:
messagebox.showerror("错误", "两次输入的密码不一致")
return
if self.data_manager.set_password(email, password1):
messagebox.showinfo("成功", "密码设置成功!")
self.show_welcome_screen()
else:
messagebox.showerror("错误", "密码格式不符合要求")
def show_login_screen(self):
"""显示登录界面"""
self.clear_frame()
# 标题
title_label = ttk.Label(self.main_frame, text="用户登录",
font=("Arial", 18, "bold"))
title_label.grid(row=0, column=0, pady=20)
# 登录表单
login_frame = ttk.Frame(self.main_frame)
login_frame.grid(row=1, column=0, pady=20)
# 邮箱输入
ttk.Label(login_frame, text="邮箱:").grid(row=0, column=0, padx=5, pady=10)
self.login_email_entry = ttk.Entry(login_frame, width=25)
self.login_email_entry.grid(row=0, column=1, padx=5, pady=10)
# 密码输入
ttk.Label(login_frame, text="密码:").grid(row=1, column=0, padx=5, pady=10)
self.login_password_entry = ttk.Entry(login_frame, show="*", width=25)
self.login_password_entry.grid(row=1, column=1, padx=5, pady=10)
# 按钮框架
button_frame = ttk.Frame(self.main_frame)
button_frame.grid(row=2, column=0, pady=20)
# 登录按钮
login_btn = ttk.Button(button_frame, text="登录",
command=self.handle_login)
login_btn.grid(row=0, column=0, padx=10)
# 修改密码按钮
change_pwd_btn = ttk.Button(button_frame, text="修改密码",
command=self.show_change_password_screen)
change_pwd_btn.grid(row=0, column=1, padx=10)
# 返回按钮
back_btn = ttk.Button(self.main_frame, text="返回",
command=self.show_welcome_screen)
back_btn.grid(row=3, column=0, pady=10)
def handle_login(self):
"""处理用户登录"""
email = self.login_email_entry.get().strip()
password = self.login_password_entry.get()
if not email or not password:
messagebox.showerror("错误", "请输入邮箱和密码")
return
if self.data_manager.login(email, password):
messagebox.showinfo("成功", "登录成功!")
self.show_level_selection_screen()
else:
messagebox.showerror("错误", "邮箱或密码错误")
def show_change_password_screen(self):
"""显示修改密码界面"""
# 首先需要登录验证
email = self.login_email_entry.get().strip()
password = self.login_password_entry.get()
if not email or not password:
messagebox.showerror("错误", "请先输入邮箱和密码进行身份验证")
return
if not self.data_manager.login(email, password):
messagebox.showerror("错误", "邮箱或密码错误,无法修改密码")
return
self.clear_frame()
# 标题
title_label = ttk.Label(self.main_frame, text="修改密码",
font=("Arial", 18, "bold"))
title_label.grid(row=0, column=0, pady=20)
# 密码输入框架
password_frame = ttk.Frame(self.main_frame)
password_frame.grid(row=1, column=0, pady=20)
# 原密码
ttk.Label(password_frame, text="原密码:").grid(row=0, column=0, padx=5, pady=10)
self.old_password_entry = ttk.Entry(password_frame, show="*", width=20)
self.old_password_entry.grid(row=0, column=1, padx=5, pady=10)
# 新密码
ttk.Label(password_frame, text="新密码:").grid(row=1, column=0, padx=5, pady=10)
self.new_password1_entry = ttk.Entry(password_frame, show="*", width=20)
self.new_password1_entry.grid(row=1, column=1, padx=5, pady=10)
# 确认新密码
ttk.Label(password_frame, text="确认新密码:").grid(row=2, column=0, padx=5, pady=10)
self.new_password2_entry = ttk.Entry(password_frame, show="*", width=20)
self.new_password2_entry.grid(row=2, column=1, padx=5, pady=10)
# 修改按钮
change_btn = ttk.Button(self.main_frame, text="修改密码",
command=self.handle_change_password)
change_btn.grid(row=2, column=0, pady=20)
# 返回按钮
back_btn = ttk.Button(self.main_frame, text="返回",
command=self.show_login_screen)
back_btn.grid(row=3, column=0, pady=10)
def handle_change_password(self):
"""处理密码修改"""
old_password = self.old_password_entry.get()
new_password1 = self.new_password1_entry.get()
new_password2 = self.new_password2_entry.get()
if not all([old_password, new_password1, new_password2]):
messagebox.showerror("错误", "请填写所有密码字段")
return
if new_password1 != new_password2:
messagebox.showerror("错误", "两次输入的新密码不一致")
return
if self.data_manager.change_password(old_password, new_password1):
messagebox.showinfo("成功", "密码修改成功!")
self.show_login_screen()
else:
messagebox.showerror("错误", "原密码错误或新密码格式不符合要求")
def show_level_selection_screen(self):
"""显示学习阶段选择界面"""
self.clear_frame()
# 欢迎信息
username = self.data_manager.get_current_username()
welcome_label = ttk.Label(self.main_frame, text=f"欢迎,{username}",
font=("Arial", 14))
welcome_label.grid(row=0, column=0, pady=10)
# 标题
title_label = ttk.Label(self.main_frame, text="选择学习阶段",
font=("Arial", 18, "bold"))
title_label.grid(row=1, column=0, pady=20)
# 阶段选择按钮
level_frame = ttk.Frame(self.main_frame)
level_frame.grid(row=2, column=0, pady=20)
levels = ["小学", "初中", "高中"]
for i, level in enumerate(levels):
btn = ttk.Button(level_frame, text=level, width=15,
command=lambda l=level: self.select_level(l))
btn.grid(row=i//2, column=i%2, padx=20, pady=15)
# 登出按钮
logout_btn = ttk.Button(self.main_frame, text="登出",
command=self.handle_logout)
logout_btn.grid(row=3, column=0, pady=30)
def select_level(self, level: str):
"""选择学习阶段"""
self.selected_level = level
self.show_question_count_input()
def show_question_count_input(self):
"""显示题目数量输入界面"""
self.clear_frame()
# 标题
title_label = ttk.Label(self.main_frame,
text=f"{self.selected_level}数学练习",
font=("Arial", 18, "bold"))
title_label.grid(row=0, column=0, pady=30)
# 题目数量输入
count_frame = ttk.Frame(self.main_frame)
count_frame.grid(row=1, column=0, pady=20)
ttk.Label(count_frame, text="请输入题目数量:").grid(row=0, column=0, padx=10)
self.question_count_entry = ttk.Entry(count_frame, width=10)
self.question_count_entry.grid(row=0, column=1, padx=10)
self.question_count_entry.insert(0, "10") # 默认10题
# 开始按钮
start_btn = ttk.Button(self.main_frame, text="开始答题",
command=self.start_test)
start_btn.grid(row=2, column=0, pady=20)
# 返回按钮
back_btn = ttk.Button(self.main_frame, text="返回",
command=self.show_level_selection_screen)
back_btn.grid(row=3, column=0, pady=10)
def start_test(self):
"""开始测试"""
try:
question_count = int(self.question_count_entry.get())
if question_count <= 0:
raise ValueError("题目数量必须大于0")
except ValueError:
messagebox.showerror("错误", "请输入有效的题目数量")
return
# 生成题目
self.current_questions = self.question_generator.generate_test_paper(
self.selected_level, question_count)
self.current_question_index = 0
self.user_answers = []
if not self.current_questions:
messagebox.showerror("错误", "题目生成失败")
return
self.show_question()
def show_question(self):
"""显示当前题目"""
if self.current_question_index >= len(self.current_questions):
self.show_result()
return
self.clear_frame()
question_data = self.current_questions[self.current_question_index]
# 进度显示
progress_label = ttk.Label(self.main_frame,
text=f"{self.current_question_index + 1} 题 / 共 {len(self.current_questions)}")
progress_label.grid(row=0, column=0, pady=10)
# 题目显示
question_label = ttk.Label(self.main_frame, text=question_data["question"],
font=("Arial", 16, "bold"))
question_label.grid(row=1, column=0, pady=30)
# 选项
self.answer_var = tk.StringVar()
options_frame = ttk.Frame(self.main_frame)
options_frame.grid(row=2, column=0, pady=20)
for i, option in enumerate(question_data["options"]):
radio_btn = ttk.Radiobutton(options_frame, text=f"{chr(65+i)}. {option}",
variable=self.answer_var, value=str(option))
radio_btn.grid(row=i, column=0, sticky=tk.W, pady=5)
# 提交按钮
submit_btn = ttk.Button(self.main_frame, text="提交答案",
command=self.submit_answer)
submit_btn.grid(row=3, column=0, pady=30)
def submit_answer(self):
"""提交答案"""
selected_answer = self.answer_var.get()
if not selected_answer:
messagebox.showerror("错误", "请选择一个答案")
return
# 记录用户答案
question_data = self.current_questions[self.current_question_index]
is_correct = int(selected_answer) == question_data["correct_answer"]
self.user_answers.append({
"question": question_data["question"],
"user_answer": int(selected_answer),
"correct_answer": question_data["correct_answer"],
"is_correct": is_correct
})
# 下一题
self.current_question_index += 1
self.show_question()
def show_result(self):
"""显示测试结果"""
self.clear_frame()
# 计算分数
correct_count = sum(1 for answer in self.user_answers if answer["is_correct"])
total_count = len(self.user_answers)
score = self.score_calculator.calculate_score(correct_count, total_count)
comment = self.score_calculator.get_grade_comment(score)
# 结果显示
result_frame = ttk.Frame(self.main_frame)
result_frame.grid(row=0, column=0, pady=30)
ttk.Label(result_frame, text="测试完成!",
font=("Arial", 20, "bold")).grid(row=0, column=0, pady=10)
ttk.Label(result_frame, text=f"正确题数: {correct_count}/{total_count}",
font=("Arial", 14)).grid(row=1, column=0, pady=5)
ttk.Label(result_frame, text=f"得分: {score}",
font=("Arial", 16, "bold")).grid(row=2, column=0, pady=10)
ttk.Label(result_frame, text=comment,
font=("Arial", 12)).grid(row=3, column=0, pady=5)
# 按钮框架
button_frame = ttk.Frame(self.main_frame)
button_frame.grid(row=1, column=0, pady=30)
# 继续做题按钮
continue_btn = ttk.Button(button_frame, text="继续做题",
command=self.show_level_selection_screen)
continue_btn.grid(row=0, column=0, padx=20)
# 退出按钮
exit_btn = ttk.Button(button_frame, text="退出",
command=self.handle_logout)
exit_btn.grid(row=0, column=1, padx=20)
def handle_logout(self):
"""处理登出"""
self.data_manager.logout()
self.show_welcome_screen()
def show_email_config(self):
"""显示邮箱配置界面"""
EmailConfigWindow(self.root, self.data_manager)
def run(self):
"""运行应用"""
self.root.mainloop()
if __name__ == "__main__":
app = MathLearningApp()
app.run()