""" 数学学习软件 - 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()