From 0e10ec4b5a8a1924fa710fe04d30b57bab960e9d Mon Sep 17 00:00:00 2001 From: hewendi23 <167489948+hewendi23@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:57:31 +0800 Subject: [PATCH 1/7] 1.0 --- app.py | 196 ++++++++++++++++++++++++++++++++++++++++++++++ main.py | 5 ++ quiz_generator.py | 96 +++++++++++++++++++++++ user_manager.py | 105 +++++++++++++++++++++++++ 4 files changed, 402 insertions(+) create mode 100644 app.py create mode 100644 main.py create mode 100644 quiz_generator.py create mode 100644 user_manager.py diff --git a/app.py b/app.py new file mode 100644 index 0000000..fb9e848 --- /dev/null +++ b/app.py @@ -0,0 +1,196 @@ +# src/app.py +import customtkinter as ctk +import random +import string +from user_manager import UserManager, send_verification_email +from quiz_generator import get_generator + +class MathApp(ctk.CTk): + def __init__(self): + super().__init__() + self.title("小初高数学学习软件") + self.geometry("500x450") + ctk.set_appearance_mode("System") + + self.user_manager = UserManager() + self.current_user = None + + container = ctk.CTkFrame(self) + container.pack(side="top", fill="both", expand=True) + container.grid_rowconfigure(0, weight=1) + container.grid_columnconfigure(0, weight=1) + + self.frames = {} + # 还原:只包含基础的五个页面 + for F in (LoginPage, RegisterPage, MainPage, QuizPage, ScorePage): + page_name = F.__name__ + frame = F(parent=container, controller=self) + self.frames[page_name] = frame + frame.grid(row=0, column=0, sticky="nsew") + + self.show_frame("LoginPage") + + def show_frame(self, page_name): + frame = self.frames[page_name] + frame.tkraise() + +class LoginPage(ctk.CTkFrame): + def __init__(self, parent, controller): + super().__init__(parent) + self.controller = controller + ctk.CTkLabel(self, text="登录", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=20) + self.email_entry = ctk.CTkEntry(self, placeholder_text="邮箱", width=200) + self.email_entry.pack(pady=10) + self.password_entry = ctk.CTkEntry(self, placeholder_text="密码", show="*", width=200) + self.password_entry.pack(pady=10) + self.error_label = ctk.CTkLabel(self, text="", text_color="red") + self.error_label.pack(pady=5) + ctk.CTkButton(self, text="登录", command=self.login).pack(pady=10) + ctk.CTkButton(self, text="没有账户?去注册", fg_color="transparent", command=lambda: controller.show_frame("RegisterPage")).pack() + + def login(self): + email, password = self.email_entry.get(), self.password_entry.get() + if self.controller.user_manager.validate_login(email, password): + self.controller.current_user = email + self.error_label.configure(text="") + self.controller.show_frame("MainPage") + else: + self.error_label.configure(text="邮箱或密码错误") + +class RegisterPage(ctk.CTkFrame): + def __init__(self, parent, controller): + super().__init__(parent) + self.controller = controller + self.code = "" + self.cooldown_time = 0 + ctk.CTkLabel(self, text="注册新账户", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=10) + self.email_entry = ctk.CTkEntry(self, placeholder_text="邮箱", width=250) + self.email_entry.pack(pady=5) + self.send_code_button = ctk.CTkButton(self, text="发送验证码", command=self.send_code, width=250) + self.send_code_button.pack(pady=5) + self.code_entry = ctk.CTkEntry(self, placeholder_text="输入收到的邮件验证码", width=250) + self.code_entry.pack(pady=5) + self.password_entry = ctk.CTkEntry(self, placeholder_text="密码 (6-10位,含大小写和数字)", show="*", width=250) + self.password_entry.pack(pady=5) + self.confirm_password_entry = ctk.CTkEntry(self, placeholder_text="确认密码", show="*", width=250) + self.confirm_password_entry.pack(pady=5) + self.info_label = ctk.CTkLabel(self, text="", text_color="red") + self.info_label.pack(pady=5) + ctk.CTkButton(self, text="注册", command=self.register).pack(pady=10) + ctk.CTkButton(self, text="返回登录", fg_color="transparent", command=lambda: controller.show_frame("LoginPage")).pack() + + def update_cooldown(self): + if self.cooldown_time > 0: + self.send_code_button.configure(text=f"{self.cooldown_time}秒后可重试") + self.cooldown_time -= 1 + self.after(1000, self.update_cooldown) + else: + self.send_code_button.configure(text="发送验证码", state="normal") + + def send_code(self): + if self.cooldown_time > 0: return + email = self.email_entry.get().strip() + if not email: + self.info_label.configure(text="请输入邮箱地址。", text_color="red") + return + if self.controller.user_manager.is_email_registered(email): + self.info_label.configure(text="该邮箱已被注册。", text_color="red") + return + + self.code = ''.join(random.choices(string.digits, k=6)) + self.info_label.configure(text="正在发送邮件,请稍候...", text_color="gray") + success = send_verification_email(email, self.code) + + if success: + self.info_label.configure(text="邮件验证码已发送至您的邮箱,请查收。", text_color="green") + self.cooldown_time = 60 + self.send_code_button.configure(state="disabled") + self.update_cooldown() + else: + self.info_label.configure(text="邮件发送失败,请检查邮箱或网络。", text_color="red") + self.code = "" + + def register(self): + email, user_code, password, confirm_password = self.email_entry.get().strip(), self.code_entry.get().strip(), self.password_entry.get(), self.confirm_password_entry.get() + if not self.code or user_code != self.code: + self.info_label.configure(text="邮件验证码错误", text_color="red") + return + if password != confirm_password: + self.info_label.configure(text="两次输入的密码不一致", text_color="red") + return + if not self.controller.user_manager.validate_password_format(password): + self.info_label.configure(text="密码格式不符合要求", text_color="red") + return + success, msg = self.controller.user_manager.register_user(email, password) + self.info_label.configure(text=msg, text_color="green" if success else "red") + if success: self.code = "" + +class MainPage(ctk.CTkFrame): + def __init__(self, parent, controller): + super().__init__(parent) + self.controller = controller + ctk.CTkLabel(self, text="选择学习阶段", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=20) + ctk.CTkButton(self, text="小学", command=lambda: self.start_quiz("小学"), height=40, width=150).pack(pady=10) + ctk.CTkButton(self, text="初中", command=lambda: self.start_quiz("初中"), height=40, width=150).pack(pady=10) + ctk.CTkButton(self, text="高中", command=lambda: self.start_quiz("高中"), height=40, width=150).pack(pady=10) + ctk.CTkButton(self, text="退出登录", fg_color="transparent", command=lambda: controller.show_frame("LoginPage")).pack(side="bottom", pady=20) + + def start_quiz(self, level): + dialog = ctk.CTkInputDialog(text="请输入题目数量 (1-20):", title="设置题目数量") + num_str = dialog.get_input() + if num_str: + try: + num = int(num_str) + if 1 <= num <= 20: + self.controller.frames["QuizPage"].setup_quiz(level, num) + self.controller.show_frame("QuizPage") + except ValueError: pass + +class QuizPage(ctk.CTkFrame): + def __init__(self, parent, controller): + super().__init__(parent) + self.controller = controller + self.question_label = ctk.CTkLabel(self, text="问题", font=ctk.CTkFont(size=18)) + self.question_label.pack(pady=20, padx=20) + self.radio_var = ctk.IntVar(value=-1) + self.radio_buttons = [] + for i in range(4): + rb = ctk.CTkRadioButton(self, text="", variable=self.radio_var, value=i, font=ctk.CTkFont(size=14)) + rb.pack(anchor="w", padx=100, pady=5) + self.radio_buttons.append(rb) + self.submit_button = ctk.CTkButton(self, text="下一题", command=self.next_question) + self.submit_button.pack(pady=20) + def setup_quiz(self, level, num_questions): + self.questions = get_generator(level).generate_bunch(num_questions) + self.current_question_index = 0 + self.user_answers = [] + self.display_question() + def display_question(self): + self.radio_var.set(-1) + q = self.questions[self.current_question_index] + self.question_label.configure(text=f"{self.current_question_index + 1}. {q.text} = ?") + for i, option in enumerate(q.options): self.radio_buttons[i].configure(text=str(option)) + self.submit_button.configure(text="提交试卷" if self.current_question_index == len(self.questions) - 1 else "下一题") + def next_question(self): + if self.radio_var.get() == -1: return + self.user_answers.append(self.radio_var.get()) + self.current_question_index += 1 + if self.current_question_index < len(self.questions): self.display_question() + else: self.calculate_score() + def calculate_score(self): + correct_count = sum(1 for i, q in enumerate(self.questions) if self.user_answers[i] == q.correct_answer_index) + total = len(self.questions) + score_percent = (correct_count / total) * 100 + self.controller.frames["ScorePage"].show_score(correct_count, total, score_percent) + self.controller.show_frame("ScorePage") + +class ScorePage(ctk.CTkFrame): + def __init__(self, parent, controller): + super().__init__(parent) + self.controller = controller + self.score_label = ctk.CTkLabel(self, text="", font=ctk.CTkFont(size=24)) + self.score_label.pack(pady=40, padx=20) + ctk.CTkButton(self, text="继续做题", command=lambda: controller.show_frame("MainPage")).pack(pady=10) + ctk.CTkButton(self, text="退出程序", command=controller.quit).pack(pady=10) + def show_score(self, correct, total, percent): + self.score_label.configure(text=f"答对了 {correct} / {total} 题\n\n得分: {percent:.2f}%") \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..931f68b --- /dev/null +++ b/main.py @@ -0,0 +1,5 @@ +from app import MathApp + +if __name__ == "__main__": + app = MathApp() + app.mainloop() \ No newline at end of file diff --git a/quiz_generator.py b/quiz_generator.py new file mode 100644 index 0000000..1ec63b0 --- /dev/null +++ b/quiz_generator.py @@ -0,0 +1,96 @@ + +import random +from abc import ABC, abstractmethod + +# 安全的表达式求值函数 +def safe_eval(expression): + try: + return eval(expression, {"__builtins__": None}, {}) + except Exception: + return None + +class Question: + def __init__(self, text, options, correct_answer_index): + self.text = text + self.options = options + self.correct_answer_index = correct_answer_index + +class QuestionGenerator(ABC): + def generate_bunch(self, count): + questions = [] + generated_texts = set() + while len(questions) < count: + text, answer = self._create_question_and_answer() + if text not in generated_texts: + options = self._generate_options(answer) + correct_index = options.index(answer) + + questions.append(Question(text, options, correct_index)) + generated_texts.add(text) + return questions + + def _generate_options(self, correct_answer): + options = {correct_answer} + while len(options) < 4: + offset = random.randint(-10, 10) + if offset == 0: + offset = random.choice([-1, 1]) * 10 + + distractor = correct_answer + offset + options.add(distractor) + + option_list = list(options) + random.shuffle(option_list) + return option_list + + @abstractmethod + def _create_question_and_answer(self): + pass + +class PrimarySchoolGenerator(QuestionGenerator): + def _create_question_and_answer(self): + ops = ['+', '-', '*', '/'] + op = random.choice(ops) + + if op == '+': + num1, num2 = random.randint(1, 100), random.randint(1, 100) + expr = f"{num1} + {num2}" + elif op == '-': + num1, num2 = random.randint(1, 100), random.randint(1, 100) + if num1 < num2: num1, num2 = num2, num1 + expr = f"{num1} - {num2}" + elif op == '*': + num1, num2 = random.randint(1, 20), random.randint(1, 20) + expr = f"{num1} * {num2}" + else: # op == '/' + divisor = random.randint(2, 10) + answer = random.randint(2, 20) + dividend = divisor * answer + expr = f"{dividend} / {divisor}" + + return expr, int(safe_eval(expr)) + +class MiddleSchoolGenerator(PrimarySchoolGenerator): + def _create_question_and_answer(self): + if random.choice([True, False]): + return super()._create_question_and_answer() + else: + base = random.randint(2, 10) + expr = f"{base}**2" + return expr, int(safe_eval(expr)) + +class HighSchoolGenerator(MiddleSchoolGenerator): + def _create_question_and_answer(self): + # 简化版高中题,可扩展为三角函数等 + return super()._create_question_and_answer() + +def get_generator(level): + generators = { + "小学": PrimarySchoolGenerator, + "初中": MiddleSchoolGenerator, + "高中": HighSchoolGenerator, + } + generator_class = generators.get(level) + if generator_class: + return generator_class() + raise ValueError("无效的等级") \ No newline at end of file diff --git a/user_manager.py b/user_manager.py new file mode 100644 index 0000000..befba82 --- /dev/null +++ b/user_manager.py @@ -0,0 +1,105 @@ +# src/user_manager.py +import json +import hashlib +import os +import re +import smtplib +import ssl +from email.message import EmailMessage + +def send_verification_email(recipient_email, code): + """ + 发送验证码邮件的函数。 + """ + # --- 配置区域 --- + SENDER_EMAIL = "876413020@qq.com" + APP_PASSWORD = "wbeyuimdbvkxbdgf" + SMTP_SERVER = "smtp.qq.com" + SMTP_PORT = 465 + + # --- 构建邮件内容 --- + msg = EmailMessage() + msg['Subject'] = "【数学学习软件】您的注册验证码" + msg['From'] = SENDER_EMAIL + msg['To'] = recipient_email + msg.set_content( + f"您好!\n\n" + f"感谢您注册数学学习软件。您的验证码是:\n\n" + f"【 {code} 】\n\n" + f"请在5分钟内使用。如果不是您本人操作,请忽略此邮件。" + ) + + # --- 发送邮件(健壮的结构)--- + smtp_server = None + try: + context = ssl.create_default_context() + smtp_server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) + smtp_server.login(SENDER_EMAIL, APP_PASSWORD) + smtp_server.send_message(msg) + + print(f"邮件已成功发送至 {recipient_email}") + return True + + except smtplib.SMTPAuthenticationError: + print("邮件发送失败: 认证错误。请检查邮箱或授权码是否正确。") + return False + except Exception as e: + print(f"邮件发送时发生错误: {e}") + return False + finally: + if smtp_server: + try: + smtp_server.quit() + except Exception: + pass + +class UserManager: + def __init__(self): + self.users = self._load_users() + + def _load_users(self): + if os.path.exists("users.json"): + try: + with open("users.json", 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + return {} + return {} + + def _save_users(self): + with open("users.json", 'w', encoding='utf-8') as f: + json.dump(self.users, f, indent=4) + + def _hash_password(self, password, salt): + return hashlib.sha256((password + salt).encode()).hexdigest() + + def is_email_registered(self, email): + return email in self.users + + def validate_password_format(self, password): + """密码必须 6-10 位,包含大小写字母和数字""" + if 6 <= len(password) <= 10 and \ + re.search(r"[a-z]", password) and \ + re.search(r"[A-Z]", password) and \ + re.search(r"\d", password): + return True + return False + + def register_user(self, email, password): + if self.is_email_registered(email): + return False, "该邮箱已被注册。" + + salt = os.urandom(16).hex() + password_hash = self._hash_password(password, salt) + self.users[email] = {"password_hash": password_hash, "salt": salt} + self._save_users() + return True, "注册成功!" + + def validate_login(self, email, password): + if not self.is_email_registered(email): + return False + + user_data = self.users[email] + salt = user_data["salt"] + password_hash = self._hash_password(password, salt) + return password_hash == user_data["password_hash"] \ No newline at end of file -- 2.34.1 From 04d4f2745c793fa809f2546fe1e32f110a63c021 Mon Sep 17 00:00:00 2001 From: hewendi23 <167489948+hewendi23@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:59:00 +0800 Subject: [PATCH 2/7] 1.0 --- app.py => src/app.py | 0 main.py => src/main.py | 0 quiz_generator.py => src/quiz_generator.py | 0 user_manager.py => src/user_manager.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename app.py => src/app.py (100%) rename main.py => src/main.py (100%) rename quiz_generator.py => src/quiz_generator.py (100%) rename user_manager.py => src/user_manager.py (100%) diff --git a/app.py b/src/app.py similarity index 100% rename from app.py rename to src/app.py diff --git a/main.py b/src/main.py similarity index 100% rename from main.py rename to src/main.py diff --git a/quiz_generator.py b/src/quiz_generator.py similarity index 100% rename from quiz_generator.py rename to src/quiz_generator.py diff --git a/user_manager.py b/src/user_manager.py similarity index 100% rename from user_manager.py rename to src/user_manager.py -- 2.34.1 From adf512bee22756ffa00a3542b3fa453e2bb6733f Mon Sep 17 00:00:00 2001 From: wang421 <1393110457@qq.com> Date: Sat, 11 Oct 2025 16:18:15 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/META-INF/MANIFEST.MF | 3 + src/app.py | 196 --------- src/com/mathlearning/Main.java | 15 + .../controller/AuthController.java | 149 +++++++ .../controller/ExamController.java | 247 +++++++++++ .../controller/NavigationController.java | 86 ++++ src/com/mathlearning/model/Paper.java | 73 ++++ src/com/mathlearning/model/Question.java | 39 ++ .../mathlearning/model/QuestionGenerator.java | 296 +++++++++++++ src/com/mathlearning/model/User.java | 42 ++ src/com/mathlearning/model/UserManager.java | 118 ++++++ src/com/mathlearning/util/EmailConfig.java | 62 +++ src/com/mathlearning/util/EmailUtil.java | 133 ++++++ src/com/mathlearning/util/ExamFileUtil.java | 86 ++++ src/com/mathlearning/util/FileStorage.java | 52 +++ .../mathlearning/util/PasswordValidator.java | 21 + .../mathlearning/util/UsernameValidator.java | 16 + src/com/mathlearning/view/ExamView.java | 397 ++++++++++++++++++ .../mathlearning/view/LevelSelectionView.java | 75 ++++ src/com/mathlearning/view/LoginView.java | 110 +++++ .../mathlearning/view/QuestionCountView.java | 85 ++++ src/com/mathlearning/view/RegisterView.java | 210 +++++++++ src/com/mathlearning/view/ScoreView.java | 111 +++++ .../mathlearning/view/SetPasswordView.java | 100 +++++ src/email_config.properties.txt | 5 + src/exam_papers/wjw/2025-10-11-13-29-32.txt | 145 +++++++ src/exam_papers/wjw/2025-10-11-13-29-45.txt | 75 ++++ src/exam_papers/wjw/2025-10-11-13-30-14.txt | 75 ++++ src/exam_papers/wjw/2025-10-11-13-40-07.txt | 75 ++++ src/main.py | 5 - src/quiz_generator.py | 96 ----- src/user_manager.py | 105 ----- src/users.dat | Bin 0 -> 255 bytes src/users_email.dat | Bin 0 -> 310 bytes src/users_username.dat | Bin 0 -> 310 bytes 35 files changed, 2901 insertions(+), 402 deletions(-) create mode 100644 src/META-INF/MANIFEST.MF delete mode 100644 src/app.py create mode 100644 src/com/mathlearning/Main.java create mode 100644 src/com/mathlearning/controller/AuthController.java create mode 100644 src/com/mathlearning/controller/ExamController.java create mode 100644 src/com/mathlearning/controller/NavigationController.java create mode 100644 src/com/mathlearning/model/Paper.java create mode 100644 src/com/mathlearning/model/Question.java create mode 100644 src/com/mathlearning/model/QuestionGenerator.java create mode 100644 src/com/mathlearning/model/User.java create mode 100644 src/com/mathlearning/model/UserManager.java create mode 100644 src/com/mathlearning/util/EmailConfig.java create mode 100644 src/com/mathlearning/util/EmailUtil.java create mode 100644 src/com/mathlearning/util/ExamFileUtil.java create mode 100644 src/com/mathlearning/util/FileStorage.java create mode 100644 src/com/mathlearning/util/PasswordValidator.java create mode 100644 src/com/mathlearning/util/UsernameValidator.java create mode 100644 src/com/mathlearning/view/ExamView.java create mode 100644 src/com/mathlearning/view/LevelSelectionView.java create mode 100644 src/com/mathlearning/view/LoginView.java create mode 100644 src/com/mathlearning/view/QuestionCountView.java create mode 100644 src/com/mathlearning/view/RegisterView.java create mode 100644 src/com/mathlearning/view/ScoreView.java create mode 100644 src/com/mathlearning/view/SetPasswordView.java create mode 100644 src/email_config.properties.txt create mode 100644 src/exam_papers/wjw/2025-10-11-13-29-32.txt create mode 100644 src/exam_papers/wjw/2025-10-11-13-29-45.txt create mode 100644 src/exam_papers/wjw/2025-10-11-13-30-14.txt create mode 100644 src/exam_papers/wjw/2025-10-11-13-40-07.txt delete mode 100644 src/main.py delete mode 100644 src/quiz_generator.py delete mode 100644 src/user_manager.py create mode 100644 src/users.dat create mode 100644 src/users_email.dat create mode 100644 src/users_username.dat diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..6eea194 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.mathlearning.Main + diff --git a/src/app.py b/src/app.py deleted file mode 100644 index fb9e848..0000000 --- a/src/app.py +++ /dev/null @@ -1,196 +0,0 @@ -# src/app.py -import customtkinter as ctk -import random -import string -from user_manager import UserManager, send_verification_email -from quiz_generator import get_generator - -class MathApp(ctk.CTk): - def __init__(self): - super().__init__() - self.title("小初高数学学习软件") - self.geometry("500x450") - ctk.set_appearance_mode("System") - - self.user_manager = UserManager() - self.current_user = None - - container = ctk.CTkFrame(self) - container.pack(side="top", fill="both", expand=True) - container.grid_rowconfigure(0, weight=1) - container.grid_columnconfigure(0, weight=1) - - self.frames = {} - # 还原:只包含基础的五个页面 - for F in (LoginPage, RegisterPage, MainPage, QuizPage, ScorePage): - page_name = F.__name__ - frame = F(parent=container, controller=self) - self.frames[page_name] = frame - frame.grid(row=0, column=0, sticky="nsew") - - self.show_frame("LoginPage") - - def show_frame(self, page_name): - frame = self.frames[page_name] - frame.tkraise() - -class LoginPage(ctk.CTkFrame): - def __init__(self, parent, controller): - super().__init__(parent) - self.controller = controller - ctk.CTkLabel(self, text="登录", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=20) - self.email_entry = ctk.CTkEntry(self, placeholder_text="邮箱", width=200) - self.email_entry.pack(pady=10) - self.password_entry = ctk.CTkEntry(self, placeholder_text="密码", show="*", width=200) - self.password_entry.pack(pady=10) - self.error_label = ctk.CTkLabel(self, text="", text_color="red") - self.error_label.pack(pady=5) - ctk.CTkButton(self, text="登录", command=self.login).pack(pady=10) - ctk.CTkButton(self, text="没有账户?去注册", fg_color="transparent", command=lambda: controller.show_frame("RegisterPage")).pack() - - def login(self): - email, password = self.email_entry.get(), self.password_entry.get() - if self.controller.user_manager.validate_login(email, password): - self.controller.current_user = email - self.error_label.configure(text="") - self.controller.show_frame("MainPage") - else: - self.error_label.configure(text="邮箱或密码错误") - -class RegisterPage(ctk.CTkFrame): - def __init__(self, parent, controller): - super().__init__(parent) - self.controller = controller - self.code = "" - self.cooldown_time = 0 - ctk.CTkLabel(self, text="注册新账户", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=10) - self.email_entry = ctk.CTkEntry(self, placeholder_text="邮箱", width=250) - self.email_entry.pack(pady=5) - self.send_code_button = ctk.CTkButton(self, text="发送验证码", command=self.send_code, width=250) - self.send_code_button.pack(pady=5) - self.code_entry = ctk.CTkEntry(self, placeholder_text="输入收到的邮件验证码", width=250) - self.code_entry.pack(pady=5) - self.password_entry = ctk.CTkEntry(self, placeholder_text="密码 (6-10位,含大小写和数字)", show="*", width=250) - self.password_entry.pack(pady=5) - self.confirm_password_entry = ctk.CTkEntry(self, placeholder_text="确认密码", show="*", width=250) - self.confirm_password_entry.pack(pady=5) - self.info_label = ctk.CTkLabel(self, text="", text_color="red") - self.info_label.pack(pady=5) - ctk.CTkButton(self, text="注册", command=self.register).pack(pady=10) - ctk.CTkButton(self, text="返回登录", fg_color="transparent", command=lambda: controller.show_frame("LoginPage")).pack() - - def update_cooldown(self): - if self.cooldown_time > 0: - self.send_code_button.configure(text=f"{self.cooldown_time}秒后可重试") - self.cooldown_time -= 1 - self.after(1000, self.update_cooldown) - else: - self.send_code_button.configure(text="发送验证码", state="normal") - - def send_code(self): - if self.cooldown_time > 0: return - email = self.email_entry.get().strip() - if not email: - self.info_label.configure(text="请输入邮箱地址。", text_color="red") - return - if self.controller.user_manager.is_email_registered(email): - self.info_label.configure(text="该邮箱已被注册。", text_color="red") - return - - self.code = ''.join(random.choices(string.digits, k=6)) - self.info_label.configure(text="正在发送邮件,请稍候...", text_color="gray") - success = send_verification_email(email, self.code) - - if success: - self.info_label.configure(text="邮件验证码已发送至您的邮箱,请查收。", text_color="green") - self.cooldown_time = 60 - self.send_code_button.configure(state="disabled") - self.update_cooldown() - else: - self.info_label.configure(text="邮件发送失败,请检查邮箱或网络。", text_color="red") - self.code = "" - - def register(self): - email, user_code, password, confirm_password = self.email_entry.get().strip(), self.code_entry.get().strip(), self.password_entry.get(), self.confirm_password_entry.get() - if not self.code or user_code != self.code: - self.info_label.configure(text="邮件验证码错误", text_color="red") - return - if password != confirm_password: - self.info_label.configure(text="两次输入的密码不一致", text_color="red") - return - if not self.controller.user_manager.validate_password_format(password): - self.info_label.configure(text="密码格式不符合要求", text_color="red") - return - success, msg = self.controller.user_manager.register_user(email, password) - self.info_label.configure(text=msg, text_color="green" if success else "red") - if success: self.code = "" - -class MainPage(ctk.CTkFrame): - def __init__(self, parent, controller): - super().__init__(parent) - self.controller = controller - ctk.CTkLabel(self, text="选择学习阶段", font=ctk.CTkFont(size=24, weight="bold")).pack(pady=20) - ctk.CTkButton(self, text="小学", command=lambda: self.start_quiz("小学"), height=40, width=150).pack(pady=10) - ctk.CTkButton(self, text="初中", command=lambda: self.start_quiz("初中"), height=40, width=150).pack(pady=10) - ctk.CTkButton(self, text="高中", command=lambda: self.start_quiz("高中"), height=40, width=150).pack(pady=10) - ctk.CTkButton(self, text="退出登录", fg_color="transparent", command=lambda: controller.show_frame("LoginPage")).pack(side="bottom", pady=20) - - def start_quiz(self, level): - dialog = ctk.CTkInputDialog(text="请输入题目数量 (1-20):", title="设置题目数量") - num_str = dialog.get_input() - if num_str: - try: - num = int(num_str) - if 1 <= num <= 20: - self.controller.frames["QuizPage"].setup_quiz(level, num) - self.controller.show_frame("QuizPage") - except ValueError: pass - -class QuizPage(ctk.CTkFrame): - def __init__(self, parent, controller): - super().__init__(parent) - self.controller = controller - self.question_label = ctk.CTkLabel(self, text="问题", font=ctk.CTkFont(size=18)) - self.question_label.pack(pady=20, padx=20) - self.radio_var = ctk.IntVar(value=-1) - self.radio_buttons = [] - for i in range(4): - rb = ctk.CTkRadioButton(self, text="", variable=self.radio_var, value=i, font=ctk.CTkFont(size=14)) - rb.pack(anchor="w", padx=100, pady=5) - self.radio_buttons.append(rb) - self.submit_button = ctk.CTkButton(self, text="下一题", command=self.next_question) - self.submit_button.pack(pady=20) - def setup_quiz(self, level, num_questions): - self.questions = get_generator(level).generate_bunch(num_questions) - self.current_question_index = 0 - self.user_answers = [] - self.display_question() - def display_question(self): - self.radio_var.set(-1) - q = self.questions[self.current_question_index] - self.question_label.configure(text=f"{self.current_question_index + 1}. {q.text} = ?") - for i, option in enumerate(q.options): self.radio_buttons[i].configure(text=str(option)) - self.submit_button.configure(text="提交试卷" if self.current_question_index == len(self.questions) - 1 else "下一题") - def next_question(self): - if self.radio_var.get() == -1: return - self.user_answers.append(self.radio_var.get()) - self.current_question_index += 1 - if self.current_question_index < len(self.questions): self.display_question() - else: self.calculate_score() - def calculate_score(self): - correct_count = sum(1 for i, q in enumerate(self.questions) if self.user_answers[i] == q.correct_answer_index) - total = len(self.questions) - score_percent = (correct_count / total) * 100 - self.controller.frames["ScorePage"].show_score(correct_count, total, score_percent) - self.controller.show_frame("ScorePage") - -class ScorePage(ctk.CTkFrame): - def __init__(self, parent, controller): - super().__init__(parent) - self.controller = controller - self.score_label = ctk.CTkLabel(self, text="", font=ctk.CTkFont(size=24)) - self.score_label.pack(pady=40, padx=20) - ctk.CTkButton(self, text="继续做题", command=lambda: controller.show_frame("MainPage")).pack(pady=10) - ctk.CTkButton(self, text="退出程序", command=controller.quit).pack(pady=10) - def show_score(self, correct, total, percent): - self.score_label.configure(text=f"答对了 {correct} / {total} 题\n\n得分: {percent:.2f}%") \ No newline at end of file diff --git a/src/com/mathlearning/Main.java b/src/com/mathlearning/Main.java new file mode 100644 index 0000000..612da2f --- /dev/null +++ b/src/com/mathlearning/Main.java @@ -0,0 +1,15 @@ +package com.mathlearning; + +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; + +public class Main { + public static void main(String[] args) { + // 直接启动程序,不使用特殊的外观设置 + SwingUtilities.invokeLater(() -> { + NavigationController navigationController = new NavigationController(); + navigationController.showLoginView(); + }); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/AuthController.java b/src/com/mathlearning/controller/AuthController.java new file mode 100644 index 0000000..ccfbc95 --- /dev/null +++ b/src/com/mathlearning/controller/AuthController.java @@ -0,0 +1,149 @@ +package com.mathlearning.controller; + +import com.mathlearning.model.User; +import com.mathlearning.model.UserManager; +import com.mathlearning.util.EmailUtil; +import com.mathlearning.util.PasswordValidator; +import com.mathlearning.util.UsernameValidator; + +public class AuthController { + private UserManager userManager; + + public AuthController() { + this.userManager = UserManager.getInstance(); + } + + /** + * 发送验证码(不创建用户) + */ + public String sendVerificationCode(String email, String username) { + if (!isValidEmail(email)) { + return null; + } + + if (!UsernameValidator.isValid(username)) { + return null; + } + + // 检查邮箱是否已被注册 + if (userManager.userExistsByEmail(email)) { + User existingUser = userManager.getUserByEmail(email); + if (existingUser.isRegistered()) { + return null; // 邮箱已被注册 + } + // 如果用户存在但未注册,可以重新发送验证码 + } + + // 检查用户名是否已被使用 + if (userManager.userExistsByUsername(username)) { + User existingUser = userManager.getUserByUsername(username); + if (existingUser.isRegistered() || !existingUser.getEmail().equals(email)) { + return null; // 用户名已被其他用户使用 + } + // 如果是同一邮箱重新发送验证码,允许使用相同的用户名 + } + + // 发送验证码 + String verificationCode = EmailUtil.sendVerificationCode(email); + return verificationCode; + } + + /** + * 创建未注册用户(保存验证码) + */ + public boolean createUnregisteredUser(String email, String username, String verificationCode) { + if (verificationCode == null) { + return false; + } + + // 检查是否已存在未注册用户 + User existingUser = userManager.getUserByEmail(email); + if (existingUser != null && !existingUser.isRegistered()) { + // 更新现有未注册用户的验证码 + existingUser.setUsername(username); + existingUser.setVerificationCode(verificationCode); + } else { + // 创建新用户 + User user = new User(email, username); + user.setVerificationCode(verificationCode); + userManager.addUser(user); + } + + return true; + } + + public boolean verifyCode(String email, String code) { + User user = userManager.getUserByEmail(email); + return user != null && code.equals(user.getVerificationCode()); + } + + public boolean setPassword(String email, String password, String confirmPassword) { + if (!password.equals(confirmPassword)) { + return false; + } + + if (!PasswordValidator.isValid(password)) { + return false; + } + + User user = userManager.getUserByEmail(email); + if (user != null) { + user.setPassword(password); + user.setRegistered(true); + userManager.saveUsers(); + return true; + } + + return false; + } + + public boolean login(String identifier, String password) { + User user = userManager.getUser(identifier); + return user != null && user.isRegistered() && + password.equals(user.getPassword()); + } + + public boolean changePassword(String identifier, String oldPassword, String newPassword, String confirmPassword) { + if (!newPassword.equals(confirmPassword)) { + return false; + } + + if (!PasswordValidator.isValid(newPassword)) { + return false; + } + + User user = userManager.getUser(identifier); + if (user != null && oldPassword.equals(user.getPassword())) { + user.setPassword(newPassword); + userManager.saveUsers(); + return true; + } + + return false; + } + + private boolean isValidEmail(String email) { + return EmailUtil.isValidEmail(email); + } + + public String getUserEmail(String identifier) { + User user = userManager.getUser(identifier); + return user != null ? user.getEmail() : identifier; + } + + /** + * 检查邮箱是否可用 + */ + public boolean isEmailAvailable(String email) { + User user = userManager.getUserByEmail(email); + return user == null || !user.isRegistered(); + } + + /** + * 检查用户名是否可用 + */ + public boolean isUsernameAvailable(String username) { + User user = userManager.getUserByUsername(username); + return user == null || !user.isRegistered(); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/ExamController.java b/src/com/mathlearning/controller/ExamController.java new file mode 100644 index 0000000..4d25f85 --- /dev/null +++ b/src/com/mathlearning/controller/ExamController.java @@ -0,0 +1,247 @@ +package com.mathlearning.controller; + +import com.mathlearning.model.*; +import com.mathlearning.util.ExamFileUtil; + +import java.util.*; + +public class ExamController { + private Paper currentPaper; + private String currentUser; + private String currentLevel; + + public ExamController() { + } + + public void createExam(String level, int questionCount) { + System.out.println("=== 开始创建试卷 ==="); + System.out.println("级别: " + level + ", 题目数量: " + questionCount); + + long startTime = System.currentTimeMillis(); + this.currentLevel = level; + + try { + // 使用工厂方法创建对应的生成器 + QuestionGenerator generator = QuestionGenerator.createGenerator(level); + System.out.println("生成器创建完成: " + generator.getClass().getSimpleName()); + + // 设置超时保护 + List questions = generateQuestionsWithTimeout(generator, questionCount, 15000); // 15秒超时 + + System.out.println("题目生成完成,生成了 " + questions.size() + " 道题目"); + + // 额外的重复检查 + int duplicateCount = checkForDuplicates(questions); + if (duplicateCount > 0) { + System.out.println("警告: 发现 " + duplicateCount + " 道重复题目"); + } + + currentPaper = new Paper(level); + for (Question question : questions) { + currentPaper.addQuestion(question); + } + + long endTime = System.currentTimeMillis(); + System.out.println("试卷创建完成,耗时: " + (endTime - startTime) + "ms"); + System.out.println("=== 试卷创建结束 ==="); + + } catch (Exception e) { + System.err.println("创建试卷时发生错误: " + e.getMessage()); + e.printStackTrace(); + // 创建空试卷避免空指针 + currentPaper = new Paper(level); + // 补充基础题目 + List basicQuestions = generateBasicQuestions(questionCount); + for (Question question : basicQuestions) { + currentPaper.addQuestion(question); + } + } + } + + /** + * 带超时保护的题目生成 + */ + private List generateQuestionsWithTimeout(QuestionGenerator generator, int count, long timeoutMs) { + long startTime = System.currentTimeMillis(); + List questions = new ArrayList<>(); + + try { + List generated = generator.generateQuestions(count); + questions.addAll(generated); + + // 检查是否超时 + if ((System.currentTimeMillis() - startTime) > timeoutMs) { + System.out.println("生成题目超时,使用已生成的部分题目"); + } + + // 如果生成的题目超过所需数量,截取 + if (questions.size() > count) { + questions = questions.subList(0, count); + } + + } catch (Exception e) { + System.err.println("生成题目时出错: " + e.getMessage()); + } + + // 如果题目数量不足,补充基础题目 + if (questions.size() < count) { + System.out.println("题目数量不足,补充基础题目..."); + int needed = count - questions.size(); + List basicQuestions = generateBasicQuestions(needed); + questions.addAll(basicQuestions); + } + + return questions; + } + + /** + * 生成基础题目作为备选 + */ + private List generateBasicQuestions(int count) { + System.out.println("生成 " + count + " 道基础题目"); + List basicQuestions = new ArrayList<>(); + Random random = new Random(); + + for (int i = 0; i < count; i++) { + int a = random.nextInt(10) + 1; + int b = random.nextInt(10) + 1; + int operation = random.nextInt(2); // 0: 加法, 1: 减法 + + String expression; + int answer; + + if (operation == 0) { + // 加法 + expression = a + " + " + b; + answer = a + b; + } else { + // 减法,确保结果为正 + if (a < b) { + int temp = a; + a = b; + b = temp; + } + expression = a + " - " + b; + answer = a - b; + } + + // 生成选项 + Set options = new HashSet<>(); + options.add(String.valueOf(answer)); + + while (options.size() < 4) { + int variation = random.nextInt(5) + 1; + int wrongAnswer; + if (random.nextBoolean()) { + wrongAnswer = answer + variation; + } else { + wrongAnswer = Math.max(1, answer - variation); + } + options.add(String.valueOf(wrongAnswer)); + } + + List optionList = new ArrayList<>(options); + Collections.shuffle(optionList); + String optionsStr = String.join(",", optionList); + + basicQuestions.add(new Question(expression + " = ?", optionsStr, String.valueOf(answer))); + } + + return basicQuestions; + } + + /** + * 保存当前试卷到文件 + * @return 是否保存成功 + */ + public boolean saveCurrentPaper() { + if (currentPaper == null || currentUser == null) { + System.err.println("无法保存试卷: 试卷或用户信息为空"); + return false; + } + + return ExamFileUtil.savePaperToFile(currentUser, currentPaper); + } + + // 检查重复题目的辅助方法 + private int checkForDuplicates(List questions) { + Set expressions = new HashSet<>(); + int duplicateCount = 0; + + for (Question question : questions) { + String expression = extractExpression(question.getContent()); + if (expressions.contains(expression)) { + duplicateCount++; + System.out.println("发现重复题目: " + question.getContent()); + } else { + expressions.add(expression); + } + } + + return duplicateCount; + } + + // 从题目内容中提取表达式(去掉"= ?"等后缀) + private String extractExpression(String content) { + if (content == null) return ""; + // 去掉"= ?"和可能的空格 + return content.replace(" = ?", "").trim(); + } + + // 其他方法保持不变... + public Question getCurrentQuestion() { + return currentPaper != null ? currentPaper.getCurrentQuestion() : null; + } + + public boolean hasNextQuestion() { + return currentPaper != null && currentPaper.hasNextQuestion(); + } + + public void nextQuestion() { + if (currentPaper != null) { + currentPaper.nextQuestion(); + } + } + + public boolean hasPreviousQuestion() { + return currentPaper != null && currentPaper.hasPreviousQuestion(); + } + + public void previousQuestion() { + if (currentPaper != null) { + currentPaper.previousQuestion(); + } + } + + public void submitAnswer(String answer) { + Question currentQuestion = getCurrentQuestion(); + if (currentQuestion != null) { + currentQuestion.setUserAnswer(answer); + System.out.println("题目 " + getCurrentQuestionNumber() + " 答案设置为: " + answer); + } + } + + public int calculateScore() { + return currentPaper != null ? currentPaper.calculateScore() : 0; + } + + public int getCurrentQuestionNumber() { + return currentPaper != null ? currentPaper.getCurrentQuestionNumber() : 0; + } + + public int getTotalQuestions() { + return currentPaper != null ? currentPaper.getTotalQuestions() : 0; + } + + public String getCurrentLevel() { + return currentLevel; + } + + public void setCurrentUser(String user) { + this.currentUser = user; + } + + public Paper getCurrentPaper() { + return currentPaper; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/NavigationController.java b/src/com/mathlearning/controller/NavigationController.java new file mode 100644 index 0000000..cf4143d --- /dev/null +++ b/src/com/mathlearning/controller/NavigationController.java @@ -0,0 +1,86 @@ +package com.mathlearning.controller; + +import com.mathlearning.view.*; + +import javax.swing.*; +import java.awt.*; + +public class NavigationController { + private JFrame mainFrame; + private CardLayout cardLayout; + private JPanel mainPanel; + private String currentUser; + private String currentUserEmail; + + public NavigationController() { + initializeMainFrame(); + } + + private void initializeMainFrame() { + mainFrame = new JFrame("数学学习软件"); + mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + mainFrame.setSize(600, 500); + mainFrame.setLocationRelativeTo(null); + + cardLayout = new CardLayout(); + mainPanel = new JPanel(cardLayout); + + // Initialize with login view + LoginView loginView = new LoginView(this); + mainPanel.add(loginView, "LOGIN"); + + mainFrame.add(mainPanel); + } + + public void showLoginView() { + cardLayout.show(mainPanel, "LOGIN"); + mainFrame.setVisible(true); + } + + public void showRegisterView() { + RegisterView registerView = new RegisterView(this); + mainPanel.add(registerView, "REGISTER"); + cardLayout.show(mainPanel, "REGISTER"); + } + + public void showSetPasswordView(String email, String verificationCode) { + SetPasswordView setPasswordView = new SetPasswordView(this, email, verificationCode); + mainPanel.add(setPasswordView, "SET_PASSWORD"); + cardLayout.show(mainPanel, "SET_PASSWORD"); + } + + public void showLevelSelectionView(String userIdentifier, String email) { + this.currentUser = userIdentifier; + this.currentUserEmail = email; + LevelSelectionView levelSelectionView = new LevelSelectionView(this, userIdentifier, email); + mainPanel.add(levelSelectionView, "LEVEL_SELECTION"); + cardLayout.show(mainPanel, "LEVEL_SELECTION"); + } + + public void showQuestionCountView(String level) { + QuestionCountView questionCountView = new QuestionCountView(this, currentUser, currentUserEmail, level); + mainPanel.add(questionCountView, "QUESTION_COUNT"); + cardLayout.show(mainPanel, "QUESTION_COUNT"); + } + + public void showExamView(String level, int questionCount) { + ExamView examView = new ExamView(this, currentUser, currentUserEmail, level, questionCount); + mainPanel.add(examView, "EXAM"); + cardLayout.show(mainPanel, "EXAM"); + } + + public void showScoreView(String level, int score, int totalQuestions) { + ScoreView scoreView = new ScoreView(this, currentUser, currentUserEmail, level, score, totalQuestions); + mainPanel.add(scoreView, "SCORE"); + cardLayout.show(mainPanel, "SCORE"); + } + + public void showChangePasswordView(String identifier) { + // 实现修改密码视图的显示 + JOptionPane.showMessageDialog(mainFrame, "修改密码功能待实现", "提示", JOptionPane.INFORMATION_MESSAGE); + } + + public JFrame getMainFrame() { + return mainFrame; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/Paper.java b/src/com/mathlearning/model/Paper.java new file mode 100644 index 0000000..bdb0795 --- /dev/null +++ b/src/com/mathlearning/model/Paper.java @@ -0,0 +1,73 @@ +package com.mathlearning.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class Paper implements Serializable { + private static final long serialVersionUID = 1L; + + private List questions; + private int currentQuestionIndex; + private String level; + + public Paper(String level) { + this.questions = new ArrayList<>(); + this.currentQuestionIndex = 0; + this.level = level; + } + + public void addQuestion(Question question) { + questions.add(question); + } + + public Question getCurrentQuestion() { + if (currentQuestionIndex < questions.size()) { + return questions.get(currentQuestionIndex); + } + return null; + } + + public boolean hasNextQuestion() { + return currentQuestionIndex < questions.size() - 1; + } + + public void nextQuestion() { + if (hasNextQuestion()) { + currentQuestionIndex++; + } + } + + public boolean hasPreviousQuestion() { + return currentQuestionIndex > 0; + } + + public void previousQuestion() { + if (hasPreviousQuestion()) { + currentQuestionIndex--; + } + } + + public int getTotalQuestions() { + return questions.size(); + } + + public int getCurrentQuestionNumber() { + return currentQuestionIndex + 1; + } + + public int calculateScore() { + int correctCount = 0; + for (Question q : questions) { + if (q.isCorrect()) { + correctCount++; + } + } + return (int) ((correctCount * 100.0) / questions.size()); + } + + public String getLevel() { return level; } + public void setLevel(String level) { this.level = level; } + + public List getQuestions() { return questions; } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/Question.java b/src/com/mathlearning/model/Question.java new file mode 100644 index 0000000..7d9d12b --- /dev/null +++ b/src/com/mathlearning/model/Question.java @@ -0,0 +1,39 @@ +package com.mathlearning.model; + +import java.io.Serializable; + +public class Question implements Serializable { + private static final long serialVersionUID = 1L; + + private String content; + private String[] options; + private String correctAnswer; + private String userAnswer; + + public Question(String content, String options, String correctAnswer) { + this.content = content; + this.options = options.split(","); + this.correctAnswer = correctAnswer; + } + + // Getters and setters + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + + public String[] getOptions() { return options; } + public void setOptions(String[] options) { this.options = options; } + + public String getCorrectAnswer() { return correctAnswer; } + public void setCorrectAnswer(String correctAnswer) { this.correctAnswer = correctAnswer; } + + public String getUserAnswer() { return userAnswer; } + public void setUserAnswer(String userAnswer) { this.userAnswer = userAnswer; } + + public boolean isCorrect() { + return correctAnswer != null && correctAnswer.equals(userAnswer); + } + + public String getOptionsAsString() { + return String.join(",", options); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/QuestionGenerator.java b/src/com/mathlearning/model/QuestionGenerator.java new file mode 100644 index 0000000..ac6668b --- /dev/null +++ b/src/com/mathlearning/model/QuestionGenerator.java @@ -0,0 +1,296 @@ +package com.mathlearning.model; + +import java.util.*; + +public abstract class QuestionGenerator { + protected Random random = new Random(); + + public abstract List generateQuestions(int count); + + protected String generateOptions(String correctAnswer) { + Set options = new HashSet<>(); + options.add(correctAnswer); + + // 生成错误选项 + while (options.size() < 4) { + try { + double correctValue = Double.parseDouble(correctAnswer); + double variation = correctValue * 0.2 + 1; // 20% 变化,至少1 + double wrongValue = correctValue + (random.nextDouble() * 2 - 1) * variation; + + // 避免负数和零 + if (wrongValue <= 0) { + wrongValue = Math.abs(wrongValue) + 1; + } + + // 格式化选项 + String formattedValue; + if (correctAnswer.contains(".")) { + formattedValue = String.format("%.2f", wrongValue); + } else { + formattedValue = String.valueOf((int) wrongValue); + } + + options.add(formattedValue); + } catch (NumberFormatException e) { + // 如果解析失败,使用整数方法 + int wrongValue = random.nextInt(20) + 1; + options.add(String.valueOf(wrongValue)); + } + } + + List optionList = new ArrayList<>(options); + Collections.shuffle(optionList); + return String.join(",", optionList); + } + + // 工厂方法 + public static QuestionGenerator createGenerator(String level) { + switch (level) { + case "小学": + return new OptimizedPrimaryQuestionGenerator(); + case "初中": + return new OptimizedMiddleSchoolQuestionGenerator(); + case "高中": + return new OptimizedHighSchoolQuestionGenerator(); + default: + return new OptimizedPrimaryQuestionGenerator(); + } + } + + // 生成随机操作数 (1-50) 减小范围提高效率 + protected int generateOperand() { + return random.nextInt(50) + 1; + } + + // 生成小范围操作数用于乘法 + protected int generateSmallOperand() { + return random.nextInt(12) + 1; + } +} + +// 优化的小学题目生成器 +class OptimizedPrimaryQuestionGenerator extends QuestionGenerator { + @Override + public List generateQuestions(int count) { + System.out.println("开始生成小学题目,数量: " + count); + List questions = new ArrayList<>(); + Set usedExpressions = new HashSet<>(); + + String[][] templates = { + {"{0} + {1} = ?", "+"}, + {"{0} - {1} = ?", "-"}, + {"{0} × {1} = ?", "×"} + }; + + int maxAttempts = count * 5; // 减少尝试次数 + int attempts = 0; + + while (questions.size() < count && attempts < maxAttempts) { + attempts++; + + String[] template = templates[random.nextInt(templates.length)]; + String expression; + int answer; + + switch (template[1]) { + case "+": + int a1 = generateOperand(); + int b1 = generateOperand(); + expression = a1 + " + " + b1; + answer = a1 + b1; + break; + case "-": + int a2 = generateOperand(); + int b2 = generateOperand(); + // 确保结果为正数 + if (a2 < b2) { + int temp = a2; + a2 = b2; + b2 = temp; + } + expression = a2 + " - " + b2; + answer = a2 - b2; + break; + case "×": + int a3 = generateSmallOperand(); + int b3 = generateSmallOperand(); + expression = a3 + " × " + b3; + answer = a3 * b3; + break; + default: + continue; + } + + // 检查表达式是否已使用 + if (usedExpressions.contains(expression)) { + continue; + } + + usedExpressions.add(expression); + String options = generateOptions(String.valueOf(answer)); + questions.add(new Question(expression + " = ?", options, String.valueOf(answer))); + + if (questions.size() % 10 == 0) { + System.out.println("已生成 " + questions.size() + " 道小学题目"); + } + } + + System.out.println("小学题目生成完成,实际生成: " + questions.size() + " 道题目"); + + // 如果因为重复问题无法生成足够的题目,补充简单题目 + if (questions.size() < count) { + System.out.println("补充简单小学题目..."); + for (int i = questions.size(); i < count; i++) { + int a = random.nextInt(10) + 1; + int b = random.nextInt(10) + 1; + String expression = a + " + " + b; + if (!usedExpressions.contains(expression)) { + int answer = a + b; + String options = generateOptions(String.valueOf(answer)); + questions.add(new Question(expression + " = ?", options, String.valueOf(answer))); + usedExpressions.add(expression); + } + } + } + + return questions; + } +} + +// 优化的初中题目生成器 +class OptimizedMiddleSchoolQuestionGenerator extends QuestionGenerator { + @Override + public List generateQuestions(int count) { + System.out.println("开始生成初中题目,数量: " + count); + List questions = new ArrayList<>(); + Set usedExpressions = new HashSet<>(); + + int maxAttempts = count * 5; + int attempts = 0; + + while (questions.size() < count && attempts < maxAttempts) { + attempts++; + + int type = random.nextInt(4); // 0-3: 平方、开方、混合、简单运算 + + String expression; + int answer; + + switch (type) { + case 0: // 平方 + int num = random.nextInt(15) + 1; // 1-15的平方 + expression = num + "²"; + answer = num * num; + break; + case 1: // 开根号 + int root = random.nextInt(12) + 1; // 1-12 + expression = "√" + (root * root); + answer = root; + break; + case 2: // 混合运算 + int x = random.nextInt(8) + 2; // 2-9 + int y = random.nextInt(8) + 2; // 2-9 + expression = "(" + x + " + " + y + ")²"; + answer = (x + y) * (x + y); + break; + default: // 简单运算 + int a = generateOperand(); + int b = generateOperand(); + if (random.nextBoolean()) { + expression = a + " + " + b; + answer = a + b; + } else { + if (a < b) { + int temp = a; + a = b; + b = temp; + } + expression = a + " - " + b; + answer = a - b; + } + break; + } + + if (usedExpressions.contains(expression)) { + continue; + } + + usedExpressions.add(expression); + String options = generateOptions(String.valueOf(answer)); + questions.add(new Question(expression + " = ?", options, String.valueOf(answer))); + + if (questions.size() % 10 == 0) { + System.out.println("已生成 " + questions.size() + " 道初中题目"); + } + } + + System.out.println("初中题目生成完成,实际生成: " + questions.size() + " 道题目"); + return questions; + } +} + +// 优化的高中题目生成器 +class OptimizedHighSchoolQuestionGenerator extends QuestionGenerator { + @Override + public List generateQuestions(int count) { + System.out.println("开始生成高中题目,数量: " + count); + List questions = new ArrayList<>(); + Set usedExpressions = new HashSet<>(); + + // 使用常见角度,避免复杂计算 + int[] angles = {0, 30, 45, 60, 90, 120, 135, 150, 180}; + String[] functions = {"sin", "cos", "tan"}; + + int maxAttempts = count * 5; + int attempts = 0; + + while (questions.size() < count && attempts < maxAttempts) { + attempts++; + + String function = functions[random.nextInt(functions.length)]; + int angle = angles[random.nextInt(angles.length)]; + + String expression = function + "(" + angle + "°)"; + double result; + + switch (function) { + case "sin": + result = Math.sin(Math.toRadians(angle)); + break; + case "cos": + result = Math.cos(Math.toRadians(angle)); + break; + case "tan": + // 避免tan(90°)等未定义的情况 + if (angle % 90 == 0 && angle % 180 != 0) { + continue; + } + result = Math.tan(Math.toRadians(angle)); + // 限制tan值范围 + if (Math.abs(result) > 10) { + continue; + } + break; + default: + result = 0; + } + + String formattedAnswer = String.format("%.2f", result); + if (usedExpressions.contains(expression)) { + continue; + } + + usedExpressions.add(expression); + String options = generateOptions(formattedAnswer); + questions.add(new Question(expression + " = ?", options, formattedAnswer)); + + if (questions.size() % 10 == 0) { + System.out.println("已生成 " + questions.size() + " 道高中题目"); + } + } + + System.out.println("高中题目生成完成,实际生成: " + questions.size() + " 道题目"); + return questions; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/User.java b/src/com/mathlearning/model/User.java new file mode 100644 index 0000000..85ee047 --- /dev/null +++ b/src/com/mathlearning/model/User.java @@ -0,0 +1,42 @@ +package com.mathlearning.model; + +import java.io.Serializable; + +public class User implements Serializable { + private static final long serialVersionUID = 1L; + + private String email; + private String username; // 新增用户名字段 + private String password; + private String verificationCode; + private boolean isRegistered; + + public User(String email, String username) { + this.email = email; + this.username = username; + this.isRegistered = false; + } + + // Getters and setters + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + + public String getVerificationCode() { return verificationCode; } + public void setVerificationCode(String verificationCode) { + this.verificationCode = verificationCode; + } + + public boolean isRegistered() { return isRegistered; } + public void setRegistered(boolean registered) { isRegistered = registered; } + + @Override + public String toString() { + return "User{username='" + username + "', email='" + email + "', registered=" + isRegistered + "}"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/UserManager.java b/src/com/mathlearning/model/UserManager.java new file mode 100644 index 0000000..c1fbc6d --- /dev/null +++ b/src/com/mathlearning/model/UserManager.java @@ -0,0 +1,118 @@ +package com.mathlearning.model; + +import com.mathlearning.util.FileStorage; + +import java.util.Map; +import java.util.HashMap; + +public class UserManager { + private static UserManager instance; + private Map usersByEmail; // 按邮箱存储 + private Map usersByUsername; // 按用户名存储 + + private UserManager() { + this.usersByEmail = FileStorage.loadUsersByEmail(); + this.usersByUsername = FileStorage.loadUsersByUsername(); + } + + public static synchronized UserManager getInstance() { + if (instance == null) { + instance = new UserManager(); + } + return instance; + } + + public void addUser(User user) { + usersByEmail.put(user.getEmail(), user); + usersByUsername.put(user.getUsername(), user); + FileStorage.saveUsers(usersByEmail, usersByUsername); + } + + public User getUserByEmail(String email) { + return usersByEmail.get(email); + } + + public User getUserByUsername(String username) { + return usersByUsername.get(username); + } + + // 通过邮箱或用户名查找用户 + public User getUser(String identifier) { + User user = usersByEmail.get(identifier); + if (user == null) { + user = usersByUsername.get(identifier); + } + return user; + } + + public boolean userExistsByEmail(String email) { + return usersByEmail.containsKey(email); + } + + public boolean userExistsByUsername(String username) { + return usersByUsername.containsKey(username); + } + + // 检查邮箱或用户名是否已存在 + public boolean userExists(String identifier) { + return usersByEmail.containsKey(identifier) || usersByUsername.containsKey(identifier); + } + + public boolean verifyUser(String identifier, String password) { + User user = getUser(identifier); + return user != null && user.isRegistered() && + password.equals(user.getPassword()); + } + + public void saveUsers() { + FileStorage.saveUsers(usersByEmail, usersByUsername); + } + + /** + * 检查邮箱是否可用(未被注册) + */ + public boolean isEmailAvailable(String email) { + User user = usersByEmail.get(email); + return user == null || !user.isRegistered(); + } + + /** + * 检查用户名是否可用(未被注册) + */ + public boolean isUsernameAvailable(String username) { + User user = usersByUsername.get(username); + return user == null || !user.isRegistered(); + } + + /** + * 删除未注册的用户(清理过期数据) + */ + public void removeUnregisteredUser(String email) { + User user = usersByEmail.get(email); + if (user != null && !user.isRegistered()) { + usersByEmail.remove(email); + usersByUsername.remove(user.getUsername()); + saveUsers(); + } + } + + /** + * 获取所有用户数量(用于调试) + */ + public int getUserCount() { + return usersByEmail.size(); + } + + /** + * 获取已注册用户数量(用于调试) + */ + public int getRegisteredUserCount() { + int count = 0; + for (User user : usersByEmail.values()) { + if (user.isRegistered()) { + count++; + } + } + return count; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/EmailConfig.java b/src/com/mathlearning/util/EmailConfig.java new file mode 100644 index 0000000..9177b06 --- /dev/null +++ b/src/com/mathlearning/util/EmailConfig.java @@ -0,0 +1,62 @@ +package com.mathlearning.util; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +public class EmailConfig { + private static final String CONFIG_FILE = "email_config.properties"; + private static Properties properties; + + static { + properties = new Properties(); + try { + // 尝试从配置文件加载 + properties.load(new FileInputStream(CONFIG_FILE)); + } catch (IOException e) { + // 如果配置文件不存在,使用默认值 + System.out.println("未找到配置文件,使用默认邮箱配置(需要修改为您的真实邮箱)"); + setDefaultProperties(); + } + } + + private static void setDefaultProperties() { + // 这里设置您的邮箱配置 - 请修改为您的真实邮箱信息 + properties.setProperty("email.from", "your-email@qq.com"); // 发件邮箱 + properties.setProperty("email.password", "your-authorization-code"); // 授权码 + properties.setProperty("email.smtp.host", "smtp.qq.com"); // SMTP服务器 + properties.setProperty("email.smtp.port", "465"); // 端口 + properties.setProperty("email.smtp.ssl", "true"); // 启用SSL + properties.setProperty("email.smtp.auth", "true"); // 需要认证 + } + + public static String getFrom() { + return properties.getProperty("email.from"); + } + + public static String getPassword() { + return properties.getProperty("email.password"); + } + + public static String getSmtpHost() { + return properties.getProperty("email.smtp.host"); + } + + public static String getSmtpPort() { + return properties.getProperty("email.smtp.port"); + } + + public static boolean isSslEnabled() { + return "true".equals(properties.getProperty("email.smtp.ssl")); + } + + public static boolean isAuthEnabled() { + return "true".equals(properties.getProperty("email.smtp.auth")); + } + + // 验证配置是否已设置(不是默认值) + public static boolean isConfigured() { + return !"your-email@qq.com".equals(getFrom()) && + !"your-authorization-code".equals(getPassword()); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/EmailUtil.java b/src/com/mathlearning/util/EmailUtil.java new file mode 100644 index 0000000..180f2fd --- /dev/null +++ b/src/com/mathlearning/util/EmailUtil.java @@ -0,0 +1,133 @@ +package com.mathlearning.util; + +import javax.mail.*; +import javax.mail.internet.*; +import javax.swing.JOptionPane; +import java.util.Properties; +import java.util.Random; + +public class EmailUtil { + private static final Random random = new Random(); + + /** + * 发送验证码 - 真实邮箱版本 + */ + public static String sendVerificationCode(String toEmail) { + // 检查邮箱配置 + if (!EmailConfig.isConfigured()) { + JOptionPane.showMessageDialog(null, + "邮箱配置未完成!\n请修改 email_config.properties 文件,配置您的邮箱信息。", + "配置错误", + JOptionPane.ERROR_MESSAGE); + return null; + } + + // 生成6位验证码 + String verificationCode = generateVerificationCode(); + + try { + // 发送邮件 + boolean sendSuccess = sendEmail(toEmail, verificationCode); + + if (sendSuccess) { + JOptionPane.showMessageDialog( + null, + "验证码已发送到您的邮箱!\n\n" + + "收件邮箱: " + toEmail + "\n" + + "请查看您的邮箱并输入验证码。", + "验证码发送成功", + JOptionPane.INFORMATION_MESSAGE + ); + return verificationCode; + } else { + JOptionPane.showMessageDialog(null, + "验证码发送失败,请检查邮箱地址是否正确或稍后重试", + "发送失败", + JOptionPane.ERROR_MESSAGE); + return null; + } + } catch (Exception e) { + JOptionPane.showMessageDialog(null, + "发送验证码时发生错误: " + e.getMessage(), + "错误", + JOptionPane.ERROR_MESSAGE); + return null; + } + } + + /** + * 发送邮件 + */ + private static boolean sendEmail(String toEmail, String verificationCode) { + Properties props = new Properties(); + props.put("mail.smtp.host", EmailConfig.getSmtpHost()); + props.put("mail.smtp.port", EmailConfig.getSmtpPort()); + props.put("mail.smtp.auth", "true"); + + if (EmailConfig.isSslEnabled()) { + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.put("mail.smtp.socketFactory.port", EmailConfig.getSmtpPort()); + } + + // 创建会话 + Session session = Session.getInstance(props, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(EmailConfig.getFrom(), EmailConfig.getPassword()); + } + }); + + try { + // 创建邮件 + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(EmailConfig.getFrom())); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); + message.setSubject("数学学习软件 - 邮箱验证码"); + + String emailContent = "尊敬的用户:\n\n" + + "您正在注册数学学习软件,验证码为:" + verificationCode + "\n\n" + + "验证码有效期为10分钟,请尽快完成注册。\n\n" + + "如果不是您本人操作,请忽略此邮件。\n\n" + + "数学学习软件团队"; + + message.setText(emailContent); + + // 发送邮件 + Transport.send(message); + System.out.println("验证码邮件已发送到: " + toEmail); + return true; + + } catch (MessagingException e) { + System.err.println("发送邮件失败: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * 生成6位数字验证码 + */ + private static String generateVerificationCode() { + return String.format("%06d", random.nextInt(1000000)); + } + + /** + * 验证邮箱格式 + */ + public static boolean isValidEmail(String email) { + if (email == null || email.trim().isEmpty()) { + return false; + } + + // 简单的邮箱格式验证 + String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"; + return email.matches(emailRegex); + } + + /** + * 获取邮箱验证要求说明 + */ + public static String getEmailRequirements() { + return "请输入有效的邮箱地址,例如:username@example.com"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/ExamFileUtil.java b/src/com/mathlearning/util/ExamFileUtil.java new file mode 100644 index 0000000..df9dc3d --- /dev/null +++ b/src/com/mathlearning/util/ExamFileUtil.java @@ -0,0 +1,86 @@ +package com.mathlearning.util; + +import com.mathlearning.model.Paper; +import com.mathlearning.model.Question; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class ExamFileUtil { + + /** + * 保存试卷到文件 + * @param username 用户名 + * @param paper 试卷对象 + * @return 是否保存成功 + */ + public static boolean savePaperToFile(String username, Paper paper) { + // 创建用户文件夹 + File userDir = new File("exam_papers/" + username); + if (!userDir.exists()) { + if (!userDir.mkdirs()) { + System.err.println("无法创建用户文件夹: " + userDir.getAbsolutePath()); + return false; + } + } + + // 生成文件名 + String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + String filename = timestamp + ".txt"; + File file = new File(userDir, filename); + + try (PrintWriter writer = new PrintWriter(new FileWriter(file))) { + // 写入试卷信息 + writer.println("试卷级别: " + paper.getLevel()); + writer.println("生成时间: " + new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(new Date())); + writer.println("题目数量: " + paper.getTotalQuestions()); + writer.println(); + + // 写入题目 + int questionNumber = 1; + for (Question question : paper.getQuestions()) { + writer.println(questionNumber + ". " + question.getContent()); + + // 写入选项 + String[] options = question.getOptions(); + for (int i = 0; i < options.length; i++) { + writer.println(" " + (char)('A' + i) + ". " + options[i]); + } + + writer.println(); // 题目之间空一行 + questionNumber++; + } + + // 写入答案 + writer.println("=== 参考答案 ==="); + questionNumber = 1; + for (Question question : paper.getQuestions()) { + writer.println(questionNumber + ". " + question.getCorrectAnswer()); + questionNumber++; + } + + System.out.println("试卷已保存到: " + file.getAbsolutePath()); + return true; + + } catch (IOException e) { + System.err.println("保存试卷时出错: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * 获取用户的所有试卷文件 + * @param username 用户名 + * @return 试卷文件列表 + */ + public static File[] getUserExamFiles(String username) { + File userDir = new File("exam_papers/" + username); + if (!userDir.exists()) { + return new File[0]; + } + + return userDir.listFiles((dir, name) -> name.endsWith(".txt")); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/FileStorage.java b/src/com/mathlearning/util/FileStorage.java new file mode 100644 index 0000000..18f1613 --- /dev/null +++ b/src/com/mathlearning/util/FileStorage.java @@ -0,0 +1,52 @@ +package com.mathlearning.util; + +import com.mathlearning.model.User; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +public class FileStorage { + private static final String USER_DATA_EMAIL_FILE = "users_email.dat"; + private static final String USER_DATA_USERNAME_FILE = "users_username.dat"; + + @SuppressWarnings("unchecked") + public static Map loadUsersByEmail() { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_EMAIL_FILE))) { + return (Map) ois.readObject(); + } catch (FileNotFoundException e) { + // 文件不存在,返回空Map + return new HashMap<>(); + } catch (IOException | ClassNotFoundException e) { + System.err.println("加载用户数据失败: " + e.getMessage()); + return new HashMap<>(); + } + } + + @SuppressWarnings("unchecked") + public static Map loadUsersByUsername() { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_USERNAME_FILE))) { + return (Map) ois.readObject(); + } catch (FileNotFoundException e) { + // 文件不存在,返回空Map + return new HashMap<>(); + } catch (IOException | ClassNotFoundException e) { + System.err.println("加载用户数据失败: " + e.getMessage()); + return new HashMap<>(); + } + } + + public static void saveUsers(Map usersByEmail, Map usersByUsername) { + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(USER_DATA_EMAIL_FILE))) { + oos.writeObject(usersByEmail); + } catch (IOException e) { + System.err.println("保存用户数据失败: " + e.getMessage()); + } + + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(USER_DATA_USERNAME_FILE))) { + oos.writeObject(usersByUsername); + } catch (IOException e) { + System.err.println("保存用户数据失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/PasswordValidator.java b/src/com/mathlearning/util/PasswordValidator.java new file mode 100644 index 0000000..ce0526f --- /dev/null +++ b/src/com/mathlearning/util/PasswordValidator.java @@ -0,0 +1,21 @@ +package com.mathlearning.util; + +public class PasswordValidator { + public static boolean isValid(String password) { + if (password == null || password.length() < 6 || password.length() > 10) { + return false; + } + + boolean hasUpperCase = false; + boolean hasLowerCase = false; + boolean hasDigit = false; + + for (char c : password.toCharArray()) { + if (Character.isUpperCase(c)) hasUpperCase = true; + if (Character.isLowerCase(c)) hasLowerCase = true; + if (Character.isDigit(c)) hasDigit = true; + } + + return hasUpperCase && hasLowerCase && hasDigit; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/UsernameValidator.java b/src/com/mathlearning/util/UsernameValidator.java new file mode 100644 index 0000000..e47214b --- /dev/null +++ b/src/com/mathlearning/util/UsernameValidator.java @@ -0,0 +1,16 @@ +package com.mathlearning.util; + +public class UsernameValidator { + public static boolean isValid(String username) { + if (username == null || username.length() < 3 || username.length() > 20) { + return false; + } + + // 用户名只能包含字母、数字和下划线 + return username.matches("^[a-zA-Z0-9_]+$"); + } + + public static String getRequirements() { + return "用户名要求: 3-20位,只能包含字母、数字和下划线"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/ExamView.java b/src/com/mathlearning/view/ExamView.java new file mode 100644 index 0000000..2f3c683 --- /dev/null +++ b/src/com/mathlearning/view/ExamView.java @@ -0,0 +1,397 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.ExamController; +import com.mathlearning.controller.NavigationController; +import com.mathlearning.model.Question; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.concurrent.ExecutionException; + +public class ExamView extends JPanel { + private NavigationController navigationController; + private ExamController examController; + private String userIdentifier; + private String email; + private String level; + private int questionCount; + + // UI组件 + private JLabel questionNumberLabel; + private JLabel questionContentLabel; + private ButtonGroup optionsGroup; + private JRadioButton[] optionButtons; + private JButton previousButton; + private JButton nextButton; + private JButton submitButton; + private JProgressBar progressBar; + private JLabel statusLabel; + private JButton cancelButton; + + // 进度跟踪 + private int generationProgress = 0; + + public ExamView(NavigationController navigationController, String userIdentifier, String email, String level, int questionCount) { + this.navigationController = navigationController; + this.userIdentifier = userIdentifier; + this.email = email; + this.level = level; + this.questionCount = questionCount; + this.examController = new ExamController(); + this.examController.setCurrentUser(userIdentifier); + + initializeUI(); + startExamGeneration(); + } + + private void initializeUI() { + setLayout(new BorderLayout(10, 10)); + + // 顶部信息面板 + JPanel infoPanel = new JPanel(new BorderLayout()); + String userInfo = "用户: " + userIdentifier; + if (!userIdentifier.equals(email)) { + userInfo += " (" + email + ")"; + } + JLabel userInfoLabel = new JLabel(userInfo, JLabel.LEFT); + userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + infoPanel.add(userInfoLabel, BorderLayout.WEST); + + JLabel levelLabel = new JLabel(level + "数学", JLabel.RIGHT); + levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + infoPanel.add(levelLabel, BorderLayout.EAST); + + add(infoPanel, BorderLayout.NORTH); + + // 进度显示面板 + JPanel progressPanel = new JPanel(new BorderLayout()); + progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + statusLabel = new JLabel("正在生成试卷,请稍候...", JLabel.CENTER); + statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + progressPanel.add(statusLabel, BorderLayout.NORTH); + + progressBar = new JProgressBar(); + progressBar.setIndeterminate(true); + progressBar.setPreferredSize(new Dimension(300, 20)); + progressPanel.add(progressBar, BorderLayout.CENTER); + + // 取消按钮 + JPanel buttonPanel = new JPanel(); + cancelButton = new JButton("取消生成"); + cancelButton.addActionListener(e -> { + // 返回题目数量选择界面 + navigationController.showQuestionCountView(level); + }); + buttonPanel.add(cancelButton); + progressPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(progressPanel, BorderLayout.CENTER); + } + + private void startExamGeneration() { + System.out.println("开始生成试卷,级别: " + level + ", 题目数量: " + questionCount); + + // 使用SwingWorker在后台生成题目 + SwingWorker worker = new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + publish("正在初始化生成器..."); + Thread.sleep(100); // 短暂延迟,让UI更新 + + try { + publish("开始生成题目..."); + long startTime = System.currentTimeMillis(); + + // 设置进度条为确定模式 + SwingUtilities.invokeLater(() -> { + progressBar.setIndeterminate(false); + progressBar.setMinimum(0); + progressBar.setMaximum(100); + progressBar.setValue(0); + }); + + // 模拟进度更新 + for (int i = 0; i <= 100; i += 10) { + if (isCancelled()) { + return false; + } + publish("生成题目中... " + i + "%"); + progressBar.setValue(i); + Thread.sleep(50); + } + + examController.createExam(level, questionCount); + + long endTime = System.currentTimeMillis(); + publish("题目生成完成,耗时: " + (endTime - startTime) + "ms"); + + return true; + } catch (Exception e) { + System.err.println("生成题目时出错: " + e.getMessage()); + e.printStackTrace(); + publish("生成题目时出错: " + e.getMessage()); + return false; + } + } + + @Override + protected void process(List chunks) { + // 更新状态信息 + if (!chunks.isEmpty()) { + String latestMessage = chunks.get(chunks.size() - 1); + statusLabel.setText(latestMessage); + + // 更新进度条文本显示 + if (latestMessage.contains("%")) { + progressBar.setString(latestMessage); + progressBar.setStringPainted(true); + } + } + } + + @Override + protected void done() { + try { + Boolean success = get(); + if (success) { + // 题目生成成功,切换到考试界面 + statusLabel.setText("题目生成成功!正在加载界面..."); + progressBar.setValue(100); + + // 短暂延迟后初始化考试界面 + Timer timer = new Timer(500, e -> { + initializeExamUI(); + loadQuestion(); + }); + timer.setRepeats(false); + timer.start(); + } else { + // 生成失败,显示错误信息 + JOptionPane.showMessageDialog(ExamView.this, + "生成试卷失败,将使用基础题目继续考试", + "生成失败", + JOptionPane.WARNING_MESSAGE); + + // 即使生成失败也尝试继续 + initializeExamUI(); + loadQuestion(); + } + } catch (InterruptedException e) { + // 用户取消了操作 + navigationController.showQuestionCountView(level); + } catch (ExecutionException e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(ExamView.this, + "生成试卷时发生错误: " + e.getCause().getMessage(), + "错误", + JOptionPane.ERROR_MESSAGE); + navigationController.showQuestionCountView(level); + } + } + }; + + worker.execute(); + } + + private void initializeExamUI() { + // 移除进度显示 + removeAll(); + setLayout(new BorderLayout(10, 10)); + + // Header Panel + JPanel headerPanel = new JPanel(new BorderLayout()); + questionNumberLabel = new JLabel("", JLabel.CENTER); + questionNumberLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + headerPanel.add(questionNumberLabel, BorderLayout.CENTER); + + JLabel levelLabel = new JLabel(level + "数学", JLabel.RIGHT); + levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + headerPanel.add(levelLabel, BorderLayout.EAST); + + add(headerPanel, BorderLayout.NORTH); + + // Question Panel + JPanel questionPanel = new JPanel(new BorderLayout(10, 10)); + questionPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + questionContentLabel = new JLabel("", JLabel.CENTER); + questionContentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + questionPanel.add(questionContentLabel, BorderLayout.NORTH); + + // Options Panel - 修复选项状态管理 + JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 10, 10)); + optionsGroup = new ButtonGroup(); + optionButtons = new JRadioButton[4]; + + for (int i = 0; i < 4; i++) { + optionButtons[i] = new JRadioButton(); + optionButtons[i].setFont(new Font("微软雅黑", Font.PLAIN, 16)); + // 添加选项变化监听器,实时保存答案 + final int index = i; + optionButtons[i].addActionListener(e -> { + if (optionButtons[index].isSelected()) { + saveCurrentAnswer(); + } + }); + optionsGroup.add(optionButtons[i]); + optionsPanel.add(optionButtons[i]); + } + + questionPanel.add(optionsPanel, BorderLayout.CENTER); + add(questionPanel, BorderLayout.CENTER); + + // Navigation Panel + JPanel navPanel = new JPanel(new FlowLayout()); + + previousButton = new JButton("上一题"); + previousButton.addActionListener(e -> handlePrevious()); + navPanel.add(previousButton); + + nextButton = new JButton("下一题"); + nextButton.addActionListener(e -> handleNext()); + navPanel.add(nextButton); + + submitButton = new JButton("提交试卷"); + submitButton.addActionListener(e -> handleSubmit()); + navPanel.add(submitButton); + + // 添加返回按钮 + JButton backButton = new JButton("返回"); + backButton.addActionListener(e -> { + int result = JOptionPane.showConfirmDialog(ExamView.this, + "确定要返回吗?当前进度将丢失。", + "确认返回", + JOptionPane.YES_NO_OPTION); + + if (result == JOptionPane.YES_OPTION) { + navigationController.showQuestionCountView(level); + } + }); + navPanel.add(backButton); + + add(navPanel, BorderLayout.SOUTH); + + updateNavigationButtons(); + revalidate(); + repaint(); + } + + private void loadQuestion() { + Question currentQuestion = examController.getCurrentQuestion(); + if (currentQuestion != null) { + // 更新题号 + questionNumberLabel.setText("第 " + examController.getCurrentQuestionNumber() + " 题 / 共 " + + examController.getTotalQuestions() + " 题"); + + // 更新题目内容 + questionContentLabel.setText("
" + currentQuestion.getContent() + "
"); + + // 更新选项 - 关键修复:先清空所有选择 + optionsGroup.clearSelection(); + + String[] options = currentQuestion.getOptions(); + for (int i = 0; i < 4; i++) { + if (i < options.length) { + optionButtons[i].setText((char)('A' + i) + ". " + options[i]); + optionButtons[i].setVisible(true); + } else { + optionButtons[i].setText(""); // 清空多余的选项 + optionButtons[i].setVisible(false); + } + } + + // 如果有用户答案,选中对应的选项 + String userAnswer = currentQuestion.getUserAnswer(); + if (userAnswer != null && !userAnswer.trim().isEmpty()) { + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isVisible() && optionButtons[i].getText().contains(userAnswer)) { + optionButtons[i].setSelected(true); + break; + } + } + } + } else { + // 如果没有题目,显示错误信息 + questionContentLabel.setText("
题目加载失败,请返回重试
"); + } + + updateNavigationButtons(); + } + + private void handlePrevious() { + // 保存当前答案 + saveCurrentAnswer(); + examController.previousQuestion(); + loadQuestion(); + } + + private void handleNext() { + // 保存当前答案 + saveCurrentAnswer(); + examController.nextQuestion(); + loadQuestion(); + } + + private void handleSubmit() { + // 保存当前答案 + saveCurrentAnswer(); + + int result = JOptionPane.showConfirmDialog(this, + "确认提交试卷?提交后将显示成绩并保存试卷。", + "确认提交", + JOptionPane.YES_NO_OPTION); + + if (result == JOptionPane.YES_OPTION) { + // 保存试卷到文件 + boolean saveSuccess = examController.saveCurrentPaper(); + + int score = examController.calculateScore(); + int totalQuestions = examController.getTotalQuestions(); + + // 显示保存结果 + if (saveSuccess) { + JOptionPane.showMessageDialog(this, + "试卷已保存到您的文件夹中!", + "保存成功", + JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog(this, + "试卷保存失败,但成绩计算完成。", + "保存失败", + JOptionPane.WARNING_MESSAGE); + } + + navigationController.showScoreView(level, score, totalQuestions); + } + } + + private void saveCurrentAnswer() { + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isSelected() && optionButtons[i].isVisible()) { + try { + // 提取答案内容(去掉"A. "等前缀) + String fullText = optionButtons[i].getText(); + String answer = fullText.substring(fullText.indexOf(". ") + 2); + examController.submitAnswer(answer); + System.out.println("保存答案: " + answer); + break; + } catch (Exception e) { + System.err.println("保存答案时出错: " + e.getMessage()); + } + } + } + } + + private void updateNavigationButtons() { + boolean hasPrevious = examController.hasPreviousQuestion(); + boolean hasNext = examController.hasNextQuestion(); + + previousButton.setEnabled(hasPrevious); + nextButton.setEnabled(hasNext); + submitButton.setEnabled(!hasNext); // 只有最后一题才能提交 + + System.out.println("导航按钮状态 - 上一题: " + hasPrevious + ", 下一题: " + hasNext + ", 提交: " + !hasNext); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/LevelSelectionView.java b/src/com/mathlearning/view/LevelSelectionView.java new file mode 100644 index 0000000..1bd9370 --- /dev/null +++ b/src/com/mathlearning/view/LevelSelectionView.java @@ -0,0 +1,75 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; +import java.awt.*; + +public class LevelSelectionView extends JPanel { + private NavigationController navigationController; + private String userIdentifier; + private String email; + + private JButton primaryButton; + private JButton middleButton; + private JButton highButton; + private JButton logoutButton; + + public LevelSelectionView(NavigationController navigationController, String userIdentifier, String email) { + this.navigationController = navigationController; + this.userIdentifier = userIdentifier; + this.email = email; + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(15, 15, 15, 15); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("选择学习阶段", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // User info + gbc.gridy = 1; + String userInfo = "当前用户: " + userIdentifier; + if (!userIdentifier.equals(email)) { + userInfo += " (" + email + ")"; + } + JLabel userLabel = new JLabel(userInfo, JLabel.CENTER); + userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + add(userLabel, gbc); + + // Primary School Button + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 2; + primaryButton = new JButton("小学"); + primaryButton.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + primaryButton.addActionListener(e -> navigationController.showQuestionCountView("小学")); + add(primaryButton, gbc); + + // Middle School Button + gbc.gridx = 1; gbc.gridy = 2; + middleButton = new JButton("初中"); + middleButton.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + middleButton.addActionListener(e -> navigationController.showQuestionCountView("初中")); + add(middleButton, gbc); + + // High School Button + gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; + highButton = new JButton("高中"); + highButton.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + highButton.addActionListener(e -> navigationController.showQuestionCountView("高中")); + add(highButton, gbc); + + // Logout Button + gbc.gridy = 4; + logoutButton = new JButton("退出登录"); + logoutButton.addActionListener(e -> navigationController.showLoginView()); + add(logoutButton, gbc); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/LoginView.java b/src/com/mathlearning/view/LoginView.java new file mode 100644 index 0000000..d00b287 --- /dev/null +++ b/src/com/mathlearning/view/LoginView.java @@ -0,0 +1,110 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; +import java.awt.*; + +public class LoginView extends JPanel { + private NavigationController navigationController; + private AuthController authController; + + private JTextField identifierField; // 改为通用标识符字段 + private JPasswordField passwordField; + private JButton loginButton; + private JButton registerButton; + private JButton changePasswordButton; + private JLabel identifierLabel; + + public LoginView(NavigationController navigationController) { + this.navigationController = navigationController; + this.authController = new AuthController(); + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("数学学习软件", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // Identifier (用户名或邮箱) + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 1; + identifierLabel = new JLabel("用户名/邮箱:"); + add(identifierLabel, gbc); + + gbc.gridx = 1; gbc.gridy = 1; + identifierField = new JTextField(20); + add(identifierField, gbc); + + // Password + gbc.gridx = 0; gbc.gridy = 2; + add(new JLabel("密码:"), gbc); + + gbc.gridx = 1; gbc.gridy = 2; + passwordField = new JPasswordField(20); + add(passwordField, gbc); + + // Login Button + gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; + loginButton = new JButton("登录"); + loginButton.addActionListener(e -> handleLogin()); + add(loginButton, gbc); + + // Register Button + gbc.gridy = 4; + registerButton = new JButton("注册新账号"); + registerButton.addActionListener(e -> navigationController.showRegisterView()); + add(registerButton, gbc); + + // Change Password Button + gbc.gridy = 5; + changePasswordButton = new JButton("修改密码"); + changePasswordButton.addActionListener(e -> handleChangePassword()); + add(changePasswordButton, gbc); + + // 提示信息 + gbc.gridy = 6; + JLabel hintLabel = new JLabel("提示: 可以使用用户名或邮箱登录", JLabel.CENTER); + hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + hintLabel.setForeground(Color.GRAY); + add(hintLabel, gbc); + } + + private void handleLogin() { + String identifier = identifierField.getText().trim(); + String password = new String(passwordField.getPassword()); + + if (identifier.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入用户名/邮箱和密码", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (authController.login(identifier, password)) { + // 获取用户信息用于显示 + String email = authController.getUserEmail(identifier); + navigationController.showLevelSelectionView(identifier, email); + } else { + JOptionPane.showMessageDialog(this, "用户名/邮箱或密码错误", "登录失败", JOptionPane.ERROR_MESSAGE); + } + } + + private void handleChangePassword() { + String identifier = identifierField.getText().trim(); + if (identifier.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入用户名/邮箱", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + // 在实际应用中,这里应该验证用户身份 + navigationController.showChangePasswordView(identifier); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/QuestionCountView.java b/src/com/mathlearning/view/QuestionCountView.java new file mode 100644 index 0000000..7ca082c --- /dev/null +++ b/src/com/mathlearning/view/QuestionCountView.java @@ -0,0 +1,85 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; +import java.awt.*; + +public class QuestionCountView extends JPanel { + private NavigationController navigationController; + private String userIdentifier; + private String email; + private String level; + + private JTextField countField; + private JButton submitButton; + private JButton backButton; + + public QuestionCountView(NavigationController navigationController, String userIdentifier, String email, String level) { + this.navigationController = navigationController; + this.userIdentifier = userIdentifier; + this.email = email; + this.level = level; + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel(level + "数学题目生成", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // User info + gbc.gridy = 1; + String userInfo = "当前用户: " + userIdentifier; + if (!userIdentifier.equals(email)) { + userInfo += " (" + email + ")"; + } + JLabel userLabel = new JLabel(userInfo, JLabel.CENTER); + userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(userLabel, gbc); + + // Instruction + gbc.gridy = 2; + JLabel instructionLabel = new JLabel("请输入要生成的题目数量 (10-30):", JLabel.CENTER); + add(instructionLabel, gbc); + + // Count Input + gbc.gridy = 3; + countField = new JTextField(10); + countField.setHorizontalAlignment(JTextField.CENTER); + add(countField, gbc); + + // Submit Button + gbc.gridy = 4; + submitButton = new JButton("生成试卷"); + submitButton.addActionListener(e -> handleSubmit()); + add(submitButton, gbc); + + // Back Button + gbc.gridy = 5; + backButton = new JButton("返回"); + backButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email)); + add(backButton, gbc); + } + + private void handleSubmit() { + try { + int count = Integer.parseInt(countField.getText().trim()); + if (count < 10 || count > 30) { + JOptionPane.showMessageDialog(this, "请输入10-30之间的数字", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + navigationController.showExamView(level, count); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "请输入有效的数字", "错误", JOptionPane.ERROR_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/RegisterView.java b/src/com/mathlearning/view/RegisterView.java new file mode 100644 index 0000000..2039a22 --- /dev/null +++ b/src/com/mathlearning/view/RegisterView.java @@ -0,0 +1,210 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import com.mathlearning.controller.NavigationController; +import com.mathlearning.util.EmailUtil; +import com.mathlearning.util.UsernameValidator; + +import javax.swing.*; +import java.awt.*; + +public class RegisterView extends JPanel { + private NavigationController navigationController; + private AuthController authController; + + private JTextField emailField; + private JTextField usernameField; + private JButton sendCodeButton; + private JButton verifyButton; + private JButton backButton; + private JLabel statusLabel; + private JTextField codeField; + private String currentVerificationCode; + + public RegisterView(NavigationController navigationController) { + this.navigationController = navigationController; + this.authController = new AuthController(); + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // Username + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 1; + add(new JLabel("用户名:"), gbc); + + gbc.gridx = 1; gbc.gridy = 1; + usernameField = new JTextField(20); + add(usernameField, gbc); + + // Email + gbc.gridx = 0; gbc.gridy = 2; + add(new JLabel("邮箱地址:"), gbc); + + gbc.gridx = 1; gbc.gridy = 2; + emailField = new JTextField(20); + add(emailField, gbc); + + // Verification Code + gbc.gridx = 0; gbc.gridy = 3; + add(new JLabel("验证码:"), gbc); + + gbc.gridx = 1; gbc.gridy = 3; + JPanel codePanel = new JPanel(new BorderLayout()); + codeField = new JTextField(10); + codePanel.add(codeField, BorderLayout.CENTER); + + sendCodeButton = new JButton("发送验证码"); + sendCodeButton.addActionListener(e -> handleSendCode()); + codePanel.add(sendCodeButton, BorderLayout.EAST); + + add(codePanel, gbc); + + // Requirements + gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; + JLabel usernameReqLabel = new JLabel(UsernameValidator.getRequirements(), JLabel.CENTER); + usernameReqLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + usernameReqLabel.setForeground(Color.GRAY); + add(usernameReqLabel, gbc); + + gbc.gridy = 5; + JLabel emailReqLabel = new JLabel(EmailUtil.getEmailRequirements(), JLabel.CENTER); + emailReqLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + emailReqLabel.setForeground(Color.GRAY); + add(emailReqLabel, gbc); + + // Status label + gbc.gridy = 6; + statusLabel = new JLabel("", JLabel.CENTER); + statusLabel.setForeground(Color.BLUE); + add(statusLabel, gbc); + + // Verify Button + gbc.gridy = 7; + verifyButton = new JButton("验证并继续"); + verifyButton.addActionListener(e -> handleVerify()); + add(verifyButton, gbc); + + // Back Button + gbc.gridy = 8; + backButton = new JButton("返回登录"); + backButton.addActionListener(e -> navigationController.showLoginView()); + add(backButton, gbc); + } + + private void handleSendCode() { + String email = emailField.getText().trim(); + String username = usernameField.getText().trim(); + + // 验证输入 + if (username.isEmpty() || email.isEmpty()) { + showError("请输入用户名和邮箱地址"); + return; + } + + if (!EmailUtil.isValidEmail(email)) { + showError("请输入有效的邮箱地址"); + return; + } + + if (!UsernameValidator.isValid(username)) { + showError(UsernameValidator.getRequirements()); + return; + } + + // 检查邮箱和用户名是否可用 + if (!authController.isEmailAvailable(email)) { + showError("该邮箱已被注册"); + return; + } + + if (!authController.isUsernameAvailable(username)) { + showError("该用户名已被使用"); + return; + } + + // 禁用按钮,显示发送中状态 + sendCodeButton.setEnabled(false); + sendCodeButton.setText("发送中..."); + statusLabel.setText("正在发送验证码,请稍候..."); + statusLabel.setForeground(Color.BLUE); + + // 在后台线程中执行发送验证码 + SwingWorker worker = new SwingWorker() { + @Override + protected String doInBackground() throws Exception { + return authController.sendVerificationCode(email, username); + } + + @Override + protected void done() { + try { + currentVerificationCode = get(); + if (currentVerificationCode != null) { + statusLabel.setText("验证码已发送!请在下方输入验证码"); + statusLabel.setForeground(Color.GREEN); + codeField.setEnabled(true); + verifyButton.setEnabled(true); + } else { + showError("发送验证码失败,请检查邮箱地址或稍后重试"); + } + } catch (Exception e) { + showError("发送验证码时发生错误: " + e.getMessage()); + } finally { + // 恢复按钮状态 + sendCodeButton.setEnabled(true); + sendCodeButton.setText("发送验证码"); + } + } + }; + + worker.execute(); + } + + private void handleVerify() { + String email = emailField.getText().trim(); + String username = usernameField.getText().trim(); + String code = codeField.getText().trim(); + + if (code.isEmpty()) { + showError("请输入验证码"); + return; + } + + // 验证验证码 + if (currentVerificationCode != null && code.equals(currentVerificationCode)) { + // 验证码正确,创建未注册用户 + boolean success = authController.createUnregisteredUser(email, username, currentVerificationCode); + if (success) { + showSuccess("验证成功!"); + // 跳转到设置密码页面 + navigationController.showSetPasswordView(email, currentVerificationCode); + } else { + showError("创建用户失败"); + } + } else { + showError("验证码错误,请重新输入"); + } + } + + private void showError(String message) { + statusLabel.setText(message); + statusLabel.setForeground(Color.RED); + } + + private void showSuccess(String message) { + statusLabel.setText(message); + statusLabel.setForeground(Color.GREEN); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/ScoreView.java b/src/com/mathlearning/view/ScoreView.java new file mode 100644 index 0000000..6e17a98 --- /dev/null +++ b/src/com/mathlearning/view/ScoreView.java @@ -0,0 +1,111 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; +import java.awt.*; + +public class ScoreView extends JPanel { + private NavigationController navigationController; + private String userIdentifier; + private String email; + private String level; + private int score; + private int totalQuestions; + + private JLabel scoreLabel; + private JLabel commentLabel; + private JButton retryButton; + private JButton newExamButton; + private JButton logoutButton; + + public ScoreView(NavigationController navigationController, String userIdentifier, String email, String level, int score, int totalQuestions) { + this.navigationController = navigationController; + this.userIdentifier = userIdentifier; + this.email = email; + this.level = level; + this.score = score; + this.totalQuestions = totalQuestions; + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(15, 15, 15, 15); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("考试结果", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // User info + gbc.gridy = 1; + String userInfo = "用户: " + userIdentifier; + if (!userIdentifier.equals(email)) { + userInfo += " (" + email + ")"; + } + JLabel userLabel = new JLabel(userInfo, JLabel.CENTER); + userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(userLabel, gbc); + + // Level info + gbc.gridy = 2; + JLabel levelLabel = new JLabel("级别: " + level, JLabel.CENTER); + levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(levelLabel, gbc); + + // Score Display + gbc.gridy = 3; + scoreLabel = new JLabel("得分: " + score + " / 100", JLabel.CENTER); + scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + scoreLabel.setForeground(getScoreColor()); + add(scoreLabel, gbc); + + // Comment + gbc.gridy = 4; + commentLabel = new JLabel(getScoreComment(), JLabel.CENTER); + commentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + add(commentLabel, gbc); + + // Stats + gbc.gridy = 5; + int correctCount = (score * totalQuestions) / 100; + JLabel statsLabel = new JLabel("正确题数: " + correctCount + " / " + totalQuestions, JLabel.CENTER); + statsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + add(statsLabel, gbc); + + // Retry Button + gbc.gridy = 6; + retryButton = new JButton("重新答题"); + retryButton.addActionListener(e -> navigationController.showExamView(level, totalQuestions)); + add(retryButton, gbc); + + // New Exam Button + gbc.gridy = 7; + newExamButton = new JButton("新的测试"); + newExamButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email)); + add(newExamButton, gbc); + + // Logout Button + gbc.gridy = 8; + logoutButton = new JButton("退出登录"); + logoutButton.addActionListener(e -> navigationController.showLoginView()); + add(logoutButton, gbc); + } + + private Color getScoreColor() { + if (score >= 80) return new Color(0, 128, 0); // 绿色 + if (score >= 60) return new Color(255, 165, 0); // 橙色 + return Color.RED; + } + + private String getScoreComment() { + if (score >= 90) return "优秀!继续保持!"; + if (score >= 80) return "良好!还有进步空间!"; + if (score >= 60) return "及格!需要多加练习!"; + return "不及格!需要认真学习!"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/SetPasswordView.java b/src/com/mathlearning/view/SetPasswordView.java new file mode 100644 index 0000000..28fa1a0 --- /dev/null +++ b/src/com/mathlearning/view/SetPasswordView.java @@ -0,0 +1,100 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import com.mathlearning.controller.NavigationController; +import com.mathlearning.util.PasswordValidator; + +import javax.swing.*; +import java.awt.*; + +public class SetPasswordView extends JPanel { + private NavigationController navigationController; + private AuthController authController; + private String email; + + private JPasswordField passwordField; + private JPasswordField confirmPasswordField; + private JButton submitButton; + private JButton backButton; + + public SetPasswordView(NavigationController navigationController, String email, String verificationCode) { + this.navigationController = navigationController; + this.authController = new AuthController(); + this.email = email; + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("设置密码", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // User info + gbc.gridy = 1; + JLabel userInfoLabel = new JLabel("邮箱: " + email, JLabel.CENTER); + userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(userInfoLabel, gbc); + + // Password requirements + gbc.gridy = 2; + JLabel requirementsLabel = new JLabel("密码要求: 6-10位,包含大小写字母和数字", JLabel.CENTER); + requirementsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(requirementsLabel, gbc); + + // Password + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 3; + add(new JLabel("密码:"), gbc); + + gbc.gridx = 1; gbc.gridy = 3; + passwordField = new JPasswordField(20); + add(passwordField, gbc); + + // Confirm Password + gbc.gridx = 0; gbc.gridy = 4; + add(new JLabel("确认密码:"), gbc); + + gbc.gridx = 1; gbc.gridy = 4; + confirmPasswordField = new JPasswordField(20); + add(confirmPasswordField, gbc); + + // Submit Button + gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 2; + submitButton = new JButton("设置密码"); + submitButton.addActionListener(e -> handleSetPassword()); + add(submitButton, gbc); + + // Back Button + gbc.gridy = 6; + backButton = new JButton("返回"); + backButton.addActionListener(e -> navigationController.showLoginView()); + add(backButton, gbc); + } + + private void handleSetPassword() { + String password = new String(passwordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + if (!PasswordValidator.isValid(password)) { + JOptionPane.showMessageDialog(this, + "密码不符合要求!\n请确保密码:\n- 长度6-10位\n- 包含大写字母\n- 包含小写字母\n- 包含数字", + "密码错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (authController.setPassword(email, password, confirmPassword)) { + JOptionPane.showMessageDialog(this, "密码设置成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + // 登录成功后使用邮箱作为标识符 + navigationController.showLevelSelectionView(email, email); + } else { + JOptionPane.showMessageDialog(this, "密码设置失败,请检查输入", "错误", JOptionPane.ERROR_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/email_config.properties.txt b/src/email_config.properties.txt new file mode 100644 index 0000000..76d2365 --- /dev/null +++ b/src/email_config.properties.txt @@ -0,0 +1,5 @@ +email.from=1393110457@qq.com +email.password=yuslygpkwwbtfedh +email.smtp.host=smtp.qq.com +email.smtp.port=465 +email.smtp.ssl=true \ No newline at end of file diff --git a/src/exam_papers/wjw/2025-10-11-13-29-32.txt b/src/exam_papers/wjw/2025-10-11-13-29-32.txt new file mode 100644 index 0000000..8dd9576 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-29-32.txt @@ -0,0 +1,145 @@ +试卷级别: 小学 +生成时间: 2025年10月11日 13时29分32秒 +题目数量: 20 + +1. 21 - 4 + 70 = ? + A. 75 + B. 87 + C. 72 + D. 101 + +2. 76 - 32 + 23 - 21 = ? + A. 39 + B. 38 + C. 46 + D. 53 + +3. 64 + 86 + 25 × 21 = ? + A. 675 + B. 792 + C. 628 + D. 771 + +4. 8 + 75 + 74 + 81 = ? + A. 238 + B. 198 + C. 241 + D. 244 + +5. 79 - 6 + 86 = ? + A. 159 + B. 138 + C. 137 + D. 185 + +6. 85 + 9 - 24 = ? + A. 73 + B. 70 + C. 58 + D. 78 + +7. 91 + 20 + 42 = ? + A. 134 + B. 125 + C. 143 + D. 153 + +8. 11 + 71 × 8 + 52 = ? + A. 675 + B. 631 + C. 601 + D. 511 + +9. 64 + 54 - 75 = ? + A. 43 + B. 44 + C. 39 + D. 38 + +10. 30 + 85 - 42 = ? + A. 84 + B. 64 + C. 58 + D. 73 + +11. 22 + 94 + 89 - 97 = ? + A. 108 + B. 88 + C. 105 + D. 94 + +12. 62 + 27 - 21 = ? + A. 70 + B. 68 + C. 63 + D. 79 + +13. 74 + 17 - 5 = ? + A. 85 + B. 86 + C. 92 + D. 90 + +14. 94 - 6 + 61 = ? + A. 120 + B. 135 + C. 149 + D. 121 + +15. 89 + 77 + 60 + 26 = ? + A. 227 + B. 291 + C. 299 + D. 252 + +16. 100 + 22 - 50 = ? + A. 86 + B. 72 + C. 84 + D. 80 + +17. 69 ÷ 1 + 92 = ? + A. 129 + B. 161 + C. 135 + D. 146 + +18. 98 + 44 + 97 × 4 = ? + A. 530 + B. 463 + C. 482 + D. 433 + +19. 98 + 80 - 68 = ? + A. 107 + B. 110 + C. 119 + D. 127 + +20. 32 + 5 × 36 = ? + A. 243 + B. 179 + C. 240 + D. 212 + +=== 参考答案 === +1. 87 +2. 46 +3. 675 +4. 238 +5. 159 +6. 70 +7. 153 +8. 631 +9. 43 +10. 73 +11. 108 +12. 68 +13. 86 +14. 149 +15. 252 +16. 72 +17. 161 +18. 530 +19. 110 +20. 212 diff --git a/src/exam_papers/wjw/2025-10-11-13-29-45.txt b/src/exam_papers/wjw/2025-10-11-13-29-45.txt new file mode 100644 index 0000000..3594b30 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-29-45.txt @@ -0,0 +1,75 @@ +试卷级别: 初中 +生成时间: 2025年10月11日 13时29分45秒 +题目数量: 10 + +1. 64² = ? + A. 4468 + B. 4122 + C. 4256 + D. 4096 + +2. √36 = ? + A. 7 + B. 5 + C. 6 + D. 4 + +3. 13² = ? + A. 144 + B. 178 + C. 200 + D. 169 + +4. 40² = ? + A. 1567 + B. 1439 + C. 1600 + D. 1369 + +5. √49 = ? + A. 6 + B. 5 + C. 7 + D. 8 + +6. (16 + 1)² = ? + A. 339 + B. 289 + C. 326 + D. 346 + +7. 97 × 57 + 15 = ? + A. 6620 + B. 5544 + C. 5391 + D. 5893 + +8. 66² = ? + A. 4107 + B. 4356 + C. 4323 + D. 4763 + +9. √100 = ? + A. 9 + B. 8 + C. 11 + D. 10 + +10. (22 + 31)² = ? + A. 2942 + B. 2654 + C. 2648 + D. 2809 + +=== 参考答案 === +1. 4096 +2. 6 +3. 169 +4. 1600 +5. 7 +6. 289 +7. 5544 +8. 4356 +9. 10 +10. 2809 diff --git a/src/exam_papers/wjw/2025-10-11-13-30-14.txt b/src/exam_papers/wjw/2025-10-11-13-30-14.txt new file mode 100644 index 0000000..96076ff --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-30-14.txt @@ -0,0 +1,75 @@ +试卷级别: 高中 +生成时间: 2025年10月11日 13时30分14秒 +题目数量: 10 + +1. 70 + cos(0°) - 91 = ? + A. -18.61 + B. -21.55 + C. -20.00 + D. -23.39 + +2. cos(150°) = ? + A. -0.87 + B. -0.74 + C. -0.89 + D. -1.01 + +3. 53 + tan(60°) - 54 = ? + A. 0.63 + B. 0.62 + C. 0.73 + D. 0.77 + +4. sin(150°) = ? + A. 0.53 + B. 0.42 + C. 0.50 + D. 0.60 + +5. 8 + sin(45°) - 82 = ? + A. -61.24 + B. -65.98 + C. -87.77 + D. -73.29 + +6. 73 - cos(180°) - 24 = ? + A. 50.00 + B. 41.82 + C. 59.18 + D. 56.02 + +7. cos(45°) = ? + A. 0.71 + B. 0.83 + C. 0.70 + D. 0.61 + +8. cos(120°) = ? + A. -0.53 + B. -0.50 + C. -0.59 + D. -0.42 + +9. 59 - cos(120°) - 29 = ? + A. 30.50 + B. 30.45 + C. 33.74 + D. 27.00 + +10. cos(135°) = ? + A. -0.77 + B. -0.70 + C. -0.71 + D. -0.61 + +=== 参考答案 === +1. -20.00 +2. -0.87 +3. 0.73 +4. 0.50 +5. -73.29 +6. 50.00 +7. 0.71 +8. -0.50 +9. 30.50 +10. -0.71 diff --git a/src/exam_papers/wjw/2025-10-11-13-40-07.txt b/src/exam_papers/wjw/2025-10-11-13-40-07.txt new file mode 100644 index 0000000..0e23104 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-40-07.txt @@ -0,0 +1,75 @@ +试卷级别: 小学 +生成时间: 2025年10月11日 13时40分07秒 +题目数量: 10 + +1. 5 + 60 = ? + A. 63 + B. 71 + C. 59 + D. 65 + +2. 68 + 22 = ? + A. 87 + B. 90 + C. 78 + D. 76 + +3. 77 + 26 = ? + A. 103 + B. 84 + C. 108 + D. 106 + +4. 62 + 59 = ? + A. 142 + B. 121 + C. 123 + D. 103 + +5. 6 × 6 = ? + A. 32 + B. 36 + C. 38 + D. 42 + +6. 96 + 35 = ? + A. 127 + B. 120 + C. 131 + D. 106 + +7. 96 + 12 = ? + A. 89 + B. 120 + C. 108 + D. 115 + +8. 66 + 2 = ? + A. 55 + B. 77 + C. 74 + D. 68 + +9. 99 - 37 = ? + A. 68 + B. 50 + C. 59 + D. 62 + +10. 81 - 11 = ? + A. 57 + B. 56 + C. 75 + D. 70 + +=== 参考答案 === +1. 65 +2. 90 +3. 103 +4. 121 +5. 36 +6. 131 +7. 108 +8. 68 +9. 62 +10. 70 diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 931f68b..0000000 --- a/src/main.py +++ /dev/null @@ -1,5 +0,0 @@ -from app import MathApp - -if __name__ == "__main__": - app = MathApp() - app.mainloop() \ No newline at end of file diff --git a/src/quiz_generator.py b/src/quiz_generator.py deleted file mode 100644 index 1ec63b0..0000000 --- a/src/quiz_generator.py +++ /dev/null @@ -1,96 +0,0 @@ - -import random -from abc import ABC, abstractmethod - -# 安全的表达式求值函数 -def safe_eval(expression): - try: - return eval(expression, {"__builtins__": None}, {}) - except Exception: - return None - -class Question: - def __init__(self, text, options, correct_answer_index): - self.text = text - self.options = options - self.correct_answer_index = correct_answer_index - -class QuestionGenerator(ABC): - def generate_bunch(self, count): - questions = [] - generated_texts = set() - while len(questions) < count: - text, answer = self._create_question_and_answer() - if text not in generated_texts: - options = self._generate_options(answer) - correct_index = options.index(answer) - - questions.append(Question(text, options, correct_index)) - generated_texts.add(text) - return questions - - def _generate_options(self, correct_answer): - options = {correct_answer} - while len(options) < 4: - offset = random.randint(-10, 10) - if offset == 0: - offset = random.choice([-1, 1]) * 10 - - distractor = correct_answer + offset - options.add(distractor) - - option_list = list(options) - random.shuffle(option_list) - return option_list - - @abstractmethod - def _create_question_and_answer(self): - pass - -class PrimarySchoolGenerator(QuestionGenerator): - def _create_question_and_answer(self): - ops = ['+', '-', '*', '/'] - op = random.choice(ops) - - if op == '+': - num1, num2 = random.randint(1, 100), random.randint(1, 100) - expr = f"{num1} + {num2}" - elif op == '-': - num1, num2 = random.randint(1, 100), random.randint(1, 100) - if num1 < num2: num1, num2 = num2, num1 - expr = f"{num1} - {num2}" - elif op == '*': - num1, num2 = random.randint(1, 20), random.randint(1, 20) - expr = f"{num1} * {num2}" - else: # op == '/' - divisor = random.randint(2, 10) - answer = random.randint(2, 20) - dividend = divisor * answer - expr = f"{dividend} / {divisor}" - - return expr, int(safe_eval(expr)) - -class MiddleSchoolGenerator(PrimarySchoolGenerator): - def _create_question_and_answer(self): - if random.choice([True, False]): - return super()._create_question_and_answer() - else: - base = random.randint(2, 10) - expr = f"{base}**2" - return expr, int(safe_eval(expr)) - -class HighSchoolGenerator(MiddleSchoolGenerator): - def _create_question_and_answer(self): - # 简化版高中题,可扩展为三角函数等 - return super()._create_question_and_answer() - -def get_generator(level): - generators = { - "小学": PrimarySchoolGenerator, - "初中": MiddleSchoolGenerator, - "高中": HighSchoolGenerator, - } - generator_class = generators.get(level) - if generator_class: - return generator_class() - raise ValueError("无效的等级") \ No newline at end of file diff --git a/src/user_manager.py b/src/user_manager.py deleted file mode 100644 index befba82..0000000 --- a/src/user_manager.py +++ /dev/null @@ -1,105 +0,0 @@ -# src/user_manager.py -import json -import hashlib -import os -import re -import smtplib -import ssl -from email.message import EmailMessage - -def send_verification_email(recipient_email, code): - """ - 发送验证码邮件的函数。 - """ - # --- 配置区域 --- - SENDER_EMAIL = "876413020@qq.com" - APP_PASSWORD = "wbeyuimdbvkxbdgf" - SMTP_SERVER = "smtp.qq.com" - SMTP_PORT = 465 - - # --- 构建邮件内容 --- - msg = EmailMessage() - msg['Subject'] = "【数学学习软件】您的注册验证码" - msg['From'] = SENDER_EMAIL - msg['To'] = recipient_email - msg.set_content( - f"您好!\n\n" - f"感谢您注册数学学习软件。您的验证码是:\n\n" - f"【 {code} 】\n\n" - f"请在5分钟内使用。如果不是您本人操作,请忽略此邮件。" - ) - - # --- 发送邮件(健壮的结构)--- - smtp_server = None - try: - context = ssl.create_default_context() - smtp_server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) - smtp_server.login(SENDER_EMAIL, APP_PASSWORD) - smtp_server.send_message(msg) - - print(f"邮件已成功发送至 {recipient_email}") - return True - - except smtplib.SMTPAuthenticationError: - print("邮件发送失败: 认证错误。请检查邮箱或授权码是否正确。") - return False - except Exception as e: - print(f"邮件发送时发生错误: {e}") - return False - finally: - if smtp_server: - try: - smtp_server.quit() - except Exception: - pass - -class UserManager: - def __init__(self): - self.users = self._load_users() - - def _load_users(self): - if os.path.exists("users.json"): - try: - with open("users.json", 'r', encoding='utf-8') as f: - return json.load(f) - except (json.JSONDecodeError, IOError): - return {} - return {} - - def _save_users(self): - with open("users.json", 'w', encoding='utf-8') as f: - json.dump(self.users, f, indent=4) - - def _hash_password(self, password, salt): - return hashlib.sha256((password + salt).encode()).hexdigest() - - def is_email_registered(self, email): - return email in self.users - - def validate_password_format(self, password): - """密码必须 6-10 位,包含大小写字母和数字""" - if 6 <= len(password) <= 10 and \ - re.search(r"[a-z]", password) and \ - re.search(r"[A-Z]", password) and \ - re.search(r"\d", password): - return True - return False - - def register_user(self, email, password): - if self.is_email_registered(email): - return False, "该邮箱已被注册。" - - salt = os.urandom(16).hex() - password_hash = self._hash_password(password, salt) - self.users[email] = {"password_hash": password_hash, "salt": salt} - self._save_users() - return True, "注册成功!" - - def validate_login(self, email, password): - if not self.is_email_registered(email): - return False - - user_data = self.users[email] - salt = user_data["salt"] - password_hash = self._hash_password(password, salt) - return password_hash == user_data["password_hash"] \ No newline at end of file diff --git a/src/users.dat b/src/users.dat new file mode 100644 index 0000000000000000000000000000000000000000..f9addffb73f903391b0ceda269985315e87d6602 GIT binary patch literal 255 zcmW-bK~BRk5Je|xLksGnWx<9!xHU~d6$>g=sz3@GstSn}qu3;_oH((ECaa!^OCV0b zmOF3<9KQ7DOMmg^fBZt^EKK%zK=w{bvwL*;BUaJi=kx30^}7e*2F|oWvB8v#eSlNS zt#rB3qOI;$&}q~R{T+J;1QR}A&N-jOiNoLBCv{EDx$pV!oI8VBTQ;lUd-IDcy+XRLcI@cI0YB)kaf$>3F^`=rJ eWy-bZ|MzVbbQuy1p7+fxzTyNY@ikv8`t4U{33rs`~t83 zga6>B=!1E~3~%1N_aE?#hT{VF$aW+oyFruRVeO5+KR!=izFcs&Fp&z=EheP&EsQDG z+~i88U46ZV9@EyhJhSBxOoA|4Hw{ZvX`Rn(V##;D1w+;FrU+}9GZ8*%rku#uU Date: Sat, 11 Oct 2025 16:30:55 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/META-INF/MANIFEST.MF | 3 + src/com/mathlearning/Main.java | 15 + .../controller/AuthController.java | 149 +++++++ .../controller/ExamController.java | 247 +++++++++++ .../controller/NavigationController.java | 86 ++++ src/com/mathlearning/model/Paper.java | 73 ++++ src/com/mathlearning/model/Question.java | 39 ++ .../mathlearning/model/QuestionGenerator.java | 296 +++++++++++++ src/com/mathlearning/model/User.java | 42 ++ src/com/mathlearning/model/UserManager.java | 118 ++++++ src/com/mathlearning/util/EmailConfig.java | 62 +++ src/com/mathlearning/util/EmailUtil.java | 133 ++++++ src/com/mathlearning/util/ExamFileUtil.java | 86 ++++ src/com/mathlearning/util/FileStorage.java | 52 +++ .../mathlearning/util/PasswordValidator.java | 21 + .../mathlearning/util/UsernameValidator.java | 16 + src/com/mathlearning/view/ExamView.java | 397 ++++++++++++++++++ .../mathlearning/view/LevelSelectionView.java | 75 ++++ src/com/mathlearning/view/LoginView.java | 110 +++++ .../mathlearning/view/QuestionCountView.java | 85 ++++ src/com/mathlearning/view/RegisterView.java | 210 +++++++++ src/com/mathlearning/view/ScoreView.java | 111 +++++ .../mathlearning/view/SetPasswordView.java | 100 +++++ src/email_config.properties.txt | 5 + src/exam_papers/wjw/2025-10-11-13-29-32.txt | 145 +++++++ src/exam_papers/wjw/2025-10-11-13-29-45.txt | 75 ++++ src/exam_papers/wjw/2025-10-11-13-30-14.txt | 75 ++++ src/exam_papers/wjw/2025-10-11-13-40-07.txt | 75 ++++ src/users.dat | Bin 0 -> 255 bytes src/users_email.dat | Bin 0 -> 310 bytes src/users_username.dat | Bin 0 -> 310 bytes 31 files changed, 2901 insertions(+) create mode 100644 src/META-INF/MANIFEST.MF create mode 100644 src/com/mathlearning/Main.java create mode 100644 src/com/mathlearning/controller/AuthController.java create mode 100644 src/com/mathlearning/controller/ExamController.java create mode 100644 src/com/mathlearning/controller/NavigationController.java create mode 100644 src/com/mathlearning/model/Paper.java create mode 100644 src/com/mathlearning/model/Question.java create mode 100644 src/com/mathlearning/model/QuestionGenerator.java create mode 100644 src/com/mathlearning/model/User.java create mode 100644 src/com/mathlearning/model/UserManager.java create mode 100644 src/com/mathlearning/util/EmailConfig.java create mode 100644 src/com/mathlearning/util/EmailUtil.java create mode 100644 src/com/mathlearning/util/ExamFileUtil.java create mode 100644 src/com/mathlearning/util/FileStorage.java create mode 100644 src/com/mathlearning/util/PasswordValidator.java create mode 100644 src/com/mathlearning/util/UsernameValidator.java create mode 100644 src/com/mathlearning/view/ExamView.java create mode 100644 src/com/mathlearning/view/LevelSelectionView.java create mode 100644 src/com/mathlearning/view/LoginView.java create mode 100644 src/com/mathlearning/view/QuestionCountView.java create mode 100644 src/com/mathlearning/view/RegisterView.java create mode 100644 src/com/mathlearning/view/ScoreView.java create mode 100644 src/com/mathlearning/view/SetPasswordView.java create mode 100644 src/email_config.properties.txt create mode 100644 src/exam_papers/wjw/2025-10-11-13-29-32.txt create mode 100644 src/exam_papers/wjw/2025-10-11-13-29-45.txt create mode 100644 src/exam_papers/wjw/2025-10-11-13-30-14.txt create mode 100644 src/exam_papers/wjw/2025-10-11-13-40-07.txt create mode 100644 src/users.dat create mode 100644 src/users_email.dat create mode 100644 src/users_username.dat diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..6eea194 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.mathlearning.Main + diff --git a/src/com/mathlearning/Main.java b/src/com/mathlearning/Main.java new file mode 100644 index 0000000..612da2f --- /dev/null +++ b/src/com/mathlearning/Main.java @@ -0,0 +1,15 @@ +package com.mathlearning; + +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; + +public class Main { + public static void main(String[] args) { + // 直接启动程序,不使用特殊的外观设置 + SwingUtilities.invokeLater(() -> { + NavigationController navigationController = new NavigationController(); + navigationController.showLoginView(); + }); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/AuthController.java b/src/com/mathlearning/controller/AuthController.java new file mode 100644 index 0000000..ccfbc95 --- /dev/null +++ b/src/com/mathlearning/controller/AuthController.java @@ -0,0 +1,149 @@ +package com.mathlearning.controller; + +import com.mathlearning.model.User; +import com.mathlearning.model.UserManager; +import com.mathlearning.util.EmailUtil; +import com.mathlearning.util.PasswordValidator; +import com.mathlearning.util.UsernameValidator; + +public class AuthController { + private UserManager userManager; + + public AuthController() { + this.userManager = UserManager.getInstance(); + } + + /** + * 发送验证码(不创建用户) + */ + public String sendVerificationCode(String email, String username) { + if (!isValidEmail(email)) { + return null; + } + + if (!UsernameValidator.isValid(username)) { + return null; + } + + // 检查邮箱是否已被注册 + if (userManager.userExistsByEmail(email)) { + User existingUser = userManager.getUserByEmail(email); + if (existingUser.isRegistered()) { + return null; // 邮箱已被注册 + } + // 如果用户存在但未注册,可以重新发送验证码 + } + + // 检查用户名是否已被使用 + if (userManager.userExistsByUsername(username)) { + User existingUser = userManager.getUserByUsername(username); + if (existingUser.isRegistered() || !existingUser.getEmail().equals(email)) { + return null; // 用户名已被其他用户使用 + } + // 如果是同一邮箱重新发送验证码,允许使用相同的用户名 + } + + // 发送验证码 + String verificationCode = EmailUtil.sendVerificationCode(email); + return verificationCode; + } + + /** + * 创建未注册用户(保存验证码) + */ + public boolean createUnregisteredUser(String email, String username, String verificationCode) { + if (verificationCode == null) { + return false; + } + + // 检查是否已存在未注册用户 + User existingUser = userManager.getUserByEmail(email); + if (existingUser != null && !existingUser.isRegistered()) { + // 更新现有未注册用户的验证码 + existingUser.setUsername(username); + existingUser.setVerificationCode(verificationCode); + } else { + // 创建新用户 + User user = new User(email, username); + user.setVerificationCode(verificationCode); + userManager.addUser(user); + } + + return true; + } + + public boolean verifyCode(String email, String code) { + User user = userManager.getUserByEmail(email); + return user != null && code.equals(user.getVerificationCode()); + } + + public boolean setPassword(String email, String password, String confirmPassword) { + if (!password.equals(confirmPassword)) { + return false; + } + + if (!PasswordValidator.isValid(password)) { + return false; + } + + User user = userManager.getUserByEmail(email); + if (user != null) { + user.setPassword(password); + user.setRegistered(true); + userManager.saveUsers(); + return true; + } + + return false; + } + + public boolean login(String identifier, String password) { + User user = userManager.getUser(identifier); + return user != null && user.isRegistered() && + password.equals(user.getPassword()); + } + + public boolean changePassword(String identifier, String oldPassword, String newPassword, String confirmPassword) { + if (!newPassword.equals(confirmPassword)) { + return false; + } + + if (!PasswordValidator.isValid(newPassword)) { + return false; + } + + User user = userManager.getUser(identifier); + if (user != null && oldPassword.equals(user.getPassword())) { + user.setPassword(newPassword); + userManager.saveUsers(); + return true; + } + + return false; + } + + private boolean isValidEmail(String email) { + return EmailUtil.isValidEmail(email); + } + + public String getUserEmail(String identifier) { + User user = userManager.getUser(identifier); + return user != null ? user.getEmail() : identifier; + } + + /** + * 检查邮箱是否可用 + */ + public boolean isEmailAvailable(String email) { + User user = userManager.getUserByEmail(email); + return user == null || !user.isRegistered(); + } + + /** + * 检查用户名是否可用 + */ + public boolean isUsernameAvailable(String username) { + User user = userManager.getUserByUsername(username); + return user == null || !user.isRegistered(); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/ExamController.java b/src/com/mathlearning/controller/ExamController.java new file mode 100644 index 0000000..4d25f85 --- /dev/null +++ b/src/com/mathlearning/controller/ExamController.java @@ -0,0 +1,247 @@ +package com.mathlearning.controller; + +import com.mathlearning.model.*; +import com.mathlearning.util.ExamFileUtil; + +import java.util.*; + +public class ExamController { + private Paper currentPaper; + private String currentUser; + private String currentLevel; + + public ExamController() { + } + + public void createExam(String level, int questionCount) { + System.out.println("=== 开始创建试卷 ==="); + System.out.println("级别: " + level + ", 题目数量: " + questionCount); + + long startTime = System.currentTimeMillis(); + this.currentLevel = level; + + try { + // 使用工厂方法创建对应的生成器 + QuestionGenerator generator = QuestionGenerator.createGenerator(level); + System.out.println("生成器创建完成: " + generator.getClass().getSimpleName()); + + // 设置超时保护 + List questions = generateQuestionsWithTimeout(generator, questionCount, 15000); // 15秒超时 + + System.out.println("题目生成完成,生成了 " + questions.size() + " 道题目"); + + // 额外的重复检查 + int duplicateCount = checkForDuplicates(questions); + if (duplicateCount > 0) { + System.out.println("警告: 发现 " + duplicateCount + " 道重复题目"); + } + + currentPaper = new Paper(level); + for (Question question : questions) { + currentPaper.addQuestion(question); + } + + long endTime = System.currentTimeMillis(); + System.out.println("试卷创建完成,耗时: " + (endTime - startTime) + "ms"); + System.out.println("=== 试卷创建结束 ==="); + + } catch (Exception e) { + System.err.println("创建试卷时发生错误: " + e.getMessage()); + e.printStackTrace(); + // 创建空试卷避免空指针 + currentPaper = new Paper(level); + // 补充基础题目 + List basicQuestions = generateBasicQuestions(questionCount); + for (Question question : basicQuestions) { + currentPaper.addQuestion(question); + } + } + } + + /** + * 带超时保护的题目生成 + */ + private List generateQuestionsWithTimeout(QuestionGenerator generator, int count, long timeoutMs) { + long startTime = System.currentTimeMillis(); + List questions = new ArrayList<>(); + + try { + List generated = generator.generateQuestions(count); + questions.addAll(generated); + + // 检查是否超时 + if ((System.currentTimeMillis() - startTime) > timeoutMs) { + System.out.println("生成题目超时,使用已生成的部分题目"); + } + + // 如果生成的题目超过所需数量,截取 + if (questions.size() > count) { + questions = questions.subList(0, count); + } + + } catch (Exception e) { + System.err.println("生成题目时出错: " + e.getMessage()); + } + + // 如果题目数量不足,补充基础题目 + if (questions.size() < count) { + System.out.println("题目数量不足,补充基础题目..."); + int needed = count - questions.size(); + List basicQuestions = generateBasicQuestions(needed); + questions.addAll(basicQuestions); + } + + return questions; + } + + /** + * 生成基础题目作为备选 + */ + private List generateBasicQuestions(int count) { + System.out.println("生成 " + count + " 道基础题目"); + List basicQuestions = new ArrayList<>(); + Random random = new Random(); + + for (int i = 0; i < count; i++) { + int a = random.nextInt(10) + 1; + int b = random.nextInt(10) + 1; + int operation = random.nextInt(2); // 0: 加法, 1: 减法 + + String expression; + int answer; + + if (operation == 0) { + // 加法 + expression = a + " + " + b; + answer = a + b; + } else { + // 减法,确保结果为正 + if (a < b) { + int temp = a; + a = b; + b = temp; + } + expression = a + " - " + b; + answer = a - b; + } + + // 生成选项 + Set options = new HashSet<>(); + options.add(String.valueOf(answer)); + + while (options.size() < 4) { + int variation = random.nextInt(5) + 1; + int wrongAnswer; + if (random.nextBoolean()) { + wrongAnswer = answer + variation; + } else { + wrongAnswer = Math.max(1, answer - variation); + } + options.add(String.valueOf(wrongAnswer)); + } + + List optionList = new ArrayList<>(options); + Collections.shuffle(optionList); + String optionsStr = String.join(",", optionList); + + basicQuestions.add(new Question(expression + " = ?", optionsStr, String.valueOf(answer))); + } + + return basicQuestions; + } + + /** + * 保存当前试卷到文件 + * @return 是否保存成功 + */ + public boolean saveCurrentPaper() { + if (currentPaper == null || currentUser == null) { + System.err.println("无法保存试卷: 试卷或用户信息为空"); + return false; + } + + return ExamFileUtil.savePaperToFile(currentUser, currentPaper); + } + + // 检查重复题目的辅助方法 + private int checkForDuplicates(List questions) { + Set expressions = new HashSet<>(); + int duplicateCount = 0; + + for (Question question : questions) { + String expression = extractExpression(question.getContent()); + if (expressions.contains(expression)) { + duplicateCount++; + System.out.println("发现重复题目: " + question.getContent()); + } else { + expressions.add(expression); + } + } + + return duplicateCount; + } + + // 从题目内容中提取表达式(去掉"= ?"等后缀) + private String extractExpression(String content) { + if (content == null) return ""; + // 去掉"= ?"和可能的空格 + return content.replace(" = ?", "").trim(); + } + + // 其他方法保持不变... + public Question getCurrentQuestion() { + return currentPaper != null ? currentPaper.getCurrentQuestion() : null; + } + + public boolean hasNextQuestion() { + return currentPaper != null && currentPaper.hasNextQuestion(); + } + + public void nextQuestion() { + if (currentPaper != null) { + currentPaper.nextQuestion(); + } + } + + public boolean hasPreviousQuestion() { + return currentPaper != null && currentPaper.hasPreviousQuestion(); + } + + public void previousQuestion() { + if (currentPaper != null) { + currentPaper.previousQuestion(); + } + } + + public void submitAnswer(String answer) { + Question currentQuestion = getCurrentQuestion(); + if (currentQuestion != null) { + currentQuestion.setUserAnswer(answer); + System.out.println("题目 " + getCurrentQuestionNumber() + " 答案设置为: " + answer); + } + } + + public int calculateScore() { + return currentPaper != null ? currentPaper.calculateScore() : 0; + } + + public int getCurrentQuestionNumber() { + return currentPaper != null ? currentPaper.getCurrentQuestionNumber() : 0; + } + + public int getTotalQuestions() { + return currentPaper != null ? currentPaper.getTotalQuestions() : 0; + } + + public String getCurrentLevel() { + return currentLevel; + } + + public void setCurrentUser(String user) { + this.currentUser = user; + } + + public Paper getCurrentPaper() { + return currentPaper; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/controller/NavigationController.java b/src/com/mathlearning/controller/NavigationController.java new file mode 100644 index 0000000..cf4143d --- /dev/null +++ b/src/com/mathlearning/controller/NavigationController.java @@ -0,0 +1,86 @@ +package com.mathlearning.controller; + +import com.mathlearning.view.*; + +import javax.swing.*; +import java.awt.*; + +public class NavigationController { + private JFrame mainFrame; + private CardLayout cardLayout; + private JPanel mainPanel; + private String currentUser; + private String currentUserEmail; + + public NavigationController() { + initializeMainFrame(); + } + + private void initializeMainFrame() { + mainFrame = new JFrame("数学学习软件"); + mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + mainFrame.setSize(600, 500); + mainFrame.setLocationRelativeTo(null); + + cardLayout = new CardLayout(); + mainPanel = new JPanel(cardLayout); + + // Initialize with login view + LoginView loginView = new LoginView(this); + mainPanel.add(loginView, "LOGIN"); + + mainFrame.add(mainPanel); + } + + public void showLoginView() { + cardLayout.show(mainPanel, "LOGIN"); + mainFrame.setVisible(true); + } + + public void showRegisterView() { + RegisterView registerView = new RegisterView(this); + mainPanel.add(registerView, "REGISTER"); + cardLayout.show(mainPanel, "REGISTER"); + } + + public void showSetPasswordView(String email, String verificationCode) { + SetPasswordView setPasswordView = new SetPasswordView(this, email, verificationCode); + mainPanel.add(setPasswordView, "SET_PASSWORD"); + cardLayout.show(mainPanel, "SET_PASSWORD"); + } + + public void showLevelSelectionView(String userIdentifier, String email) { + this.currentUser = userIdentifier; + this.currentUserEmail = email; + LevelSelectionView levelSelectionView = new LevelSelectionView(this, userIdentifier, email); + mainPanel.add(levelSelectionView, "LEVEL_SELECTION"); + cardLayout.show(mainPanel, "LEVEL_SELECTION"); + } + + public void showQuestionCountView(String level) { + QuestionCountView questionCountView = new QuestionCountView(this, currentUser, currentUserEmail, level); + mainPanel.add(questionCountView, "QUESTION_COUNT"); + cardLayout.show(mainPanel, "QUESTION_COUNT"); + } + + public void showExamView(String level, int questionCount) { + ExamView examView = new ExamView(this, currentUser, currentUserEmail, level, questionCount); + mainPanel.add(examView, "EXAM"); + cardLayout.show(mainPanel, "EXAM"); + } + + public void showScoreView(String level, int score, int totalQuestions) { + ScoreView scoreView = new ScoreView(this, currentUser, currentUserEmail, level, score, totalQuestions); + mainPanel.add(scoreView, "SCORE"); + cardLayout.show(mainPanel, "SCORE"); + } + + public void showChangePasswordView(String identifier) { + // 实现修改密码视图的显示 + JOptionPane.showMessageDialog(mainFrame, "修改密码功能待实现", "提示", JOptionPane.INFORMATION_MESSAGE); + } + + public JFrame getMainFrame() { + return mainFrame; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/Paper.java b/src/com/mathlearning/model/Paper.java new file mode 100644 index 0000000..bdb0795 --- /dev/null +++ b/src/com/mathlearning/model/Paper.java @@ -0,0 +1,73 @@ +package com.mathlearning.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class Paper implements Serializable { + private static final long serialVersionUID = 1L; + + private List questions; + private int currentQuestionIndex; + private String level; + + public Paper(String level) { + this.questions = new ArrayList<>(); + this.currentQuestionIndex = 0; + this.level = level; + } + + public void addQuestion(Question question) { + questions.add(question); + } + + public Question getCurrentQuestion() { + if (currentQuestionIndex < questions.size()) { + return questions.get(currentQuestionIndex); + } + return null; + } + + public boolean hasNextQuestion() { + return currentQuestionIndex < questions.size() - 1; + } + + public void nextQuestion() { + if (hasNextQuestion()) { + currentQuestionIndex++; + } + } + + public boolean hasPreviousQuestion() { + return currentQuestionIndex > 0; + } + + public void previousQuestion() { + if (hasPreviousQuestion()) { + currentQuestionIndex--; + } + } + + public int getTotalQuestions() { + return questions.size(); + } + + public int getCurrentQuestionNumber() { + return currentQuestionIndex + 1; + } + + public int calculateScore() { + int correctCount = 0; + for (Question q : questions) { + if (q.isCorrect()) { + correctCount++; + } + } + return (int) ((correctCount * 100.0) / questions.size()); + } + + public String getLevel() { return level; } + public void setLevel(String level) { this.level = level; } + + public List getQuestions() { return questions; } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/Question.java b/src/com/mathlearning/model/Question.java new file mode 100644 index 0000000..7d9d12b --- /dev/null +++ b/src/com/mathlearning/model/Question.java @@ -0,0 +1,39 @@ +package com.mathlearning.model; + +import java.io.Serializable; + +public class Question implements Serializable { + private static final long serialVersionUID = 1L; + + private String content; + private String[] options; + private String correctAnswer; + private String userAnswer; + + public Question(String content, String options, String correctAnswer) { + this.content = content; + this.options = options.split(","); + this.correctAnswer = correctAnswer; + } + + // Getters and setters + public String getContent() { return content; } + public void setContent(String content) { this.content = content; } + + public String[] getOptions() { return options; } + public void setOptions(String[] options) { this.options = options; } + + public String getCorrectAnswer() { return correctAnswer; } + public void setCorrectAnswer(String correctAnswer) { this.correctAnswer = correctAnswer; } + + public String getUserAnswer() { return userAnswer; } + public void setUserAnswer(String userAnswer) { this.userAnswer = userAnswer; } + + public boolean isCorrect() { + return correctAnswer != null && correctAnswer.equals(userAnswer); + } + + public String getOptionsAsString() { + return String.join(",", options); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/QuestionGenerator.java b/src/com/mathlearning/model/QuestionGenerator.java new file mode 100644 index 0000000..ac6668b --- /dev/null +++ b/src/com/mathlearning/model/QuestionGenerator.java @@ -0,0 +1,296 @@ +package com.mathlearning.model; + +import java.util.*; + +public abstract class QuestionGenerator { + protected Random random = new Random(); + + public abstract List generateQuestions(int count); + + protected String generateOptions(String correctAnswer) { + Set options = new HashSet<>(); + options.add(correctAnswer); + + // 生成错误选项 + while (options.size() < 4) { + try { + double correctValue = Double.parseDouble(correctAnswer); + double variation = correctValue * 0.2 + 1; // 20% 变化,至少1 + double wrongValue = correctValue + (random.nextDouble() * 2 - 1) * variation; + + // 避免负数和零 + if (wrongValue <= 0) { + wrongValue = Math.abs(wrongValue) + 1; + } + + // 格式化选项 + String formattedValue; + if (correctAnswer.contains(".")) { + formattedValue = String.format("%.2f", wrongValue); + } else { + formattedValue = String.valueOf((int) wrongValue); + } + + options.add(formattedValue); + } catch (NumberFormatException e) { + // 如果解析失败,使用整数方法 + int wrongValue = random.nextInt(20) + 1; + options.add(String.valueOf(wrongValue)); + } + } + + List optionList = new ArrayList<>(options); + Collections.shuffle(optionList); + return String.join(",", optionList); + } + + // 工厂方法 + public static QuestionGenerator createGenerator(String level) { + switch (level) { + case "小学": + return new OptimizedPrimaryQuestionGenerator(); + case "初中": + return new OptimizedMiddleSchoolQuestionGenerator(); + case "高中": + return new OptimizedHighSchoolQuestionGenerator(); + default: + return new OptimizedPrimaryQuestionGenerator(); + } + } + + // 生成随机操作数 (1-50) 减小范围提高效率 + protected int generateOperand() { + return random.nextInt(50) + 1; + } + + // 生成小范围操作数用于乘法 + protected int generateSmallOperand() { + return random.nextInt(12) + 1; + } +} + +// 优化的小学题目生成器 +class OptimizedPrimaryQuestionGenerator extends QuestionGenerator { + @Override + public List generateQuestions(int count) { + System.out.println("开始生成小学题目,数量: " + count); + List questions = new ArrayList<>(); + Set usedExpressions = new HashSet<>(); + + String[][] templates = { + {"{0} + {1} = ?", "+"}, + {"{0} - {1} = ?", "-"}, + {"{0} × {1} = ?", "×"} + }; + + int maxAttempts = count * 5; // 减少尝试次数 + int attempts = 0; + + while (questions.size() < count && attempts < maxAttempts) { + attempts++; + + String[] template = templates[random.nextInt(templates.length)]; + String expression; + int answer; + + switch (template[1]) { + case "+": + int a1 = generateOperand(); + int b1 = generateOperand(); + expression = a1 + " + " + b1; + answer = a1 + b1; + break; + case "-": + int a2 = generateOperand(); + int b2 = generateOperand(); + // 确保结果为正数 + if (a2 < b2) { + int temp = a2; + a2 = b2; + b2 = temp; + } + expression = a2 + " - " + b2; + answer = a2 - b2; + break; + case "×": + int a3 = generateSmallOperand(); + int b3 = generateSmallOperand(); + expression = a3 + " × " + b3; + answer = a3 * b3; + break; + default: + continue; + } + + // 检查表达式是否已使用 + if (usedExpressions.contains(expression)) { + continue; + } + + usedExpressions.add(expression); + String options = generateOptions(String.valueOf(answer)); + questions.add(new Question(expression + " = ?", options, String.valueOf(answer))); + + if (questions.size() % 10 == 0) { + System.out.println("已生成 " + questions.size() + " 道小学题目"); + } + } + + System.out.println("小学题目生成完成,实际生成: " + questions.size() + " 道题目"); + + // 如果因为重复问题无法生成足够的题目,补充简单题目 + if (questions.size() < count) { + System.out.println("补充简单小学题目..."); + for (int i = questions.size(); i < count; i++) { + int a = random.nextInt(10) + 1; + int b = random.nextInt(10) + 1; + String expression = a + " + " + b; + if (!usedExpressions.contains(expression)) { + int answer = a + b; + String options = generateOptions(String.valueOf(answer)); + questions.add(new Question(expression + " = ?", options, String.valueOf(answer))); + usedExpressions.add(expression); + } + } + } + + return questions; + } +} + +// 优化的初中题目生成器 +class OptimizedMiddleSchoolQuestionGenerator extends QuestionGenerator { + @Override + public List generateQuestions(int count) { + System.out.println("开始生成初中题目,数量: " + count); + List questions = new ArrayList<>(); + Set usedExpressions = new HashSet<>(); + + int maxAttempts = count * 5; + int attempts = 0; + + while (questions.size() < count && attempts < maxAttempts) { + attempts++; + + int type = random.nextInt(4); // 0-3: 平方、开方、混合、简单运算 + + String expression; + int answer; + + switch (type) { + case 0: // 平方 + int num = random.nextInt(15) + 1; // 1-15的平方 + expression = num + "²"; + answer = num * num; + break; + case 1: // 开根号 + int root = random.nextInt(12) + 1; // 1-12 + expression = "√" + (root * root); + answer = root; + break; + case 2: // 混合运算 + int x = random.nextInt(8) + 2; // 2-9 + int y = random.nextInt(8) + 2; // 2-9 + expression = "(" + x + " + " + y + ")²"; + answer = (x + y) * (x + y); + break; + default: // 简单运算 + int a = generateOperand(); + int b = generateOperand(); + if (random.nextBoolean()) { + expression = a + " + " + b; + answer = a + b; + } else { + if (a < b) { + int temp = a; + a = b; + b = temp; + } + expression = a + " - " + b; + answer = a - b; + } + break; + } + + if (usedExpressions.contains(expression)) { + continue; + } + + usedExpressions.add(expression); + String options = generateOptions(String.valueOf(answer)); + questions.add(new Question(expression + " = ?", options, String.valueOf(answer))); + + if (questions.size() % 10 == 0) { + System.out.println("已生成 " + questions.size() + " 道初中题目"); + } + } + + System.out.println("初中题目生成完成,实际生成: " + questions.size() + " 道题目"); + return questions; + } +} + +// 优化的高中题目生成器 +class OptimizedHighSchoolQuestionGenerator extends QuestionGenerator { + @Override + public List generateQuestions(int count) { + System.out.println("开始生成高中题目,数量: " + count); + List questions = new ArrayList<>(); + Set usedExpressions = new HashSet<>(); + + // 使用常见角度,避免复杂计算 + int[] angles = {0, 30, 45, 60, 90, 120, 135, 150, 180}; + String[] functions = {"sin", "cos", "tan"}; + + int maxAttempts = count * 5; + int attempts = 0; + + while (questions.size() < count && attempts < maxAttempts) { + attempts++; + + String function = functions[random.nextInt(functions.length)]; + int angle = angles[random.nextInt(angles.length)]; + + String expression = function + "(" + angle + "°)"; + double result; + + switch (function) { + case "sin": + result = Math.sin(Math.toRadians(angle)); + break; + case "cos": + result = Math.cos(Math.toRadians(angle)); + break; + case "tan": + // 避免tan(90°)等未定义的情况 + if (angle % 90 == 0 && angle % 180 != 0) { + continue; + } + result = Math.tan(Math.toRadians(angle)); + // 限制tan值范围 + if (Math.abs(result) > 10) { + continue; + } + break; + default: + result = 0; + } + + String formattedAnswer = String.format("%.2f", result); + if (usedExpressions.contains(expression)) { + continue; + } + + usedExpressions.add(expression); + String options = generateOptions(formattedAnswer); + questions.add(new Question(expression + " = ?", options, formattedAnswer)); + + if (questions.size() % 10 == 0) { + System.out.println("已生成 " + questions.size() + " 道高中题目"); + } + } + + System.out.println("高中题目生成完成,实际生成: " + questions.size() + " 道题目"); + return questions; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/User.java b/src/com/mathlearning/model/User.java new file mode 100644 index 0000000..85ee047 --- /dev/null +++ b/src/com/mathlearning/model/User.java @@ -0,0 +1,42 @@ +package com.mathlearning.model; + +import java.io.Serializable; + +public class User implements Serializable { + private static final long serialVersionUID = 1L; + + private String email; + private String username; // 新增用户名字段 + private String password; + private String verificationCode; + private boolean isRegistered; + + public User(String email, String username) { + this.email = email; + this.username = username; + this.isRegistered = false; + } + + // Getters and setters + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + + public String getVerificationCode() { return verificationCode; } + public void setVerificationCode(String verificationCode) { + this.verificationCode = verificationCode; + } + + public boolean isRegistered() { return isRegistered; } + public void setRegistered(boolean registered) { isRegistered = registered; } + + @Override + public String toString() { + return "User{username='" + username + "', email='" + email + "', registered=" + isRegistered + "}"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/UserManager.java b/src/com/mathlearning/model/UserManager.java new file mode 100644 index 0000000..c1fbc6d --- /dev/null +++ b/src/com/mathlearning/model/UserManager.java @@ -0,0 +1,118 @@ +package com.mathlearning.model; + +import com.mathlearning.util.FileStorage; + +import java.util.Map; +import java.util.HashMap; + +public class UserManager { + private static UserManager instance; + private Map usersByEmail; // 按邮箱存储 + private Map usersByUsername; // 按用户名存储 + + private UserManager() { + this.usersByEmail = FileStorage.loadUsersByEmail(); + this.usersByUsername = FileStorage.loadUsersByUsername(); + } + + public static synchronized UserManager getInstance() { + if (instance == null) { + instance = new UserManager(); + } + return instance; + } + + public void addUser(User user) { + usersByEmail.put(user.getEmail(), user); + usersByUsername.put(user.getUsername(), user); + FileStorage.saveUsers(usersByEmail, usersByUsername); + } + + public User getUserByEmail(String email) { + return usersByEmail.get(email); + } + + public User getUserByUsername(String username) { + return usersByUsername.get(username); + } + + // 通过邮箱或用户名查找用户 + public User getUser(String identifier) { + User user = usersByEmail.get(identifier); + if (user == null) { + user = usersByUsername.get(identifier); + } + return user; + } + + public boolean userExistsByEmail(String email) { + return usersByEmail.containsKey(email); + } + + public boolean userExistsByUsername(String username) { + return usersByUsername.containsKey(username); + } + + // 检查邮箱或用户名是否已存在 + public boolean userExists(String identifier) { + return usersByEmail.containsKey(identifier) || usersByUsername.containsKey(identifier); + } + + public boolean verifyUser(String identifier, String password) { + User user = getUser(identifier); + return user != null && user.isRegistered() && + password.equals(user.getPassword()); + } + + public void saveUsers() { + FileStorage.saveUsers(usersByEmail, usersByUsername); + } + + /** + * 检查邮箱是否可用(未被注册) + */ + public boolean isEmailAvailable(String email) { + User user = usersByEmail.get(email); + return user == null || !user.isRegistered(); + } + + /** + * 检查用户名是否可用(未被注册) + */ + public boolean isUsernameAvailable(String username) { + User user = usersByUsername.get(username); + return user == null || !user.isRegistered(); + } + + /** + * 删除未注册的用户(清理过期数据) + */ + public void removeUnregisteredUser(String email) { + User user = usersByEmail.get(email); + if (user != null && !user.isRegistered()) { + usersByEmail.remove(email); + usersByUsername.remove(user.getUsername()); + saveUsers(); + } + } + + /** + * 获取所有用户数量(用于调试) + */ + public int getUserCount() { + return usersByEmail.size(); + } + + /** + * 获取已注册用户数量(用于调试) + */ + public int getRegisteredUserCount() { + int count = 0; + for (User user : usersByEmail.values()) { + if (user.isRegistered()) { + count++; + } + } + return count; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/EmailConfig.java b/src/com/mathlearning/util/EmailConfig.java new file mode 100644 index 0000000..9177b06 --- /dev/null +++ b/src/com/mathlearning/util/EmailConfig.java @@ -0,0 +1,62 @@ +package com.mathlearning.util; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +public class EmailConfig { + private static final String CONFIG_FILE = "email_config.properties"; + private static Properties properties; + + static { + properties = new Properties(); + try { + // 尝试从配置文件加载 + properties.load(new FileInputStream(CONFIG_FILE)); + } catch (IOException e) { + // 如果配置文件不存在,使用默认值 + System.out.println("未找到配置文件,使用默认邮箱配置(需要修改为您的真实邮箱)"); + setDefaultProperties(); + } + } + + private static void setDefaultProperties() { + // 这里设置您的邮箱配置 - 请修改为您的真实邮箱信息 + properties.setProperty("email.from", "your-email@qq.com"); // 发件邮箱 + properties.setProperty("email.password", "your-authorization-code"); // 授权码 + properties.setProperty("email.smtp.host", "smtp.qq.com"); // SMTP服务器 + properties.setProperty("email.smtp.port", "465"); // 端口 + properties.setProperty("email.smtp.ssl", "true"); // 启用SSL + properties.setProperty("email.smtp.auth", "true"); // 需要认证 + } + + public static String getFrom() { + return properties.getProperty("email.from"); + } + + public static String getPassword() { + return properties.getProperty("email.password"); + } + + public static String getSmtpHost() { + return properties.getProperty("email.smtp.host"); + } + + public static String getSmtpPort() { + return properties.getProperty("email.smtp.port"); + } + + public static boolean isSslEnabled() { + return "true".equals(properties.getProperty("email.smtp.ssl")); + } + + public static boolean isAuthEnabled() { + return "true".equals(properties.getProperty("email.smtp.auth")); + } + + // 验证配置是否已设置(不是默认值) + public static boolean isConfigured() { + return !"your-email@qq.com".equals(getFrom()) && + !"your-authorization-code".equals(getPassword()); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/EmailUtil.java b/src/com/mathlearning/util/EmailUtil.java new file mode 100644 index 0000000..180f2fd --- /dev/null +++ b/src/com/mathlearning/util/EmailUtil.java @@ -0,0 +1,133 @@ +package com.mathlearning.util; + +import javax.mail.*; +import javax.mail.internet.*; +import javax.swing.JOptionPane; +import java.util.Properties; +import java.util.Random; + +public class EmailUtil { + private static final Random random = new Random(); + + /** + * 发送验证码 - 真实邮箱版本 + */ + public static String sendVerificationCode(String toEmail) { + // 检查邮箱配置 + if (!EmailConfig.isConfigured()) { + JOptionPane.showMessageDialog(null, + "邮箱配置未完成!\n请修改 email_config.properties 文件,配置您的邮箱信息。", + "配置错误", + JOptionPane.ERROR_MESSAGE); + return null; + } + + // 生成6位验证码 + String verificationCode = generateVerificationCode(); + + try { + // 发送邮件 + boolean sendSuccess = sendEmail(toEmail, verificationCode); + + if (sendSuccess) { + JOptionPane.showMessageDialog( + null, + "验证码已发送到您的邮箱!\n\n" + + "收件邮箱: " + toEmail + "\n" + + "请查看您的邮箱并输入验证码。", + "验证码发送成功", + JOptionPane.INFORMATION_MESSAGE + ); + return verificationCode; + } else { + JOptionPane.showMessageDialog(null, + "验证码发送失败,请检查邮箱地址是否正确或稍后重试", + "发送失败", + JOptionPane.ERROR_MESSAGE); + return null; + } + } catch (Exception e) { + JOptionPane.showMessageDialog(null, + "发送验证码时发生错误: " + e.getMessage(), + "错误", + JOptionPane.ERROR_MESSAGE); + return null; + } + } + + /** + * 发送邮件 + */ + private static boolean sendEmail(String toEmail, String verificationCode) { + Properties props = new Properties(); + props.put("mail.smtp.host", EmailConfig.getSmtpHost()); + props.put("mail.smtp.port", EmailConfig.getSmtpPort()); + props.put("mail.smtp.auth", "true"); + + if (EmailConfig.isSslEnabled()) { + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.put("mail.smtp.socketFactory.port", EmailConfig.getSmtpPort()); + } + + // 创建会话 + Session session = Session.getInstance(props, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(EmailConfig.getFrom(), EmailConfig.getPassword()); + } + }); + + try { + // 创建邮件 + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(EmailConfig.getFrom())); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); + message.setSubject("数学学习软件 - 邮箱验证码"); + + String emailContent = "尊敬的用户:\n\n" + + "您正在注册数学学习软件,验证码为:" + verificationCode + "\n\n" + + "验证码有效期为10分钟,请尽快完成注册。\n\n" + + "如果不是您本人操作,请忽略此邮件。\n\n" + + "数学学习软件团队"; + + message.setText(emailContent); + + // 发送邮件 + Transport.send(message); + System.out.println("验证码邮件已发送到: " + toEmail); + return true; + + } catch (MessagingException e) { + System.err.println("发送邮件失败: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * 生成6位数字验证码 + */ + private static String generateVerificationCode() { + return String.format("%06d", random.nextInt(1000000)); + } + + /** + * 验证邮箱格式 + */ + public static boolean isValidEmail(String email) { + if (email == null || email.trim().isEmpty()) { + return false; + } + + // 简单的邮箱格式验证 + String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"; + return email.matches(emailRegex); + } + + /** + * 获取邮箱验证要求说明 + */ + public static String getEmailRequirements() { + return "请输入有效的邮箱地址,例如:username@example.com"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/ExamFileUtil.java b/src/com/mathlearning/util/ExamFileUtil.java new file mode 100644 index 0000000..df9dc3d --- /dev/null +++ b/src/com/mathlearning/util/ExamFileUtil.java @@ -0,0 +1,86 @@ +package com.mathlearning.util; + +import com.mathlearning.model.Paper; +import com.mathlearning.model.Question; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class ExamFileUtil { + + /** + * 保存试卷到文件 + * @param username 用户名 + * @param paper 试卷对象 + * @return 是否保存成功 + */ + public static boolean savePaperToFile(String username, Paper paper) { + // 创建用户文件夹 + File userDir = new File("exam_papers/" + username); + if (!userDir.exists()) { + if (!userDir.mkdirs()) { + System.err.println("无法创建用户文件夹: " + userDir.getAbsolutePath()); + return false; + } + } + + // 生成文件名 + String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + String filename = timestamp + ".txt"; + File file = new File(userDir, filename); + + try (PrintWriter writer = new PrintWriter(new FileWriter(file))) { + // 写入试卷信息 + writer.println("试卷级别: " + paper.getLevel()); + writer.println("生成时间: " + new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(new Date())); + writer.println("题目数量: " + paper.getTotalQuestions()); + writer.println(); + + // 写入题目 + int questionNumber = 1; + for (Question question : paper.getQuestions()) { + writer.println(questionNumber + ". " + question.getContent()); + + // 写入选项 + String[] options = question.getOptions(); + for (int i = 0; i < options.length; i++) { + writer.println(" " + (char)('A' + i) + ". " + options[i]); + } + + writer.println(); // 题目之间空一行 + questionNumber++; + } + + // 写入答案 + writer.println("=== 参考答案 ==="); + questionNumber = 1; + for (Question question : paper.getQuestions()) { + writer.println(questionNumber + ". " + question.getCorrectAnswer()); + questionNumber++; + } + + System.out.println("试卷已保存到: " + file.getAbsolutePath()); + return true; + + } catch (IOException e) { + System.err.println("保存试卷时出错: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * 获取用户的所有试卷文件 + * @param username 用户名 + * @return 试卷文件列表 + */ + public static File[] getUserExamFiles(String username) { + File userDir = new File("exam_papers/" + username); + if (!userDir.exists()) { + return new File[0]; + } + + return userDir.listFiles((dir, name) -> name.endsWith(".txt")); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/FileStorage.java b/src/com/mathlearning/util/FileStorage.java new file mode 100644 index 0000000..18f1613 --- /dev/null +++ b/src/com/mathlearning/util/FileStorage.java @@ -0,0 +1,52 @@ +package com.mathlearning.util; + +import com.mathlearning.model.User; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +public class FileStorage { + private static final String USER_DATA_EMAIL_FILE = "users_email.dat"; + private static final String USER_DATA_USERNAME_FILE = "users_username.dat"; + + @SuppressWarnings("unchecked") + public static Map loadUsersByEmail() { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_EMAIL_FILE))) { + return (Map) ois.readObject(); + } catch (FileNotFoundException e) { + // 文件不存在,返回空Map + return new HashMap<>(); + } catch (IOException | ClassNotFoundException e) { + System.err.println("加载用户数据失败: " + e.getMessage()); + return new HashMap<>(); + } + } + + @SuppressWarnings("unchecked") + public static Map loadUsersByUsername() { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_USERNAME_FILE))) { + return (Map) ois.readObject(); + } catch (FileNotFoundException e) { + // 文件不存在,返回空Map + return new HashMap<>(); + } catch (IOException | ClassNotFoundException e) { + System.err.println("加载用户数据失败: " + e.getMessage()); + return new HashMap<>(); + } + } + + public static void saveUsers(Map usersByEmail, Map usersByUsername) { + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(USER_DATA_EMAIL_FILE))) { + oos.writeObject(usersByEmail); + } catch (IOException e) { + System.err.println("保存用户数据失败: " + e.getMessage()); + } + + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(USER_DATA_USERNAME_FILE))) { + oos.writeObject(usersByUsername); + } catch (IOException e) { + System.err.println("保存用户数据失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/PasswordValidator.java b/src/com/mathlearning/util/PasswordValidator.java new file mode 100644 index 0000000..ce0526f --- /dev/null +++ b/src/com/mathlearning/util/PasswordValidator.java @@ -0,0 +1,21 @@ +package com.mathlearning.util; + +public class PasswordValidator { + public static boolean isValid(String password) { + if (password == null || password.length() < 6 || password.length() > 10) { + return false; + } + + boolean hasUpperCase = false; + boolean hasLowerCase = false; + boolean hasDigit = false; + + for (char c : password.toCharArray()) { + if (Character.isUpperCase(c)) hasUpperCase = true; + if (Character.isLowerCase(c)) hasLowerCase = true; + if (Character.isDigit(c)) hasDigit = true; + } + + return hasUpperCase && hasLowerCase && hasDigit; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/util/UsernameValidator.java b/src/com/mathlearning/util/UsernameValidator.java new file mode 100644 index 0000000..e47214b --- /dev/null +++ b/src/com/mathlearning/util/UsernameValidator.java @@ -0,0 +1,16 @@ +package com.mathlearning.util; + +public class UsernameValidator { + public static boolean isValid(String username) { + if (username == null || username.length() < 3 || username.length() > 20) { + return false; + } + + // 用户名只能包含字母、数字和下划线 + return username.matches("^[a-zA-Z0-9_]+$"); + } + + public static String getRequirements() { + return "用户名要求: 3-20位,只能包含字母、数字和下划线"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/ExamView.java b/src/com/mathlearning/view/ExamView.java new file mode 100644 index 0000000..2f3c683 --- /dev/null +++ b/src/com/mathlearning/view/ExamView.java @@ -0,0 +1,397 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.ExamController; +import com.mathlearning.controller.NavigationController; +import com.mathlearning.model.Question; + +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.concurrent.ExecutionException; + +public class ExamView extends JPanel { + private NavigationController navigationController; + private ExamController examController; + private String userIdentifier; + private String email; + private String level; + private int questionCount; + + // UI组件 + private JLabel questionNumberLabel; + private JLabel questionContentLabel; + private ButtonGroup optionsGroup; + private JRadioButton[] optionButtons; + private JButton previousButton; + private JButton nextButton; + private JButton submitButton; + private JProgressBar progressBar; + private JLabel statusLabel; + private JButton cancelButton; + + // 进度跟踪 + private int generationProgress = 0; + + public ExamView(NavigationController navigationController, String userIdentifier, String email, String level, int questionCount) { + this.navigationController = navigationController; + this.userIdentifier = userIdentifier; + this.email = email; + this.level = level; + this.questionCount = questionCount; + this.examController = new ExamController(); + this.examController.setCurrentUser(userIdentifier); + + initializeUI(); + startExamGeneration(); + } + + private void initializeUI() { + setLayout(new BorderLayout(10, 10)); + + // 顶部信息面板 + JPanel infoPanel = new JPanel(new BorderLayout()); + String userInfo = "用户: " + userIdentifier; + if (!userIdentifier.equals(email)) { + userInfo += " (" + email + ")"; + } + JLabel userInfoLabel = new JLabel(userInfo, JLabel.LEFT); + userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + infoPanel.add(userInfoLabel, BorderLayout.WEST); + + JLabel levelLabel = new JLabel(level + "数学", JLabel.RIGHT); + levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + infoPanel.add(levelLabel, BorderLayout.EAST); + + add(infoPanel, BorderLayout.NORTH); + + // 进度显示面板 + JPanel progressPanel = new JPanel(new BorderLayout()); + progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + statusLabel = new JLabel("正在生成试卷,请稍候...", JLabel.CENTER); + statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + progressPanel.add(statusLabel, BorderLayout.NORTH); + + progressBar = new JProgressBar(); + progressBar.setIndeterminate(true); + progressBar.setPreferredSize(new Dimension(300, 20)); + progressPanel.add(progressBar, BorderLayout.CENTER); + + // 取消按钮 + JPanel buttonPanel = new JPanel(); + cancelButton = new JButton("取消生成"); + cancelButton.addActionListener(e -> { + // 返回题目数量选择界面 + navigationController.showQuestionCountView(level); + }); + buttonPanel.add(cancelButton); + progressPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(progressPanel, BorderLayout.CENTER); + } + + private void startExamGeneration() { + System.out.println("开始生成试卷,级别: " + level + ", 题目数量: " + questionCount); + + // 使用SwingWorker在后台生成题目 + SwingWorker worker = new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + publish("正在初始化生成器..."); + Thread.sleep(100); // 短暂延迟,让UI更新 + + try { + publish("开始生成题目..."); + long startTime = System.currentTimeMillis(); + + // 设置进度条为确定模式 + SwingUtilities.invokeLater(() -> { + progressBar.setIndeterminate(false); + progressBar.setMinimum(0); + progressBar.setMaximum(100); + progressBar.setValue(0); + }); + + // 模拟进度更新 + for (int i = 0; i <= 100; i += 10) { + if (isCancelled()) { + return false; + } + publish("生成题目中... " + i + "%"); + progressBar.setValue(i); + Thread.sleep(50); + } + + examController.createExam(level, questionCount); + + long endTime = System.currentTimeMillis(); + publish("题目生成完成,耗时: " + (endTime - startTime) + "ms"); + + return true; + } catch (Exception e) { + System.err.println("生成题目时出错: " + e.getMessage()); + e.printStackTrace(); + publish("生成题目时出错: " + e.getMessage()); + return false; + } + } + + @Override + protected void process(List chunks) { + // 更新状态信息 + if (!chunks.isEmpty()) { + String latestMessage = chunks.get(chunks.size() - 1); + statusLabel.setText(latestMessage); + + // 更新进度条文本显示 + if (latestMessage.contains("%")) { + progressBar.setString(latestMessage); + progressBar.setStringPainted(true); + } + } + } + + @Override + protected void done() { + try { + Boolean success = get(); + if (success) { + // 题目生成成功,切换到考试界面 + statusLabel.setText("题目生成成功!正在加载界面..."); + progressBar.setValue(100); + + // 短暂延迟后初始化考试界面 + Timer timer = new Timer(500, e -> { + initializeExamUI(); + loadQuestion(); + }); + timer.setRepeats(false); + timer.start(); + } else { + // 生成失败,显示错误信息 + JOptionPane.showMessageDialog(ExamView.this, + "生成试卷失败,将使用基础题目继续考试", + "生成失败", + JOptionPane.WARNING_MESSAGE); + + // 即使生成失败也尝试继续 + initializeExamUI(); + loadQuestion(); + } + } catch (InterruptedException e) { + // 用户取消了操作 + navigationController.showQuestionCountView(level); + } catch (ExecutionException e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(ExamView.this, + "生成试卷时发生错误: " + e.getCause().getMessage(), + "错误", + JOptionPane.ERROR_MESSAGE); + navigationController.showQuestionCountView(level); + } + } + }; + + worker.execute(); + } + + private void initializeExamUI() { + // 移除进度显示 + removeAll(); + setLayout(new BorderLayout(10, 10)); + + // Header Panel + JPanel headerPanel = new JPanel(new BorderLayout()); + questionNumberLabel = new JLabel("", JLabel.CENTER); + questionNumberLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + headerPanel.add(questionNumberLabel, BorderLayout.CENTER); + + JLabel levelLabel = new JLabel(level + "数学", JLabel.RIGHT); + levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + headerPanel.add(levelLabel, BorderLayout.EAST); + + add(headerPanel, BorderLayout.NORTH); + + // Question Panel + JPanel questionPanel = new JPanel(new BorderLayout(10, 10)); + questionPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + questionContentLabel = new JLabel("", JLabel.CENTER); + questionContentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + questionPanel.add(questionContentLabel, BorderLayout.NORTH); + + // Options Panel - 修复选项状态管理 + JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 10, 10)); + optionsGroup = new ButtonGroup(); + optionButtons = new JRadioButton[4]; + + for (int i = 0; i < 4; i++) { + optionButtons[i] = new JRadioButton(); + optionButtons[i].setFont(new Font("微软雅黑", Font.PLAIN, 16)); + // 添加选项变化监听器,实时保存答案 + final int index = i; + optionButtons[i].addActionListener(e -> { + if (optionButtons[index].isSelected()) { + saveCurrentAnswer(); + } + }); + optionsGroup.add(optionButtons[i]); + optionsPanel.add(optionButtons[i]); + } + + questionPanel.add(optionsPanel, BorderLayout.CENTER); + add(questionPanel, BorderLayout.CENTER); + + // Navigation Panel + JPanel navPanel = new JPanel(new FlowLayout()); + + previousButton = new JButton("上一题"); + previousButton.addActionListener(e -> handlePrevious()); + navPanel.add(previousButton); + + nextButton = new JButton("下一题"); + nextButton.addActionListener(e -> handleNext()); + navPanel.add(nextButton); + + submitButton = new JButton("提交试卷"); + submitButton.addActionListener(e -> handleSubmit()); + navPanel.add(submitButton); + + // 添加返回按钮 + JButton backButton = new JButton("返回"); + backButton.addActionListener(e -> { + int result = JOptionPane.showConfirmDialog(ExamView.this, + "确定要返回吗?当前进度将丢失。", + "确认返回", + JOptionPane.YES_NO_OPTION); + + if (result == JOptionPane.YES_OPTION) { + navigationController.showQuestionCountView(level); + } + }); + navPanel.add(backButton); + + add(navPanel, BorderLayout.SOUTH); + + updateNavigationButtons(); + revalidate(); + repaint(); + } + + private void loadQuestion() { + Question currentQuestion = examController.getCurrentQuestion(); + if (currentQuestion != null) { + // 更新题号 + questionNumberLabel.setText("第 " + examController.getCurrentQuestionNumber() + " 题 / 共 " + + examController.getTotalQuestions() + " 题"); + + // 更新题目内容 + questionContentLabel.setText("
" + currentQuestion.getContent() + "
"); + + // 更新选项 - 关键修复:先清空所有选择 + optionsGroup.clearSelection(); + + String[] options = currentQuestion.getOptions(); + for (int i = 0; i < 4; i++) { + if (i < options.length) { + optionButtons[i].setText((char)('A' + i) + ". " + options[i]); + optionButtons[i].setVisible(true); + } else { + optionButtons[i].setText(""); // 清空多余的选项 + optionButtons[i].setVisible(false); + } + } + + // 如果有用户答案,选中对应的选项 + String userAnswer = currentQuestion.getUserAnswer(); + if (userAnswer != null && !userAnswer.trim().isEmpty()) { + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isVisible() && optionButtons[i].getText().contains(userAnswer)) { + optionButtons[i].setSelected(true); + break; + } + } + } + } else { + // 如果没有题目,显示错误信息 + questionContentLabel.setText("
题目加载失败,请返回重试
"); + } + + updateNavigationButtons(); + } + + private void handlePrevious() { + // 保存当前答案 + saveCurrentAnswer(); + examController.previousQuestion(); + loadQuestion(); + } + + private void handleNext() { + // 保存当前答案 + saveCurrentAnswer(); + examController.nextQuestion(); + loadQuestion(); + } + + private void handleSubmit() { + // 保存当前答案 + saveCurrentAnswer(); + + int result = JOptionPane.showConfirmDialog(this, + "确认提交试卷?提交后将显示成绩并保存试卷。", + "确认提交", + JOptionPane.YES_NO_OPTION); + + if (result == JOptionPane.YES_OPTION) { + // 保存试卷到文件 + boolean saveSuccess = examController.saveCurrentPaper(); + + int score = examController.calculateScore(); + int totalQuestions = examController.getTotalQuestions(); + + // 显示保存结果 + if (saveSuccess) { + JOptionPane.showMessageDialog(this, + "试卷已保存到您的文件夹中!", + "保存成功", + JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog(this, + "试卷保存失败,但成绩计算完成。", + "保存失败", + JOptionPane.WARNING_MESSAGE); + } + + navigationController.showScoreView(level, score, totalQuestions); + } + } + + private void saveCurrentAnswer() { + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isSelected() && optionButtons[i].isVisible()) { + try { + // 提取答案内容(去掉"A. "等前缀) + String fullText = optionButtons[i].getText(); + String answer = fullText.substring(fullText.indexOf(". ") + 2); + examController.submitAnswer(answer); + System.out.println("保存答案: " + answer); + break; + } catch (Exception e) { + System.err.println("保存答案时出错: " + e.getMessage()); + } + } + } + } + + private void updateNavigationButtons() { + boolean hasPrevious = examController.hasPreviousQuestion(); + boolean hasNext = examController.hasNextQuestion(); + + previousButton.setEnabled(hasPrevious); + nextButton.setEnabled(hasNext); + submitButton.setEnabled(!hasNext); // 只有最后一题才能提交 + + System.out.println("导航按钮状态 - 上一题: " + hasPrevious + ", 下一题: " + hasNext + ", 提交: " + !hasNext); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/LevelSelectionView.java b/src/com/mathlearning/view/LevelSelectionView.java new file mode 100644 index 0000000..1bd9370 --- /dev/null +++ b/src/com/mathlearning/view/LevelSelectionView.java @@ -0,0 +1,75 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; +import java.awt.*; + +public class LevelSelectionView extends JPanel { + private NavigationController navigationController; + private String userIdentifier; + private String email; + + private JButton primaryButton; + private JButton middleButton; + private JButton highButton; + private JButton logoutButton; + + public LevelSelectionView(NavigationController navigationController, String userIdentifier, String email) { + this.navigationController = navigationController; + this.userIdentifier = userIdentifier; + this.email = email; + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(15, 15, 15, 15); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("选择学习阶段", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // User info + gbc.gridy = 1; + String userInfo = "当前用户: " + userIdentifier; + if (!userIdentifier.equals(email)) { + userInfo += " (" + email + ")"; + } + JLabel userLabel = new JLabel(userInfo, JLabel.CENTER); + userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + add(userLabel, gbc); + + // Primary School Button + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 2; + primaryButton = new JButton("小学"); + primaryButton.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + primaryButton.addActionListener(e -> navigationController.showQuestionCountView("小学")); + add(primaryButton, gbc); + + // Middle School Button + gbc.gridx = 1; gbc.gridy = 2; + middleButton = new JButton("初中"); + middleButton.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + middleButton.addActionListener(e -> navigationController.showQuestionCountView("初中")); + add(middleButton, gbc); + + // High School Button + gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; + highButton = new JButton("高中"); + highButton.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + highButton.addActionListener(e -> navigationController.showQuestionCountView("高中")); + add(highButton, gbc); + + // Logout Button + gbc.gridy = 4; + logoutButton = new JButton("退出登录"); + logoutButton.addActionListener(e -> navigationController.showLoginView()); + add(logoutButton, gbc); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/LoginView.java b/src/com/mathlearning/view/LoginView.java new file mode 100644 index 0000000..d00b287 --- /dev/null +++ b/src/com/mathlearning/view/LoginView.java @@ -0,0 +1,110 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; +import java.awt.*; + +public class LoginView extends JPanel { + private NavigationController navigationController; + private AuthController authController; + + private JTextField identifierField; // 改为通用标识符字段 + private JPasswordField passwordField; + private JButton loginButton; + private JButton registerButton; + private JButton changePasswordButton; + private JLabel identifierLabel; + + public LoginView(NavigationController navigationController) { + this.navigationController = navigationController; + this.authController = new AuthController(); + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("数学学习软件", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // Identifier (用户名或邮箱) + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 1; + identifierLabel = new JLabel("用户名/邮箱:"); + add(identifierLabel, gbc); + + gbc.gridx = 1; gbc.gridy = 1; + identifierField = new JTextField(20); + add(identifierField, gbc); + + // Password + gbc.gridx = 0; gbc.gridy = 2; + add(new JLabel("密码:"), gbc); + + gbc.gridx = 1; gbc.gridy = 2; + passwordField = new JPasswordField(20); + add(passwordField, gbc); + + // Login Button + gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; + loginButton = new JButton("登录"); + loginButton.addActionListener(e -> handleLogin()); + add(loginButton, gbc); + + // Register Button + gbc.gridy = 4; + registerButton = new JButton("注册新账号"); + registerButton.addActionListener(e -> navigationController.showRegisterView()); + add(registerButton, gbc); + + // Change Password Button + gbc.gridy = 5; + changePasswordButton = new JButton("修改密码"); + changePasswordButton.addActionListener(e -> handleChangePassword()); + add(changePasswordButton, gbc); + + // 提示信息 + gbc.gridy = 6; + JLabel hintLabel = new JLabel("提示: 可以使用用户名或邮箱登录", JLabel.CENTER); + hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + hintLabel.setForeground(Color.GRAY); + add(hintLabel, gbc); + } + + private void handleLogin() { + String identifier = identifierField.getText().trim(); + String password = new String(passwordField.getPassword()); + + if (identifier.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入用户名/邮箱和密码", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (authController.login(identifier, password)) { + // 获取用户信息用于显示 + String email = authController.getUserEmail(identifier); + navigationController.showLevelSelectionView(identifier, email); + } else { + JOptionPane.showMessageDialog(this, "用户名/邮箱或密码错误", "登录失败", JOptionPane.ERROR_MESSAGE); + } + } + + private void handleChangePassword() { + String identifier = identifierField.getText().trim(); + if (identifier.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入用户名/邮箱", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + // 在实际应用中,这里应该验证用户身份 + navigationController.showChangePasswordView(identifier); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/QuestionCountView.java b/src/com/mathlearning/view/QuestionCountView.java new file mode 100644 index 0000000..7ca082c --- /dev/null +++ b/src/com/mathlearning/view/QuestionCountView.java @@ -0,0 +1,85 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; +import java.awt.*; + +public class QuestionCountView extends JPanel { + private NavigationController navigationController; + private String userIdentifier; + private String email; + private String level; + + private JTextField countField; + private JButton submitButton; + private JButton backButton; + + public QuestionCountView(NavigationController navigationController, String userIdentifier, String email, String level) { + this.navigationController = navigationController; + this.userIdentifier = userIdentifier; + this.email = email; + this.level = level; + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel(level + "数学题目生成", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // User info + gbc.gridy = 1; + String userInfo = "当前用户: " + userIdentifier; + if (!userIdentifier.equals(email)) { + userInfo += " (" + email + ")"; + } + JLabel userLabel = new JLabel(userInfo, JLabel.CENTER); + userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(userLabel, gbc); + + // Instruction + gbc.gridy = 2; + JLabel instructionLabel = new JLabel("请输入要生成的题目数量 (10-30):", JLabel.CENTER); + add(instructionLabel, gbc); + + // Count Input + gbc.gridy = 3; + countField = new JTextField(10); + countField.setHorizontalAlignment(JTextField.CENTER); + add(countField, gbc); + + // Submit Button + gbc.gridy = 4; + submitButton = new JButton("生成试卷"); + submitButton.addActionListener(e -> handleSubmit()); + add(submitButton, gbc); + + // Back Button + gbc.gridy = 5; + backButton = new JButton("返回"); + backButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email)); + add(backButton, gbc); + } + + private void handleSubmit() { + try { + int count = Integer.parseInt(countField.getText().trim()); + if (count < 10 || count > 30) { + JOptionPane.showMessageDialog(this, "请输入10-30之间的数字", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + navigationController.showExamView(level, count); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "请输入有效的数字", "错误", JOptionPane.ERROR_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/RegisterView.java b/src/com/mathlearning/view/RegisterView.java new file mode 100644 index 0000000..2039a22 --- /dev/null +++ b/src/com/mathlearning/view/RegisterView.java @@ -0,0 +1,210 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import com.mathlearning.controller.NavigationController; +import com.mathlearning.util.EmailUtil; +import com.mathlearning.util.UsernameValidator; + +import javax.swing.*; +import java.awt.*; + +public class RegisterView extends JPanel { + private NavigationController navigationController; + private AuthController authController; + + private JTextField emailField; + private JTextField usernameField; + private JButton sendCodeButton; + private JButton verifyButton; + private JButton backButton; + private JLabel statusLabel; + private JTextField codeField; + private String currentVerificationCode; + + public RegisterView(NavigationController navigationController) { + this.navigationController = navigationController; + this.authController = new AuthController(); + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // Username + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 1; + add(new JLabel("用户名:"), gbc); + + gbc.gridx = 1; gbc.gridy = 1; + usernameField = new JTextField(20); + add(usernameField, gbc); + + // Email + gbc.gridx = 0; gbc.gridy = 2; + add(new JLabel("邮箱地址:"), gbc); + + gbc.gridx = 1; gbc.gridy = 2; + emailField = new JTextField(20); + add(emailField, gbc); + + // Verification Code + gbc.gridx = 0; gbc.gridy = 3; + add(new JLabel("验证码:"), gbc); + + gbc.gridx = 1; gbc.gridy = 3; + JPanel codePanel = new JPanel(new BorderLayout()); + codeField = new JTextField(10); + codePanel.add(codeField, BorderLayout.CENTER); + + sendCodeButton = new JButton("发送验证码"); + sendCodeButton.addActionListener(e -> handleSendCode()); + codePanel.add(sendCodeButton, BorderLayout.EAST); + + add(codePanel, gbc); + + // Requirements + gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; + JLabel usernameReqLabel = new JLabel(UsernameValidator.getRequirements(), JLabel.CENTER); + usernameReqLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + usernameReqLabel.setForeground(Color.GRAY); + add(usernameReqLabel, gbc); + + gbc.gridy = 5; + JLabel emailReqLabel = new JLabel(EmailUtil.getEmailRequirements(), JLabel.CENTER); + emailReqLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + emailReqLabel.setForeground(Color.GRAY); + add(emailReqLabel, gbc); + + // Status label + gbc.gridy = 6; + statusLabel = new JLabel("", JLabel.CENTER); + statusLabel.setForeground(Color.BLUE); + add(statusLabel, gbc); + + // Verify Button + gbc.gridy = 7; + verifyButton = new JButton("验证并继续"); + verifyButton.addActionListener(e -> handleVerify()); + add(verifyButton, gbc); + + // Back Button + gbc.gridy = 8; + backButton = new JButton("返回登录"); + backButton.addActionListener(e -> navigationController.showLoginView()); + add(backButton, gbc); + } + + private void handleSendCode() { + String email = emailField.getText().trim(); + String username = usernameField.getText().trim(); + + // 验证输入 + if (username.isEmpty() || email.isEmpty()) { + showError("请输入用户名和邮箱地址"); + return; + } + + if (!EmailUtil.isValidEmail(email)) { + showError("请输入有效的邮箱地址"); + return; + } + + if (!UsernameValidator.isValid(username)) { + showError(UsernameValidator.getRequirements()); + return; + } + + // 检查邮箱和用户名是否可用 + if (!authController.isEmailAvailable(email)) { + showError("该邮箱已被注册"); + return; + } + + if (!authController.isUsernameAvailable(username)) { + showError("该用户名已被使用"); + return; + } + + // 禁用按钮,显示发送中状态 + sendCodeButton.setEnabled(false); + sendCodeButton.setText("发送中..."); + statusLabel.setText("正在发送验证码,请稍候..."); + statusLabel.setForeground(Color.BLUE); + + // 在后台线程中执行发送验证码 + SwingWorker worker = new SwingWorker() { + @Override + protected String doInBackground() throws Exception { + return authController.sendVerificationCode(email, username); + } + + @Override + protected void done() { + try { + currentVerificationCode = get(); + if (currentVerificationCode != null) { + statusLabel.setText("验证码已发送!请在下方输入验证码"); + statusLabel.setForeground(Color.GREEN); + codeField.setEnabled(true); + verifyButton.setEnabled(true); + } else { + showError("发送验证码失败,请检查邮箱地址或稍后重试"); + } + } catch (Exception e) { + showError("发送验证码时发生错误: " + e.getMessage()); + } finally { + // 恢复按钮状态 + sendCodeButton.setEnabled(true); + sendCodeButton.setText("发送验证码"); + } + } + }; + + worker.execute(); + } + + private void handleVerify() { + String email = emailField.getText().trim(); + String username = usernameField.getText().trim(); + String code = codeField.getText().trim(); + + if (code.isEmpty()) { + showError("请输入验证码"); + return; + } + + // 验证验证码 + if (currentVerificationCode != null && code.equals(currentVerificationCode)) { + // 验证码正确,创建未注册用户 + boolean success = authController.createUnregisteredUser(email, username, currentVerificationCode); + if (success) { + showSuccess("验证成功!"); + // 跳转到设置密码页面 + navigationController.showSetPasswordView(email, currentVerificationCode); + } else { + showError("创建用户失败"); + } + } else { + showError("验证码错误,请重新输入"); + } + } + + private void showError(String message) { + statusLabel.setText(message); + statusLabel.setForeground(Color.RED); + } + + private void showSuccess(String message) { + statusLabel.setText(message); + statusLabel.setForeground(Color.GREEN); + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/ScoreView.java b/src/com/mathlearning/view/ScoreView.java new file mode 100644 index 0000000..6e17a98 --- /dev/null +++ b/src/com/mathlearning/view/ScoreView.java @@ -0,0 +1,111 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.NavigationController; + +import javax.swing.*; +import java.awt.*; + +public class ScoreView extends JPanel { + private NavigationController navigationController; + private String userIdentifier; + private String email; + private String level; + private int score; + private int totalQuestions; + + private JLabel scoreLabel; + private JLabel commentLabel; + private JButton retryButton; + private JButton newExamButton; + private JButton logoutButton; + + public ScoreView(NavigationController navigationController, String userIdentifier, String email, String level, int score, int totalQuestions) { + this.navigationController = navigationController; + this.userIdentifier = userIdentifier; + this.email = email; + this.level = level; + this.score = score; + this.totalQuestions = totalQuestions; + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(15, 15, 15, 15); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("考试结果", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // User info + gbc.gridy = 1; + String userInfo = "用户: " + userIdentifier; + if (!userIdentifier.equals(email)) { + userInfo += " (" + email + ")"; + } + JLabel userLabel = new JLabel(userInfo, JLabel.CENTER); + userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(userLabel, gbc); + + // Level info + gbc.gridy = 2; + JLabel levelLabel = new JLabel("级别: " + level, JLabel.CENTER); + levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(levelLabel, gbc); + + // Score Display + gbc.gridy = 3; + scoreLabel = new JLabel("得分: " + score + " / 100", JLabel.CENTER); + scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + scoreLabel.setForeground(getScoreColor()); + add(scoreLabel, gbc); + + // Comment + gbc.gridy = 4; + commentLabel = new JLabel(getScoreComment(), JLabel.CENTER); + commentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + add(commentLabel, gbc); + + // Stats + gbc.gridy = 5; + int correctCount = (score * totalQuestions) / 100; + JLabel statsLabel = new JLabel("正确题数: " + correctCount + " / " + totalQuestions, JLabel.CENTER); + statsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + add(statsLabel, gbc); + + // Retry Button + gbc.gridy = 6; + retryButton = new JButton("重新答题"); + retryButton.addActionListener(e -> navigationController.showExamView(level, totalQuestions)); + add(retryButton, gbc); + + // New Exam Button + gbc.gridy = 7; + newExamButton = new JButton("新的测试"); + newExamButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email)); + add(newExamButton, gbc); + + // Logout Button + gbc.gridy = 8; + logoutButton = new JButton("退出登录"); + logoutButton.addActionListener(e -> navigationController.showLoginView()); + add(logoutButton, gbc); + } + + private Color getScoreColor() { + if (score >= 80) return new Color(0, 128, 0); // 绿色 + if (score >= 60) return new Color(255, 165, 0); // 橙色 + return Color.RED; + } + + private String getScoreComment() { + if (score >= 90) return "优秀!继续保持!"; + if (score >= 80) return "良好!还有进步空间!"; + if (score >= 60) return "及格!需要多加练习!"; + return "不及格!需要认真学习!"; + } +} \ No newline at end of file diff --git a/src/com/mathlearning/view/SetPasswordView.java b/src/com/mathlearning/view/SetPasswordView.java new file mode 100644 index 0000000..28fa1a0 --- /dev/null +++ b/src/com/mathlearning/view/SetPasswordView.java @@ -0,0 +1,100 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import com.mathlearning.controller.NavigationController; +import com.mathlearning.util.PasswordValidator; + +import javax.swing.*; +import java.awt.*; + +public class SetPasswordView extends JPanel { + private NavigationController navigationController; + private AuthController authController; + private String email; + + private JPasswordField passwordField; + private JPasswordField confirmPasswordField; + private JButton submitButton; + private JButton backButton; + + public SetPasswordView(NavigationController navigationController, String email, String verificationCode) { + this.navigationController = navigationController; + this.authController = new AuthController(); + this.email = email; + initializeUI(); + } + + private void initializeUI() { + setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Title + JLabel titleLabel = new JLabel("设置密码", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + add(titleLabel, gbc); + + // User info + gbc.gridy = 1; + JLabel userInfoLabel = new JLabel("邮箱: " + email, JLabel.CENTER); + userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(userInfoLabel, gbc); + + // Password requirements + gbc.gridy = 2; + JLabel requirementsLabel = new JLabel("密码要求: 6-10位,包含大小写字母和数字", JLabel.CENTER); + requirementsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + add(requirementsLabel, gbc); + + // Password + gbc.gridwidth = 1; + gbc.gridx = 0; gbc.gridy = 3; + add(new JLabel("密码:"), gbc); + + gbc.gridx = 1; gbc.gridy = 3; + passwordField = new JPasswordField(20); + add(passwordField, gbc); + + // Confirm Password + gbc.gridx = 0; gbc.gridy = 4; + add(new JLabel("确认密码:"), gbc); + + gbc.gridx = 1; gbc.gridy = 4; + confirmPasswordField = new JPasswordField(20); + add(confirmPasswordField, gbc); + + // Submit Button + gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 2; + submitButton = new JButton("设置密码"); + submitButton.addActionListener(e -> handleSetPassword()); + add(submitButton, gbc); + + // Back Button + gbc.gridy = 6; + backButton = new JButton("返回"); + backButton.addActionListener(e -> navigationController.showLoginView()); + add(backButton, gbc); + } + + private void handleSetPassword() { + String password = new String(passwordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + if (!PasswordValidator.isValid(password)) { + JOptionPane.showMessageDialog(this, + "密码不符合要求!\n请确保密码:\n- 长度6-10位\n- 包含大写字母\n- 包含小写字母\n- 包含数字", + "密码错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (authController.setPassword(email, password, confirmPassword)) { + JOptionPane.showMessageDialog(this, "密码设置成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + // 登录成功后使用邮箱作为标识符 + navigationController.showLevelSelectionView(email, email); + } else { + JOptionPane.showMessageDialog(this, "密码设置失败,请检查输入", "错误", JOptionPane.ERROR_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/email_config.properties.txt b/src/email_config.properties.txt new file mode 100644 index 0000000..76d2365 --- /dev/null +++ b/src/email_config.properties.txt @@ -0,0 +1,5 @@ +email.from=1393110457@qq.com +email.password=yuslygpkwwbtfedh +email.smtp.host=smtp.qq.com +email.smtp.port=465 +email.smtp.ssl=true \ No newline at end of file diff --git a/src/exam_papers/wjw/2025-10-11-13-29-32.txt b/src/exam_papers/wjw/2025-10-11-13-29-32.txt new file mode 100644 index 0000000..8dd9576 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-29-32.txt @@ -0,0 +1,145 @@ +试卷级别: 小学 +生成时间: 2025年10月11日 13时29分32秒 +题目数量: 20 + +1. 21 - 4 + 70 = ? + A. 75 + B. 87 + C. 72 + D. 101 + +2. 76 - 32 + 23 - 21 = ? + A. 39 + B. 38 + C. 46 + D. 53 + +3. 64 + 86 + 25 × 21 = ? + A. 675 + B. 792 + C. 628 + D. 771 + +4. 8 + 75 + 74 + 81 = ? + A. 238 + B. 198 + C. 241 + D. 244 + +5. 79 - 6 + 86 = ? + A. 159 + B. 138 + C. 137 + D. 185 + +6. 85 + 9 - 24 = ? + A. 73 + B. 70 + C. 58 + D. 78 + +7. 91 + 20 + 42 = ? + A. 134 + B. 125 + C. 143 + D. 153 + +8. 11 + 71 × 8 + 52 = ? + A. 675 + B. 631 + C. 601 + D. 511 + +9. 64 + 54 - 75 = ? + A. 43 + B. 44 + C. 39 + D. 38 + +10. 30 + 85 - 42 = ? + A. 84 + B. 64 + C. 58 + D. 73 + +11. 22 + 94 + 89 - 97 = ? + A. 108 + B. 88 + C. 105 + D. 94 + +12. 62 + 27 - 21 = ? + A. 70 + B. 68 + C. 63 + D. 79 + +13. 74 + 17 - 5 = ? + A. 85 + B. 86 + C. 92 + D. 90 + +14. 94 - 6 + 61 = ? + A. 120 + B. 135 + C. 149 + D. 121 + +15. 89 + 77 + 60 + 26 = ? + A. 227 + B. 291 + C. 299 + D. 252 + +16. 100 + 22 - 50 = ? + A. 86 + B. 72 + C. 84 + D. 80 + +17. 69 ÷ 1 + 92 = ? + A. 129 + B. 161 + C. 135 + D. 146 + +18. 98 + 44 + 97 × 4 = ? + A. 530 + B. 463 + C. 482 + D. 433 + +19. 98 + 80 - 68 = ? + A. 107 + B. 110 + C. 119 + D. 127 + +20. 32 + 5 × 36 = ? + A. 243 + B. 179 + C. 240 + D. 212 + +=== 参考答案 === +1. 87 +2. 46 +3. 675 +4. 238 +5. 159 +6. 70 +7. 153 +8. 631 +9. 43 +10. 73 +11. 108 +12. 68 +13. 86 +14. 149 +15. 252 +16. 72 +17. 161 +18. 530 +19. 110 +20. 212 diff --git a/src/exam_papers/wjw/2025-10-11-13-29-45.txt b/src/exam_papers/wjw/2025-10-11-13-29-45.txt new file mode 100644 index 0000000..3594b30 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-29-45.txt @@ -0,0 +1,75 @@ +试卷级别: 初中 +生成时间: 2025年10月11日 13时29分45秒 +题目数量: 10 + +1. 64² = ? + A. 4468 + B. 4122 + C. 4256 + D. 4096 + +2. √36 = ? + A. 7 + B. 5 + C. 6 + D. 4 + +3. 13² = ? + A. 144 + B. 178 + C. 200 + D. 169 + +4. 40² = ? + A. 1567 + B. 1439 + C. 1600 + D. 1369 + +5. √49 = ? + A. 6 + B. 5 + C. 7 + D. 8 + +6. (16 + 1)² = ? + A. 339 + B. 289 + C. 326 + D. 346 + +7. 97 × 57 + 15 = ? + A. 6620 + B. 5544 + C. 5391 + D. 5893 + +8. 66² = ? + A. 4107 + B. 4356 + C. 4323 + D. 4763 + +9. √100 = ? + A. 9 + B. 8 + C. 11 + D. 10 + +10. (22 + 31)² = ? + A. 2942 + B. 2654 + C. 2648 + D. 2809 + +=== 参考答案 === +1. 4096 +2. 6 +3. 169 +4. 1600 +5. 7 +6. 289 +7. 5544 +8. 4356 +9. 10 +10. 2809 diff --git a/src/exam_papers/wjw/2025-10-11-13-30-14.txt b/src/exam_papers/wjw/2025-10-11-13-30-14.txt new file mode 100644 index 0000000..96076ff --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-30-14.txt @@ -0,0 +1,75 @@ +试卷级别: 高中 +生成时间: 2025年10月11日 13时30分14秒 +题目数量: 10 + +1. 70 + cos(0°) - 91 = ? + A. -18.61 + B. -21.55 + C. -20.00 + D. -23.39 + +2. cos(150°) = ? + A. -0.87 + B. -0.74 + C. -0.89 + D. -1.01 + +3. 53 + tan(60°) - 54 = ? + A. 0.63 + B. 0.62 + C. 0.73 + D. 0.77 + +4. sin(150°) = ? + A. 0.53 + B. 0.42 + C. 0.50 + D. 0.60 + +5. 8 + sin(45°) - 82 = ? + A. -61.24 + B. -65.98 + C. -87.77 + D. -73.29 + +6. 73 - cos(180°) - 24 = ? + A. 50.00 + B. 41.82 + C. 59.18 + D. 56.02 + +7. cos(45°) = ? + A. 0.71 + B. 0.83 + C. 0.70 + D. 0.61 + +8. cos(120°) = ? + A. -0.53 + B. -0.50 + C. -0.59 + D. -0.42 + +9. 59 - cos(120°) - 29 = ? + A. 30.50 + B. 30.45 + C. 33.74 + D. 27.00 + +10. cos(135°) = ? + A. -0.77 + B. -0.70 + C. -0.71 + D. -0.61 + +=== 参考答案 === +1. -20.00 +2. -0.87 +3. 0.73 +4. 0.50 +5. -73.29 +6. 50.00 +7. 0.71 +8. -0.50 +9. 30.50 +10. -0.71 diff --git a/src/exam_papers/wjw/2025-10-11-13-40-07.txt b/src/exam_papers/wjw/2025-10-11-13-40-07.txt new file mode 100644 index 0000000..0e23104 --- /dev/null +++ b/src/exam_papers/wjw/2025-10-11-13-40-07.txt @@ -0,0 +1,75 @@ +试卷级别: 小学 +生成时间: 2025年10月11日 13时40分07秒 +题目数量: 10 + +1. 5 + 60 = ? + A. 63 + B. 71 + C. 59 + D. 65 + +2. 68 + 22 = ? + A. 87 + B. 90 + C. 78 + D. 76 + +3. 77 + 26 = ? + A. 103 + B. 84 + C. 108 + D. 106 + +4. 62 + 59 = ? + A. 142 + B. 121 + C. 123 + D. 103 + +5. 6 × 6 = ? + A. 32 + B. 36 + C. 38 + D. 42 + +6. 96 + 35 = ? + A. 127 + B. 120 + C. 131 + D. 106 + +7. 96 + 12 = ? + A. 89 + B. 120 + C. 108 + D. 115 + +8. 66 + 2 = ? + A. 55 + B. 77 + C. 74 + D. 68 + +9. 99 - 37 = ? + A. 68 + B. 50 + C. 59 + D. 62 + +10. 81 - 11 = ? + A. 57 + B. 56 + C. 75 + D. 70 + +=== 参考答案 === +1. 65 +2. 90 +3. 103 +4. 121 +5. 36 +6. 131 +7. 108 +8. 68 +9. 62 +10. 70 diff --git a/src/users.dat b/src/users.dat new file mode 100644 index 0000000000000000000000000000000000000000..f9addffb73f903391b0ceda269985315e87d6602 GIT binary patch literal 255 zcmW-bK~BRk5Je|xLksGnWx<9!xHU~d6$>g=sz3@GstSn}qu3;_oH((ECaa!^OCV0b zmOF3<9KQ7DOMmg^fBZt^EKK%zK=w{bvwL*;BUaJi=kx30^}7e*2F|oWvB8v#eSlNS zt#rB3qOI;$&}q~R{T+J;1QR}A&N-jOiNoLBCv{EDx$pV!oI8VBTQ;lUd-IDcy+XRLcI@cI0YB)kaf$>3F^`=rJ eWy-bZ|MzVbbQuy1p7+fxzTyNY@ikv8`t4U{33rs`~t83 zga6>B=!1E~3~%1N_aE?#hT{VF$aW+oyFruRVeO5+KR!=izFcs&Fp&z=EheP&EsQDG z+~i88U46ZV9@EyhJhSBxOoA|4Hw{ZvX`Rn(V##;D1w+;FrU+}9GZ8*%rku#uU Date: Sat, 11 Oct 2025 16:33:13 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/#说明.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/#说明.md diff --git a/doc/#说明.md b/doc/#说明.md new file mode 100644 index 0000000..e69de29 -- 2.34.1 From 7a3c2c49fce2100f50e94c0315715517a8410ea8 Mon Sep 17 00:00:00 2001 From: wang421 <1393110457@qq.com> Date: Sat, 11 Oct 2025 18:34:25 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/exam_papers/wjw/2025-10-11-13-29-32.txt | 145 -------------------- src/exam_papers/wjw/2025-10-11-13-29-45.txt | 75 ---------- src/exam_papers/wjw/2025-10-11-13-30-14.txt | 75 ---------- src/exam_papers/wjw/2025-10-11-13-40-07.txt | 75 ---------- 4 files changed, 370 deletions(-) delete mode 100644 src/exam_papers/wjw/2025-10-11-13-29-32.txt delete mode 100644 src/exam_papers/wjw/2025-10-11-13-29-45.txt delete mode 100644 src/exam_papers/wjw/2025-10-11-13-30-14.txt delete mode 100644 src/exam_papers/wjw/2025-10-11-13-40-07.txt diff --git a/src/exam_papers/wjw/2025-10-11-13-29-32.txt b/src/exam_papers/wjw/2025-10-11-13-29-32.txt deleted file mode 100644 index 8dd9576..0000000 --- a/src/exam_papers/wjw/2025-10-11-13-29-32.txt +++ /dev/null @@ -1,145 +0,0 @@ -试卷级别: 小学 -生成时间: 2025年10月11日 13时29分32秒 -题目数量: 20 - -1. 21 - 4 + 70 = ? - A. 75 - B. 87 - C. 72 - D. 101 - -2. 76 - 32 + 23 - 21 = ? - A. 39 - B. 38 - C. 46 - D. 53 - -3. 64 + 86 + 25 × 21 = ? - A. 675 - B. 792 - C. 628 - D. 771 - -4. 8 + 75 + 74 + 81 = ? - A. 238 - B. 198 - C. 241 - D. 244 - -5. 79 - 6 + 86 = ? - A. 159 - B. 138 - C. 137 - D. 185 - -6. 85 + 9 - 24 = ? - A. 73 - B. 70 - C. 58 - D. 78 - -7. 91 + 20 + 42 = ? - A. 134 - B. 125 - C. 143 - D. 153 - -8. 11 + 71 × 8 + 52 = ? - A. 675 - B. 631 - C. 601 - D. 511 - -9. 64 + 54 - 75 = ? - A. 43 - B. 44 - C. 39 - D. 38 - -10. 30 + 85 - 42 = ? - A. 84 - B. 64 - C. 58 - D. 73 - -11. 22 + 94 + 89 - 97 = ? - A. 108 - B. 88 - C. 105 - D. 94 - -12. 62 + 27 - 21 = ? - A. 70 - B. 68 - C. 63 - D. 79 - -13. 74 + 17 - 5 = ? - A. 85 - B. 86 - C. 92 - D. 90 - -14. 94 - 6 + 61 = ? - A. 120 - B. 135 - C. 149 - D. 121 - -15. 89 + 77 + 60 + 26 = ? - A. 227 - B. 291 - C. 299 - D. 252 - -16. 100 + 22 - 50 = ? - A. 86 - B. 72 - C. 84 - D. 80 - -17. 69 ÷ 1 + 92 = ? - A. 129 - B. 161 - C. 135 - D. 146 - -18. 98 + 44 + 97 × 4 = ? - A. 530 - B. 463 - C. 482 - D. 433 - -19. 98 + 80 - 68 = ? - A. 107 - B. 110 - C. 119 - D. 127 - -20. 32 + 5 × 36 = ? - A. 243 - B. 179 - C. 240 - D. 212 - -=== 参考答案 === -1. 87 -2. 46 -3. 675 -4. 238 -5. 159 -6. 70 -7. 153 -8. 631 -9. 43 -10. 73 -11. 108 -12. 68 -13. 86 -14. 149 -15. 252 -16. 72 -17. 161 -18. 530 -19. 110 -20. 212 diff --git a/src/exam_papers/wjw/2025-10-11-13-29-45.txt b/src/exam_papers/wjw/2025-10-11-13-29-45.txt deleted file mode 100644 index 3594b30..0000000 --- a/src/exam_papers/wjw/2025-10-11-13-29-45.txt +++ /dev/null @@ -1,75 +0,0 @@ -试卷级别: 初中 -生成时间: 2025年10月11日 13时29分45秒 -题目数量: 10 - -1. 64² = ? - A. 4468 - B. 4122 - C. 4256 - D. 4096 - -2. √36 = ? - A. 7 - B. 5 - C. 6 - D. 4 - -3. 13² = ? - A. 144 - B. 178 - C. 200 - D. 169 - -4. 40² = ? - A. 1567 - B. 1439 - C. 1600 - D. 1369 - -5. √49 = ? - A. 6 - B. 5 - C. 7 - D. 8 - -6. (16 + 1)² = ? - A. 339 - B. 289 - C. 326 - D. 346 - -7. 97 × 57 + 15 = ? - A. 6620 - B. 5544 - C. 5391 - D. 5893 - -8. 66² = ? - A. 4107 - B. 4356 - C. 4323 - D. 4763 - -9. √100 = ? - A. 9 - B. 8 - C. 11 - D. 10 - -10. (22 + 31)² = ? - A. 2942 - B. 2654 - C. 2648 - D. 2809 - -=== 参考答案 === -1. 4096 -2. 6 -3. 169 -4. 1600 -5. 7 -6. 289 -7. 5544 -8. 4356 -9. 10 -10. 2809 diff --git a/src/exam_papers/wjw/2025-10-11-13-30-14.txt b/src/exam_papers/wjw/2025-10-11-13-30-14.txt deleted file mode 100644 index 96076ff..0000000 --- a/src/exam_papers/wjw/2025-10-11-13-30-14.txt +++ /dev/null @@ -1,75 +0,0 @@ -试卷级别: 高中 -生成时间: 2025年10月11日 13时30分14秒 -题目数量: 10 - -1. 70 + cos(0°) - 91 = ? - A. -18.61 - B. -21.55 - C. -20.00 - D. -23.39 - -2. cos(150°) = ? - A. -0.87 - B. -0.74 - C. -0.89 - D. -1.01 - -3. 53 + tan(60°) - 54 = ? - A. 0.63 - B. 0.62 - C. 0.73 - D. 0.77 - -4. sin(150°) = ? - A. 0.53 - B. 0.42 - C. 0.50 - D. 0.60 - -5. 8 + sin(45°) - 82 = ? - A. -61.24 - B. -65.98 - C. -87.77 - D. -73.29 - -6. 73 - cos(180°) - 24 = ? - A. 50.00 - B. 41.82 - C. 59.18 - D. 56.02 - -7. cos(45°) = ? - A. 0.71 - B. 0.83 - C. 0.70 - D. 0.61 - -8. cos(120°) = ? - A. -0.53 - B. -0.50 - C. -0.59 - D. -0.42 - -9. 59 - cos(120°) - 29 = ? - A. 30.50 - B. 30.45 - C. 33.74 - D. 27.00 - -10. cos(135°) = ? - A. -0.77 - B. -0.70 - C. -0.71 - D. -0.61 - -=== 参考答案 === -1. -20.00 -2. -0.87 -3. 0.73 -4. 0.50 -5. -73.29 -6. 50.00 -7. 0.71 -8. -0.50 -9. 30.50 -10. -0.71 diff --git a/src/exam_papers/wjw/2025-10-11-13-40-07.txt b/src/exam_papers/wjw/2025-10-11-13-40-07.txt deleted file mode 100644 index 0e23104..0000000 --- a/src/exam_papers/wjw/2025-10-11-13-40-07.txt +++ /dev/null @@ -1,75 +0,0 @@ -试卷级别: 小学 -生成时间: 2025年10月11日 13时40分07秒 -题目数量: 10 - -1. 5 + 60 = ? - A. 63 - B. 71 - C. 59 - D. 65 - -2. 68 + 22 = ? - A. 87 - B. 90 - C. 78 - D. 76 - -3. 77 + 26 = ? - A. 103 - B. 84 - C. 108 - D. 106 - -4. 62 + 59 = ? - A. 142 - B. 121 - C. 123 - D. 103 - -5. 6 × 6 = ? - A. 32 - B. 36 - C. 38 - D. 42 - -6. 96 + 35 = ? - A. 127 - B. 120 - C. 131 - D. 106 - -7. 96 + 12 = ? - A. 89 - B. 120 - C. 108 - D. 115 - -8. 66 + 2 = ? - A. 55 - B. 77 - C. 74 - D. 68 - -9. 99 - 37 = ? - A. 68 - B. 50 - C. 59 - D. 62 - -10. 81 - 11 = ? - A. 57 - B. 56 - C. 75 - D. 70 - -=== 参考答案 === -1. 65 -2. 90 -3. 103 -4. 121 -5. 36 -6. 131 -7. 108 -8. 68 -9. 62 -10. 70 -- 2.34.1 From e042bc6c5d080cec1c7db133764aa4ed38b75e1b Mon Sep 17 00:00:00 2001 From: wang421 <1393110457@qq.com> Date: Sat, 11 Oct 2025 18:44:50 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/com/mathlearning/Main.java | 1 - .../controller/AuthController.java | 28 +++---------- .../controller/ExamController.java | 28 ++----------- .../controller/NavigationController.java | 2 - .../mathlearning/model/QuestionGenerator.java | 40 +++++-------------- src/com/mathlearning/model/User.java | 3 +- src/com/mathlearning/model/UserManager.java | 26 ++++-------- src/com/mathlearning/util/EmailConfig.java | 16 +++----- src/com/mathlearning/util/EmailUtil.java | 27 +++---------- src/com/mathlearning/util/ExamFileUtil.java | 21 ++-------- src/com/mathlearning/util/FileStorage.java | 2 - .../mathlearning/util/UsernameValidator.java | 1 - src/com/mathlearning/view/ExamView.java | 40 ++----------------- .../mathlearning/view/LevelSelectionView.java | 6 --- src/com/mathlearning/view/LoginView.java | 11 +---- .../mathlearning/view/QuestionCountView.java | 6 --- src/com/mathlearning/view/RegisterView.java | 16 -------- src/com/mathlearning/view/ScoreView.java | 9 ----- .../mathlearning/view/SetPasswordView.java | 8 ---- 19 files changed, 46 insertions(+), 245 deletions(-) diff --git a/src/com/mathlearning/Main.java b/src/com/mathlearning/Main.java index 612da2f..7c423fa 100644 --- a/src/com/mathlearning/Main.java +++ b/src/com/mathlearning/Main.java @@ -6,7 +6,6 @@ import javax.swing.*; public class Main { public static void main(String[] args) { - // 直接启动程序,不使用特殊的外观设置 SwingUtilities.invokeLater(() -> { NavigationController navigationController = new NavigationController(); navigationController.showLoginView(); diff --git a/src/com/mathlearning/controller/AuthController.java b/src/com/mathlearning/controller/AuthController.java index ccfbc95..548cc05 100644 --- a/src/com/mathlearning/controller/AuthController.java +++ b/src/com/mathlearning/controller/AuthController.java @@ -13,9 +13,7 @@ public class AuthController { this.userManager = UserManager.getInstance(); } - /** - * 发送验证码(不创建用户) - */ + public String sendVerificationCode(String email, String username) { if (!isValidEmail(email)) { return null; @@ -25,45 +23,35 @@ public class AuthController { return null; } - // 检查邮箱是否已被注册 if (userManager.userExistsByEmail(email)) { User existingUser = userManager.getUserByEmail(email); if (existingUser.isRegistered()) { - return null; // 邮箱已被注册 + return null; } - // 如果用户存在但未注册,可以重新发送验证码 } - // 检查用户名是否已被使用 if (userManager.userExistsByUsername(username)) { User existingUser = userManager.getUserByUsername(username); if (existingUser.isRegistered() || !existingUser.getEmail().equals(email)) { - return null; // 用户名已被其他用户使用 + return null; } - // 如果是同一邮箱重新发送验证码,允许使用相同的用户名 } - // 发送验证码 String verificationCode = EmailUtil.sendVerificationCode(email); return verificationCode; } - /** - * 创建未注册用户(保存验证码) - */ + public boolean createUnregisteredUser(String email, String username, String verificationCode) { if (verificationCode == null) { return false; } - // 检查是否已存在未注册用户 User existingUser = userManager.getUserByEmail(email); if (existingUser != null && !existingUser.isRegistered()) { - // 更新现有未注册用户的验证码 existingUser.setUsername(username); existingUser.setVerificationCode(verificationCode); } else { - // 创建新用户 User user = new User(email, username); user.setVerificationCode(verificationCode); userManager.addUser(user); @@ -131,17 +119,13 @@ public class AuthController { return user != null ? user.getEmail() : identifier; } - /** - * 检查邮箱是否可用 - */ + public boolean isEmailAvailable(String email) { User user = userManager.getUserByEmail(email); return user == null || !user.isRegistered(); } - /** - * 检查用户名是否可用 - */ + public boolean isUsernameAvailable(String username) { User user = userManager.getUserByUsername(username); return user == null || !user.isRegistered(); diff --git a/src/com/mathlearning/controller/ExamController.java b/src/com/mathlearning/controller/ExamController.java index 4d25f85..29625a1 100644 --- a/src/com/mathlearning/controller/ExamController.java +++ b/src/com/mathlearning/controller/ExamController.java @@ -21,16 +21,13 @@ public class ExamController { this.currentLevel = level; try { - // 使用工厂方法创建对应的生成器 QuestionGenerator generator = QuestionGenerator.createGenerator(level); System.out.println("生成器创建完成: " + generator.getClass().getSimpleName()); - // 设置超时保护 List questions = generateQuestionsWithTimeout(generator, questionCount, 15000); // 15秒超时 System.out.println("题目生成完成,生成了 " + questions.size() + " 道题目"); - // 额外的重复检查 int duplicateCount = checkForDuplicates(questions); if (duplicateCount > 0) { System.out.println("警告: 发现 " + duplicateCount + " 道重复题目"); @@ -48,9 +45,7 @@ public class ExamController { } catch (Exception e) { System.err.println("创建试卷时发生错误: " + e.getMessage()); e.printStackTrace(); - // 创建空试卷避免空指针 currentPaper = new Paper(level); - // 补充基础题目 List basicQuestions = generateBasicQuestions(questionCount); for (Question question : basicQuestions) { currentPaper.addQuestion(question); @@ -58,9 +53,7 @@ public class ExamController { } } - /** - * 带超时保护的题目生成 - */ + private List generateQuestionsWithTimeout(QuestionGenerator generator, int count, long timeoutMs) { long startTime = System.currentTimeMillis(); List questions = new ArrayList<>(); @@ -69,12 +62,10 @@ public class ExamController { List generated = generator.generateQuestions(count); questions.addAll(generated); - // 检查是否超时 if ((System.currentTimeMillis() - startTime) > timeoutMs) { System.out.println("生成题目超时,使用已生成的部分题目"); } - // 如果生成的题目超过所需数量,截取 if (questions.size() > count) { questions = questions.subList(0, count); } @@ -83,7 +74,6 @@ public class ExamController { System.err.println("生成题目时出错: " + e.getMessage()); } - // 如果题目数量不足,补充基础题目 if (questions.size() < count) { System.out.println("题目数量不足,补充基础题目..."); int needed = count - questions.size(); @@ -94,9 +84,7 @@ public class ExamController { return questions; } - /** - * 生成基础题目作为备选 - */ + private List generateBasicQuestions(int count) { System.out.println("生成 " + count + " 道基础题目"); List basicQuestions = new ArrayList<>(); @@ -111,11 +99,9 @@ public class ExamController { int answer; if (operation == 0) { - // 加法 expression = a + " + " + b; answer = a + b; } else { - // 减法,确保结果为正 if (a < b) { int temp = a; a = b; @@ -125,7 +111,6 @@ public class ExamController { answer = a - b; } - // 生成选项 Set options = new HashSet<>(); options.add(String.valueOf(answer)); @@ -150,10 +135,7 @@ public class ExamController { return basicQuestions; } - /** - * 保存当前试卷到文件 - * @return 是否保存成功 - */ + public boolean saveCurrentPaper() { if (currentPaper == null || currentUser == null) { System.err.println("无法保存试卷: 试卷或用户信息为空"); @@ -163,7 +145,6 @@ public class ExamController { return ExamFileUtil.savePaperToFile(currentUser, currentPaper); } - // 检查重复题目的辅助方法 private int checkForDuplicates(List questions) { Set expressions = new HashSet<>(); int duplicateCount = 0; @@ -181,14 +162,11 @@ public class ExamController { return duplicateCount; } - // 从题目内容中提取表达式(去掉"= ?"等后缀) private String extractExpression(String content) { if (content == null) return ""; - // 去掉"= ?"和可能的空格 return content.replace(" = ?", "").trim(); } - // 其他方法保持不变... public Question getCurrentQuestion() { return currentPaper != null ? currentPaper.getCurrentQuestion() : null; } diff --git a/src/com/mathlearning/controller/NavigationController.java b/src/com/mathlearning/controller/NavigationController.java index cf4143d..548a6b5 100644 --- a/src/com/mathlearning/controller/NavigationController.java +++ b/src/com/mathlearning/controller/NavigationController.java @@ -25,7 +25,6 @@ public class NavigationController { cardLayout = new CardLayout(); mainPanel = new JPanel(cardLayout); - // Initialize with login view LoginView loginView = new LoginView(this); mainPanel.add(loginView, "LOGIN"); @@ -76,7 +75,6 @@ public class NavigationController { } public void showChangePasswordView(String identifier) { - // 实现修改密码视图的显示 JOptionPane.showMessageDialog(mainFrame, "修改密码功能待实现", "提示", JOptionPane.INFORMATION_MESSAGE); } diff --git a/src/com/mathlearning/model/QuestionGenerator.java b/src/com/mathlearning/model/QuestionGenerator.java index ac6668b..7f4f097 100644 --- a/src/com/mathlearning/model/QuestionGenerator.java +++ b/src/com/mathlearning/model/QuestionGenerator.java @@ -11,19 +11,16 @@ public abstract class QuestionGenerator { Set options = new HashSet<>(); options.add(correctAnswer); - // 生成错误选项 while (options.size() < 4) { try { double correctValue = Double.parseDouble(correctAnswer); double variation = correctValue * 0.2 + 1; // 20% 变化,至少1 double wrongValue = correctValue + (random.nextDouble() * 2 - 1) * variation; - // 避免负数和零 if (wrongValue <= 0) { wrongValue = Math.abs(wrongValue) + 1; } - // 格式化选项 String formattedValue; if (correctAnswer.contains(".")) { formattedValue = String.format("%.2f", wrongValue); @@ -33,7 +30,6 @@ public abstract class QuestionGenerator { options.add(formattedValue); } catch (NumberFormatException e) { - // 如果解析失败,使用整数方法 int wrongValue = random.nextInt(20) + 1; options.add(String.valueOf(wrongValue)); } @@ -44,7 +40,6 @@ public abstract class QuestionGenerator { return String.join(",", optionList); } - // 工厂方法 public static QuestionGenerator createGenerator(String level) { switch (level) { case "小学": @@ -58,18 +53,15 @@ public abstract class QuestionGenerator { } } - // 生成随机操作数 (1-50) 减小范围提高效率 protected int generateOperand() { return random.nextInt(50) + 1; } - // 生成小范围操作数用于乘法 protected int generateSmallOperand() { return random.nextInt(12) + 1; } } -// 优化的小学题目生成器 class OptimizedPrimaryQuestionGenerator extends QuestionGenerator { @Override public List generateQuestions(int count) { @@ -83,7 +75,7 @@ class OptimizedPrimaryQuestionGenerator extends QuestionGenerator { {"{0} × {1} = ?", "×"} }; - int maxAttempts = count * 5; // 减少尝试次数 + int maxAttempts = count * 5; int attempts = 0; while (questions.size() < count && attempts < maxAttempts) { @@ -103,7 +95,6 @@ class OptimizedPrimaryQuestionGenerator extends QuestionGenerator { case "-": int a2 = generateOperand(); int b2 = generateOperand(); - // 确保结果为正数 if (a2 < b2) { int temp = a2; a2 = b2; @@ -121,12 +112,9 @@ class OptimizedPrimaryQuestionGenerator extends QuestionGenerator { default: continue; } - - // 检查表达式是否已使用 - if (usedExpressions.contains(expression)) { + if (usedExpressions.contains(expression)) { continue; } - usedExpressions.add(expression); String options = generateOptions(String.valueOf(answer)); questions.add(new Question(expression + " = ?", options, String.valueOf(answer))); @@ -138,7 +126,6 @@ class OptimizedPrimaryQuestionGenerator extends QuestionGenerator { System.out.println("小学题目生成完成,实际生成: " + questions.size() + " 道题目"); - // 如果因为重复问题无法生成足够的题目,补充简单题目 if (questions.size() < count) { System.out.println("补充简单小学题目..."); for (int i = questions.size(); i < count; i++) { @@ -158,7 +145,6 @@ class OptimizedPrimaryQuestionGenerator extends QuestionGenerator { } } -// 优化的初中题目生成器 class OptimizedMiddleSchoolQuestionGenerator extends QuestionGenerator { @Override public List generateQuestions(int count) { @@ -172,29 +158,29 @@ class OptimizedMiddleSchoolQuestionGenerator extends QuestionGenerator { while (questions.size() < count && attempts < maxAttempts) { attempts++; - int type = random.nextInt(4); // 0-3: 平方、开方、混合、简单运算 + int type = random.nextInt(4); String expression; int answer; switch (type) { - case 0: // 平方 - int num = random.nextInt(15) + 1; // 1-15的平方 + case 0: + int num = random.nextInt(15) + 1; expression = num + "²"; answer = num * num; break; - case 1: // 开根号 - int root = random.nextInt(12) + 1; // 1-12 + case 1: + int root = random.nextInt(12) + 1; expression = "√" + (root * root); answer = root; break; - case 2: // 混合运算 - int x = random.nextInt(8) + 2; // 2-9 - int y = random.nextInt(8) + 2; // 2-9 + case 2: + int x = random.nextInt(8) + 2; + int y = random.nextInt(8) + 2; expression = "(" + x + " + " + y + ")²"; answer = (x + y) * (x + y); break; - default: // 简单运算 + default: int a = generateOperand(); int b = generateOperand(); if (random.nextBoolean()) { @@ -230,7 +216,6 @@ class OptimizedMiddleSchoolQuestionGenerator extends QuestionGenerator { } } -// 优化的高中题目生成器 class OptimizedHighSchoolQuestionGenerator extends QuestionGenerator { @Override public List generateQuestions(int count) { @@ -238,7 +223,6 @@ class OptimizedHighSchoolQuestionGenerator extends QuestionGenerator { List questions = new ArrayList<>(); Set usedExpressions = new HashSet<>(); - // 使用常见角度,避免复杂计算 int[] angles = {0, 30, 45, 60, 90, 120, 135, 150, 180}; String[] functions = {"sin", "cos", "tan"}; @@ -262,12 +246,10 @@ class OptimizedHighSchoolQuestionGenerator extends QuestionGenerator { result = Math.cos(Math.toRadians(angle)); break; case "tan": - // 避免tan(90°)等未定义的情况 if (angle % 90 == 0 && angle % 180 != 0) { continue; } result = Math.tan(Math.toRadians(angle)); - // 限制tan值范围 if (Math.abs(result) > 10) { continue; } diff --git a/src/com/mathlearning/model/User.java b/src/com/mathlearning/model/User.java index 85ee047..76beaac 100644 --- a/src/com/mathlearning/model/User.java +++ b/src/com/mathlearning/model/User.java @@ -6,7 +6,7 @@ public class User implements Serializable { private static final long serialVersionUID = 1L; private String email; - private String username; // 新增用户名字段 + private String username; private String password; private String verificationCode; private boolean isRegistered; @@ -17,7 +17,6 @@ public class User implements Serializable { this.isRegistered = false; } - // Getters and setters public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } diff --git a/src/com/mathlearning/model/UserManager.java b/src/com/mathlearning/model/UserManager.java index c1fbc6d..8c6ded8 100644 --- a/src/com/mathlearning/model/UserManager.java +++ b/src/com/mathlearning/model/UserManager.java @@ -7,8 +7,8 @@ import java.util.HashMap; public class UserManager { private static UserManager instance; - private Map usersByEmail; // 按邮箱存储 - private Map usersByUsername; // 按用户名存储 + private Map usersByEmail; + private Map usersByUsername; private UserManager() { this.usersByEmail = FileStorage.loadUsersByEmail(); @@ -36,7 +36,6 @@ public class UserManager { return usersByUsername.get(username); } - // 通过邮箱或用户名查找用户 public User getUser(String identifier) { User user = usersByEmail.get(identifier); if (user == null) { @@ -53,7 +52,6 @@ public class UserManager { return usersByUsername.containsKey(username); } - // 检查邮箱或用户名是否已存在 public boolean userExists(String identifier) { return usersByEmail.containsKey(identifier) || usersByUsername.containsKey(identifier); } @@ -68,25 +66,19 @@ public class UserManager { FileStorage.saveUsers(usersByEmail, usersByUsername); } - /** - * 检查邮箱是否可用(未被注册) - */ + public boolean isEmailAvailable(String email) { User user = usersByEmail.get(email); return user == null || !user.isRegistered(); } - /** - * 检查用户名是否可用(未被注册) - */ + public boolean isUsernameAvailable(String username) { User user = usersByUsername.get(username); return user == null || !user.isRegistered(); } - /** - * 删除未注册的用户(清理过期数据) - */ + public void removeUnregisteredUser(String email) { User user = usersByEmail.get(email); if (user != null && !user.isRegistered()) { @@ -96,16 +88,12 @@ public class UserManager { } } - /** - * 获取所有用户数量(用于调试) - */ + public int getUserCount() { return usersByEmail.size(); } - /** - * 获取已注册用户数量(用于调试) - */ + public int getRegisteredUserCount() { int count = 0; for (User user : usersByEmail.values()) { diff --git a/src/com/mathlearning/util/EmailConfig.java b/src/com/mathlearning/util/EmailConfig.java index 9177b06..f35ffb9 100644 --- a/src/com/mathlearning/util/EmailConfig.java +++ b/src/com/mathlearning/util/EmailConfig.java @@ -11,23 +11,20 @@ public class EmailConfig { static { properties = new Properties(); try { - // 尝试从配置文件加载 properties.load(new FileInputStream(CONFIG_FILE)); } catch (IOException e) { - // 如果配置文件不存在,使用默认值 System.out.println("未找到配置文件,使用默认邮箱配置(需要修改为您的真实邮箱)"); setDefaultProperties(); } } private static void setDefaultProperties() { - // 这里设置您的邮箱配置 - 请修改为您的真实邮箱信息 - properties.setProperty("email.from", "your-email@qq.com"); // 发件邮箱 - properties.setProperty("email.password", "your-authorization-code"); // 授权码 - properties.setProperty("email.smtp.host", "smtp.qq.com"); // SMTP服务器 - properties.setProperty("email.smtp.port", "465"); // 端口 - properties.setProperty("email.smtp.ssl", "true"); // 启用SSL - properties.setProperty("email.smtp.auth", "true"); // 需要认证 + properties.setProperty("email.from", "your-email@qq.com"); + properties.setProperty("email.password", "your-authorization-code"); + properties.setProperty("email.smtp.host", "smtp.qq.com"); + properties.setProperty("email.smtp.port", "465"); + properties.setProperty("email.smtp.ssl", "true"); + properties.setProperty("email.smtp.auth", "true"); } public static String getFrom() { @@ -54,7 +51,6 @@ public class EmailConfig { return "true".equals(properties.getProperty("email.smtp.auth")); } - // 验证配置是否已设置(不是默认值) public static boolean isConfigured() { return !"your-email@qq.com".equals(getFrom()) && !"your-authorization-code".equals(getPassword()); diff --git a/src/com/mathlearning/util/EmailUtil.java b/src/com/mathlearning/util/EmailUtil.java index 180f2fd..85975a3 100644 --- a/src/com/mathlearning/util/EmailUtil.java +++ b/src/com/mathlearning/util/EmailUtil.java @@ -9,11 +9,8 @@ import java.util.Random; public class EmailUtil { private static final Random random = new Random(); - /** - * 发送验证码 - 真实邮箱版本 - */ + public static String sendVerificationCode(String toEmail) { - // 检查邮箱配置 if (!EmailConfig.isConfigured()) { JOptionPane.showMessageDialog(null, "邮箱配置未完成!\n请修改 email_config.properties 文件,配置您的邮箱信息。", @@ -22,11 +19,9 @@ public class EmailUtil { return null; } - // 生成6位验证码 String verificationCode = generateVerificationCode(); try { - // 发送邮件 boolean sendSuccess = sendEmail(toEmail, verificationCode); if (sendSuccess) { @@ -55,9 +50,7 @@ public class EmailUtil { } } - /** - * 发送邮件 - */ + private static boolean sendEmail(String toEmail, String verificationCode) { Properties props = new Properties(); props.put("mail.smtp.host", EmailConfig.getSmtpHost()); @@ -69,7 +62,6 @@ public class EmailUtil { props.put("mail.smtp.socketFactory.port", EmailConfig.getSmtpPort()); } - // 创建会话 Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { @@ -78,7 +70,6 @@ public class EmailUtil { }); try { - // 创建邮件 Message message = new MimeMessage(session); message.setFrom(new InternetAddress(EmailConfig.getFrom())); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); @@ -92,7 +83,6 @@ public class EmailUtil { message.setText(emailContent); - // 发送邮件 Transport.send(message); System.out.println("验证码邮件已发送到: " + toEmail); return true; @@ -104,29 +94,22 @@ public class EmailUtil { } } - /** - * 生成6位数字验证码 - */ + private static String generateVerificationCode() { return String.format("%06d", random.nextInt(1000000)); } - /** - * 验证邮箱格式 - */ + public static boolean isValidEmail(String email) { if (email == null || email.trim().isEmpty()) { return false; } - // 简单的邮箱格式验证 String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"; return email.matches(emailRegex); } - /** - * 获取邮箱验证要求说明 - */ + public static String getEmailRequirements() { return "请输入有效的邮箱地址,例如:username@example.com"; } diff --git a/src/com/mathlearning/util/ExamFileUtil.java b/src/com/mathlearning/util/ExamFileUtil.java index df9dc3d..efe7153 100644 --- a/src/com/mathlearning/util/ExamFileUtil.java +++ b/src/com/mathlearning/util/ExamFileUtil.java @@ -9,14 +9,8 @@ import java.util.Date; public class ExamFileUtil { - /** - * 保存试卷到文件 - * @param username 用户名 - * @param paper 试卷对象 - * @return 是否保存成功 - */ + public static boolean savePaperToFile(String username, Paper paper) { - // 创建用户文件夹 File userDir = new File("exam_papers/" + username); if (!userDir.exists()) { if (!userDir.mkdirs()) { @@ -25,34 +19,29 @@ public class ExamFileUtil { } } - // 生成文件名 String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); String filename = timestamp + ".txt"; File file = new File(userDir, filename); try (PrintWriter writer = new PrintWriter(new FileWriter(file))) { - // 写入试卷信息 writer.println("试卷级别: " + paper.getLevel()); writer.println("生成时间: " + new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(new Date())); writer.println("题目数量: " + paper.getTotalQuestions()); writer.println(); - // 写入题目 int questionNumber = 1; for (Question question : paper.getQuestions()) { writer.println(questionNumber + ". " + question.getContent()); - // 写入选项 String[] options = question.getOptions(); for (int i = 0; i < options.length; i++) { writer.println(" " + (char)('A' + i) + ". " + options[i]); } - writer.println(); // 题目之间空一行 + writer.println(); questionNumber++; } - // 写入答案 writer.println("=== 参考答案 ==="); questionNumber = 1; for (Question question : paper.getQuestions()) { @@ -70,11 +59,7 @@ public class ExamFileUtil { } } - /** - * 获取用户的所有试卷文件 - * @param username 用户名 - * @return 试卷文件列表 - */ + public static File[] getUserExamFiles(String username) { File userDir = new File("exam_papers/" + username); if (!userDir.exists()) { diff --git a/src/com/mathlearning/util/FileStorage.java b/src/com/mathlearning/util/FileStorage.java index 18f1613..71e38c2 100644 --- a/src/com/mathlearning/util/FileStorage.java +++ b/src/com/mathlearning/util/FileStorage.java @@ -15,7 +15,6 @@ public class FileStorage { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_EMAIL_FILE))) { return (Map) ois.readObject(); } catch (FileNotFoundException e) { - // 文件不存在,返回空Map return new HashMap<>(); } catch (IOException | ClassNotFoundException e) { System.err.println("加载用户数据失败: " + e.getMessage()); @@ -28,7 +27,6 @@ public class FileStorage { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_USERNAME_FILE))) { return (Map) ois.readObject(); } catch (FileNotFoundException e) { - // 文件不存在,返回空Map return new HashMap<>(); } catch (IOException | ClassNotFoundException e) { System.err.println("加载用户数据失败: " + e.getMessage()); diff --git a/src/com/mathlearning/util/UsernameValidator.java b/src/com/mathlearning/util/UsernameValidator.java index e47214b..a6725cc 100644 --- a/src/com/mathlearning/util/UsernameValidator.java +++ b/src/com/mathlearning/util/UsernameValidator.java @@ -6,7 +6,6 @@ public class UsernameValidator { return false; } - // 用户名只能包含字母、数字和下划线 return username.matches("^[a-zA-Z0-9_]+$"); } diff --git a/src/com/mathlearning/view/ExamView.java b/src/com/mathlearning/view/ExamView.java index 2f3c683..fcf4981 100644 --- a/src/com/mathlearning/view/ExamView.java +++ b/src/com/mathlearning/view/ExamView.java @@ -17,7 +17,6 @@ public class ExamView extends JPanel { private String level; private int questionCount; - // UI组件 private JLabel questionNumberLabel; private JLabel questionContentLabel; private ButtonGroup optionsGroup; @@ -29,7 +28,6 @@ public class ExamView extends JPanel { private JLabel statusLabel; private JButton cancelButton; - // 进度跟踪 private int generationProgress = 0; public ExamView(NavigationController navigationController, String userIdentifier, String email, String level, int questionCount) { @@ -48,7 +46,6 @@ public class ExamView extends JPanel { private void initializeUI() { setLayout(new BorderLayout(10, 10)); - // 顶部信息面板 JPanel infoPanel = new JPanel(new BorderLayout()); String userInfo = "用户: " + userIdentifier; if (!userIdentifier.equals(email)) { @@ -64,7 +61,6 @@ public class ExamView extends JPanel { add(infoPanel, BorderLayout.NORTH); - // 进度显示面板 JPanel progressPanel = new JPanel(new BorderLayout()); progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); @@ -77,11 +73,9 @@ public class ExamView extends JPanel { progressBar.setPreferredSize(new Dimension(300, 20)); progressPanel.add(progressBar, BorderLayout.CENTER); - // 取消按钮 JPanel buttonPanel = new JPanel(); cancelButton = new JButton("取消生成"); cancelButton.addActionListener(e -> { - // 返回题目数量选择界面 navigationController.showQuestionCountView(level); }); buttonPanel.add(cancelButton); @@ -93,18 +87,16 @@ public class ExamView extends JPanel { private void startExamGeneration() { System.out.println("开始生成试卷,级别: " + level + ", 题目数量: " + questionCount); - // 使用SwingWorker在后台生成题目 SwingWorker worker = new SwingWorker() { @Override protected Boolean doInBackground() throws Exception { publish("正在初始化生成器..."); - Thread.sleep(100); // 短暂延迟,让UI更新 + Thread.sleep(100); try { publish("开始生成题目..."); long startTime = System.currentTimeMillis(); - // 设置进度条为确定模式 SwingUtilities.invokeLater(() -> { progressBar.setIndeterminate(false); progressBar.setMinimum(0); @@ -112,7 +104,6 @@ public class ExamView extends JPanel { progressBar.setValue(0); }); - // 模拟进度更新 for (int i = 0; i <= 100; i += 10) { if (isCancelled()) { return false; @@ -138,12 +129,10 @@ public class ExamView extends JPanel { @Override protected void process(List chunks) { - // 更新状态信息 if (!chunks.isEmpty()) { String latestMessage = chunks.get(chunks.size() - 1); statusLabel.setText(latestMessage); - // 更新进度条文本显示 if (latestMessage.contains("%")) { progressBar.setString(latestMessage); progressBar.setStringPainted(true); @@ -156,11 +145,9 @@ public class ExamView extends JPanel { try { Boolean success = get(); if (success) { - // 题目生成成功,切换到考试界面 statusLabel.setText("题目生成成功!正在加载界面..."); progressBar.setValue(100); - // 短暂延迟后初始化考试界面 Timer timer = new Timer(500, e -> { initializeExamUI(); loadQuestion(); @@ -168,18 +155,15 @@ public class ExamView extends JPanel { timer.setRepeats(false); timer.start(); } else { - // 生成失败,显示错误信息 JOptionPane.showMessageDialog(ExamView.this, "生成试卷失败,将使用基础题目继续考试", "生成失败", JOptionPane.WARNING_MESSAGE); - // 即使生成失败也尝试继续 initializeExamUI(); loadQuestion(); } } catch (InterruptedException e) { - // 用户取消了操作 navigationController.showQuestionCountView(level); } catch (ExecutionException e) { e.printStackTrace(); @@ -196,11 +180,9 @@ public class ExamView extends JPanel { } private void initializeExamUI() { - // 移除进度显示 removeAll(); setLayout(new BorderLayout(10, 10)); - // Header Panel JPanel headerPanel = new JPanel(new BorderLayout()); questionNumberLabel = new JLabel("", JLabel.CENTER); questionNumberLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); @@ -212,7 +194,6 @@ public class ExamView extends JPanel { add(headerPanel, BorderLayout.NORTH); - // Question Panel JPanel questionPanel = new JPanel(new BorderLayout(10, 10)); questionPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); @@ -220,7 +201,6 @@ public class ExamView extends JPanel { questionContentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18)); questionPanel.add(questionContentLabel, BorderLayout.NORTH); - // Options Panel - 修复选项状态管理 JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 10, 10)); optionsGroup = new ButtonGroup(); optionButtons = new JRadioButton[4]; @@ -228,7 +208,6 @@ public class ExamView extends JPanel { for (int i = 0; i < 4; i++) { optionButtons[i] = new JRadioButton(); optionButtons[i].setFont(new Font("微软雅黑", Font.PLAIN, 16)); - // 添加选项变化监听器,实时保存答案 final int index = i; optionButtons[i].addActionListener(e -> { if (optionButtons[index].isSelected()) { @@ -242,7 +221,6 @@ public class ExamView extends JPanel { questionPanel.add(optionsPanel, BorderLayout.CENTER); add(questionPanel, BorderLayout.CENTER); - // Navigation Panel JPanel navPanel = new JPanel(new FlowLayout()); previousButton = new JButton("上一题"); @@ -257,7 +235,6 @@ public class ExamView extends JPanel { submitButton.addActionListener(e -> handleSubmit()); navPanel.add(submitButton); - // 添加返回按钮 JButton backButton = new JButton("返回"); backButton.addActionListener(e -> { int result = JOptionPane.showConfirmDialog(ExamView.this, @@ -281,14 +258,11 @@ public class ExamView extends JPanel { private void loadQuestion() { Question currentQuestion = examController.getCurrentQuestion(); if (currentQuestion != null) { - // 更新题号 questionNumberLabel.setText("第 " + examController.getCurrentQuestionNumber() + " 题 / 共 " + examController.getTotalQuestions() + " 题"); - // 更新题目内容 questionContentLabel.setText("
" + currentQuestion.getContent() + "
"); - // 更新选项 - 关键修复:先清空所有选择 optionsGroup.clearSelection(); String[] options = currentQuestion.getOptions(); @@ -297,12 +271,11 @@ public class ExamView extends JPanel { optionButtons[i].setText((char)('A' + i) + ". " + options[i]); optionButtons[i].setVisible(true); } else { - optionButtons[i].setText(""); // 清空多余的选项 + optionButtons[i].setText(""); optionButtons[i].setVisible(false); } } - // 如果有用户答案,选中对应的选项 String userAnswer = currentQuestion.getUserAnswer(); if (userAnswer != null && !userAnswer.trim().isEmpty()) { for (int i = 0; i < 4; i++) { @@ -313,7 +286,6 @@ public class ExamView extends JPanel { } } } else { - // 如果没有题目,显示错误信息 questionContentLabel.setText("
题目加载失败,请返回重试
"); } @@ -321,21 +293,18 @@ public class ExamView extends JPanel { } private void handlePrevious() { - // 保存当前答案 saveCurrentAnswer(); examController.previousQuestion(); loadQuestion(); } private void handleNext() { - // 保存当前答案 saveCurrentAnswer(); examController.nextQuestion(); loadQuestion(); } private void handleSubmit() { - // 保存当前答案 saveCurrentAnswer(); int result = JOptionPane.showConfirmDialog(this, @@ -344,13 +313,11 @@ public class ExamView extends JPanel { JOptionPane.YES_NO_OPTION); if (result == JOptionPane.YES_OPTION) { - // 保存试卷到文件 boolean saveSuccess = examController.saveCurrentPaper(); int score = examController.calculateScore(); int totalQuestions = examController.getTotalQuestions(); - // 显示保存结果 if (saveSuccess) { JOptionPane.showMessageDialog(this, "试卷已保存到您的文件夹中!", @@ -371,7 +338,6 @@ public class ExamView extends JPanel { for (int i = 0; i < 4; i++) { if (optionButtons[i].isSelected() && optionButtons[i].isVisible()) { try { - // 提取答案内容(去掉"A. "等前缀) String fullText = optionButtons[i].getText(); String answer = fullText.substring(fullText.indexOf(". ") + 2); examController.submitAnswer(answer); @@ -390,7 +356,7 @@ public class ExamView extends JPanel { previousButton.setEnabled(hasPrevious); nextButton.setEnabled(hasNext); - submitButton.setEnabled(!hasNext); // 只有最后一题才能提交 + submitButton.setEnabled(!hasNext); System.out.println("导航按钮状态 - 上一题: " + hasPrevious + ", 下一题: " + hasNext + ", 提交: " + !hasNext); } diff --git a/src/com/mathlearning/view/LevelSelectionView.java b/src/com/mathlearning/view/LevelSelectionView.java index 1bd9370..c536607 100644 --- a/src/com/mathlearning/view/LevelSelectionView.java +++ b/src/com/mathlearning/view/LevelSelectionView.java @@ -28,13 +28,11 @@ public class LevelSelectionView extends JPanel { gbc.insets = new Insets(15, 15, 15, 15); gbc.fill = GridBagConstraints.HORIZONTAL; - // Title JLabel titleLabel = new JLabel("选择学习阶段", JLabel.CENTER); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; add(titleLabel, gbc); - // User info gbc.gridy = 1; String userInfo = "当前用户: " + userIdentifier; if (!userIdentifier.equals(email)) { @@ -44,7 +42,6 @@ public class LevelSelectionView extends JPanel { userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); add(userLabel, gbc); - // Primary School Button gbc.gridwidth = 1; gbc.gridx = 0; gbc.gridy = 2; primaryButton = new JButton("小学"); @@ -52,21 +49,18 @@ public class LevelSelectionView extends JPanel { primaryButton.addActionListener(e -> navigationController.showQuestionCountView("小学")); add(primaryButton, gbc); - // Middle School Button gbc.gridx = 1; gbc.gridy = 2; middleButton = new JButton("初中"); middleButton.setFont(new Font("微软雅黑", Font.PLAIN, 18)); middleButton.addActionListener(e -> navigationController.showQuestionCountView("初中")); add(middleButton, gbc); - // High School Button gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; highButton = new JButton("高中"); highButton.setFont(new Font("微软雅黑", Font.PLAIN, 18)); highButton.addActionListener(e -> navigationController.showQuestionCountView("高中")); add(highButton, gbc); - // Logout Button gbc.gridy = 4; logoutButton = new JButton("退出登录"); logoutButton.addActionListener(e -> navigationController.showLoginView()); diff --git a/src/com/mathlearning/view/LoginView.java b/src/com/mathlearning/view/LoginView.java index d00b287..7ea09af 100644 --- a/src/com/mathlearning/view/LoginView.java +++ b/src/com/mathlearning/view/LoginView.java @@ -10,7 +10,7 @@ public class LoginView extends JPanel { private NavigationController navigationController; private AuthController authController; - private JTextField identifierField; // 改为通用标识符字段 + private JTextField identifierField; private JPasswordField passwordField; private JButton loginButton; private JButton registerButton; @@ -29,13 +29,11 @@ public class LoginView extends JPanel { gbc.insets = new Insets(10, 10, 10, 10); gbc.fill = GridBagConstraints.HORIZONTAL; - // Title JLabel titleLabel = new JLabel("数学学习软件", JLabel.CENTER); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; add(titleLabel, gbc); - // Identifier (用户名或邮箱) gbc.gridwidth = 1; gbc.gridx = 0; gbc.gridy = 1; identifierLabel = new JLabel("用户名/邮箱:"); @@ -45,7 +43,6 @@ public class LoginView extends JPanel { identifierField = new JTextField(20); add(identifierField, gbc); - // Password gbc.gridx = 0; gbc.gridy = 2; add(new JLabel("密码:"), gbc); @@ -53,25 +50,21 @@ public class LoginView extends JPanel { passwordField = new JPasswordField(20); add(passwordField, gbc); - // Login Button gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2; loginButton = new JButton("登录"); loginButton.addActionListener(e -> handleLogin()); add(loginButton, gbc); - // Register Button gbc.gridy = 4; registerButton = new JButton("注册新账号"); registerButton.addActionListener(e -> navigationController.showRegisterView()); add(registerButton, gbc); - // Change Password Button gbc.gridy = 5; changePasswordButton = new JButton("修改密码"); changePasswordButton.addActionListener(e -> handleChangePassword()); add(changePasswordButton, gbc); - // 提示信息 gbc.gridy = 6; JLabel hintLabel = new JLabel("提示: 可以使用用户名或邮箱登录", JLabel.CENTER); hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); @@ -89,7 +82,6 @@ public class LoginView extends JPanel { } if (authController.login(identifier, password)) { - // 获取用户信息用于显示 String email = authController.getUserEmail(identifier); navigationController.showLevelSelectionView(identifier, email); } else { @@ -104,7 +96,6 @@ public class LoginView extends JPanel { return; } - // 在实际应用中,这里应该验证用户身份 navigationController.showChangePasswordView(identifier); } } \ No newline at end of file diff --git a/src/com/mathlearning/view/QuestionCountView.java b/src/com/mathlearning/view/QuestionCountView.java index 7ca082c..a5448d5 100644 --- a/src/com/mathlearning/view/QuestionCountView.java +++ b/src/com/mathlearning/view/QuestionCountView.java @@ -29,13 +29,11 @@ public class QuestionCountView extends JPanel { gbc.insets = new Insets(10, 10, 10, 10); gbc.fill = GridBagConstraints.HORIZONTAL; - // Title JLabel titleLabel = new JLabel(level + "数学题目生成", JLabel.CENTER); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; add(titleLabel, gbc); - // User info gbc.gridy = 1; String userInfo = "当前用户: " + userIdentifier; if (!userIdentifier.equals(email)) { @@ -45,24 +43,20 @@ public class QuestionCountView extends JPanel { userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); add(userLabel, gbc); - // Instruction gbc.gridy = 2; JLabel instructionLabel = new JLabel("请输入要生成的题目数量 (10-30):", JLabel.CENTER); add(instructionLabel, gbc); - // Count Input gbc.gridy = 3; countField = new JTextField(10); countField.setHorizontalAlignment(JTextField.CENTER); add(countField, gbc); - // Submit Button gbc.gridy = 4; submitButton = new JButton("生成试卷"); submitButton.addActionListener(e -> handleSubmit()); add(submitButton, gbc); - // Back Button gbc.gridy = 5; backButton = new JButton("返回"); backButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email)); diff --git a/src/com/mathlearning/view/RegisterView.java b/src/com/mathlearning/view/RegisterView.java index 2039a22..8424831 100644 --- a/src/com/mathlearning/view/RegisterView.java +++ b/src/com/mathlearning/view/RegisterView.java @@ -33,13 +33,11 @@ public class RegisterView extends JPanel { gbc.insets = new Insets(10, 10, 10, 10); gbc.fill = GridBagConstraints.HORIZONTAL; - // Title JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; add(titleLabel, gbc); - // Username gbc.gridwidth = 1; gbc.gridx = 0; gbc.gridy = 1; add(new JLabel("用户名:"), gbc); @@ -48,7 +46,6 @@ public class RegisterView extends JPanel { usernameField = new JTextField(20); add(usernameField, gbc); - // Email gbc.gridx = 0; gbc.gridy = 2; add(new JLabel("邮箱地址:"), gbc); @@ -56,7 +53,6 @@ public class RegisterView extends JPanel { emailField = new JTextField(20); add(emailField, gbc); - // Verification Code gbc.gridx = 0; gbc.gridy = 3; add(new JLabel("验证码:"), gbc); @@ -71,7 +67,6 @@ public class RegisterView extends JPanel { add(codePanel, gbc); - // Requirements gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 2; JLabel usernameReqLabel = new JLabel(UsernameValidator.getRequirements(), JLabel.CENTER); usernameReqLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); @@ -84,19 +79,16 @@ public class RegisterView extends JPanel { emailReqLabel.setForeground(Color.GRAY); add(emailReqLabel, gbc); - // Status label gbc.gridy = 6; statusLabel = new JLabel("", JLabel.CENTER); statusLabel.setForeground(Color.BLUE); add(statusLabel, gbc); - // Verify Button gbc.gridy = 7; verifyButton = new JButton("验证并继续"); verifyButton.addActionListener(e -> handleVerify()); add(verifyButton, gbc); - // Back Button gbc.gridy = 8; backButton = new JButton("返回登录"); backButton.addActionListener(e -> navigationController.showLoginView()); @@ -107,7 +99,6 @@ public class RegisterView extends JPanel { String email = emailField.getText().trim(); String username = usernameField.getText().trim(); - // 验证输入 if (username.isEmpty() || email.isEmpty()) { showError("请输入用户名和邮箱地址"); return; @@ -123,7 +114,6 @@ public class RegisterView extends JPanel { return; } - // 检查邮箱和用户名是否可用 if (!authController.isEmailAvailable(email)) { showError("该邮箱已被注册"); return; @@ -134,13 +124,11 @@ public class RegisterView extends JPanel { return; } - // 禁用按钮,显示发送中状态 sendCodeButton.setEnabled(false); sendCodeButton.setText("发送中..."); statusLabel.setText("正在发送验证码,请稍候..."); statusLabel.setForeground(Color.BLUE); - // 在后台线程中执行发送验证码 SwingWorker worker = new SwingWorker() { @Override protected String doInBackground() throws Exception { @@ -162,7 +150,6 @@ public class RegisterView extends JPanel { } catch (Exception e) { showError("发送验证码时发生错误: " + e.getMessage()); } finally { - // 恢复按钮状态 sendCodeButton.setEnabled(true); sendCodeButton.setText("发送验证码"); } @@ -182,13 +169,10 @@ public class RegisterView extends JPanel { return; } - // 验证验证码 if (currentVerificationCode != null && code.equals(currentVerificationCode)) { - // 验证码正确,创建未注册用户 boolean success = authController.createUnregisteredUser(email, username, currentVerificationCode); if (success) { showSuccess("验证成功!"); - // 跳转到设置密码页面 navigationController.showSetPasswordView(email, currentVerificationCode); } else { showError("创建用户失败"); diff --git a/src/com/mathlearning/view/ScoreView.java b/src/com/mathlearning/view/ScoreView.java index 6e17a98..558decc 100644 --- a/src/com/mathlearning/view/ScoreView.java +++ b/src/com/mathlearning/view/ScoreView.java @@ -35,13 +35,11 @@ public class ScoreView extends JPanel { gbc.insets = new Insets(15, 15, 15, 15); gbc.fill = GridBagConstraints.HORIZONTAL; - // Title JLabel titleLabel = new JLabel("考试结果", JLabel.CENTER); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; add(titleLabel, gbc); - // User info gbc.gridy = 1; String userInfo = "用户: " + userIdentifier; if (!userIdentifier.equals(email)) { @@ -51,45 +49,38 @@ public class ScoreView extends JPanel { userLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); add(userLabel, gbc); - // Level info gbc.gridy = 2; JLabel levelLabel = new JLabel("级别: " + level, JLabel.CENTER); levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); add(levelLabel, gbc); - // Score Display gbc.gridy = 3; scoreLabel = new JLabel("得分: " + score + " / 100", JLabel.CENTER); scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); scoreLabel.setForeground(getScoreColor()); add(scoreLabel, gbc); - // Comment gbc.gridy = 4; commentLabel = new JLabel(getScoreComment(), JLabel.CENTER); commentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); add(commentLabel, gbc); - // Stats gbc.gridy = 5; int correctCount = (score * totalQuestions) / 100; JLabel statsLabel = new JLabel("正确题数: " + correctCount + " / " + totalQuestions, JLabel.CENTER); statsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); add(statsLabel, gbc); - // Retry Button gbc.gridy = 6; retryButton = new JButton("重新答题"); retryButton.addActionListener(e -> navigationController.showExamView(level, totalQuestions)); add(retryButton, gbc); - // New Exam Button gbc.gridy = 7; newExamButton = new JButton("新的测试"); newExamButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email)); add(newExamButton, gbc); - // Logout Button gbc.gridy = 8; logoutButton = new JButton("退出登录"); logoutButton.addActionListener(e -> navigationController.showLoginView()); diff --git a/src/com/mathlearning/view/SetPasswordView.java b/src/com/mathlearning/view/SetPasswordView.java index 28fa1a0..7a6e81d 100644 --- a/src/com/mathlearning/view/SetPasswordView.java +++ b/src/com/mathlearning/view/SetPasswordView.java @@ -30,25 +30,21 @@ public class SetPasswordView extends JPanel { gbc.insets = new Insets(10, 10, 10, 10); gbc.fill = GridBagConstraints.HORIZONTAL; - // Title JLabel titleLabel = new JLabel("设置密码", JLabel.CENTER); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; add(titleLabel, gbc); - // User info gbc.gridy = 1; JLabel userInfoLabel = new JLabel("邮箱: " + email, JLabel.CENTER); userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); add(userInfoLabel, gbc); - // Password requirements gbc.gridy = 2; JLabel requirementsLabel = new JLabel("密码要求: 6-10位,包含大小写字母和数字", JLabel.CENTER); requirementsLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); add(requirementsLabel, gbc); - // Password gbc.gridwidth = 1; gbc.gridx = 0; gbc.gridy = 3; add(new JLabel("密码:"), gbc); @@ -57,7 +53,6 @@ public class SetPasswordView extends JPanel { passwordField = new JPasswordField(20); add(passwordField, gbc); - // Confirm Password gbc.gridx = 0; gbc.gridy = 4; add(new JLabel("确认密码:"), gbc); @@ -65,13 +60,11 @@ public class SetPasswordView extends JPanel { confirmPasswordField = new JPasswordField(20); add(confirmPasswordField, gbc); - // Submit Button gbc.gridx = 0; gbc.gridy = 5; gbc.gridwidth = 2; submitButton = new JButton("设置密码"); submitButton.addActionListener(e -> handleSetPassword()); add(submitButton, gbc); - // Back Button gbc.gridy = 6; backButton = new JButton("返回"); backButton.addActionListener(e -> navigationController.showLoginView()); @@ -91,7 +84,6 @@ public class SetPasswordView extends JPanel { if (authController.setPassword(email, password, confirmPassword)) { JOptionPane.showMessageDialog(this, "密码设置成功!", "成功", JOptionPane.INFORMATION_MESSAGE); - // 登录成功后使用邮箱作为标识符 navigationController.showLevelSelectionView(email, email); } else { JOptionPane.showMessageDialog(this, "密码设置失败,请检查输入", "错误", JOptionPane.ERROR_MESSAGE); -- 2.34.1