Compare commits
8 Commits
hewendi_br
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
3dc827588a | 4 months ago |
|
|
afddb9a116 | 4 months ago |
|
|
e042bc6c5d | 4 months ago |
|
|
ad5053f355 | 4 months ago |
|
|
7a3c2c49fc | 4 months ago |
|
|
c681ad121f | 4 months ago |
|
|
5f2029834b | 4 months ago |
|
|
adf512bee2 | 4 months ago |
@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: com.mathlearning.Main
|
||||
|
||||
@ -1,275 +0,0 @@
|
||||
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("500x550") # 增加了窗口高度以适应新页面
|
||||
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 = {}
|
||||
# 添加了新的 ForgotPasswordPage
|
||||
for F in (LoginPage, RegisterPage, ForgotPasswordPage, 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)
|
||||
|
||||
# 登录和注册按钮的容器
|
||||
bottom_frame = ctk.CTkFrame(self, fg_color="transparent")
|
||||
bottom_frame.pack(pady=5)
|
||||
ctk.CTkButton(bottom_frame, text="没有账户?去注册", fg_color="transparent", command=lambda: controller.show_frame("RegisterPage")).pack(side="left", padx=5)
|
||||
# 新增忘记密码按钮
|
||||
ctk.CTkButton(bottom_frame, text="忘记密码?", fg_color="transparent", command=lambda: controller.show_frame("ForgotPasswordPage")).pack(side="left", padx=5)
|
||||
|
||||
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 ForgotPasswordPage(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.reset_password).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 not 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 reset_password(self):
|
||||
email, user_code, new_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 new_password != confirm_password:
|
||||
self.info_label.configure(text="两次输入的密码不一致", text_color="red")
|
||||
return
|
||||
if not self.controller.user_manager.validate_password_format(new_password):
|
||||
self.info_label.configure(text="新密码格式不符合要求", text_color="red")
|
||||
return
|
||||
|
||||
success, msg = self.controller.user_manager.reset_password(email, new_password)
|
||||
self.info_label.configure(text=msg, text_color="green" if success else "red")
|
||||
if success:
|
||||
self.code = ""
|
||||
self.after(2000, lambda: self.controller.show_frame("LoginPage"))
|
||||
|
||||
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="请输入题目数量 (10-30):", title="设置题目数量")
|
||||
num_str = dialog.get_input()
|
||||
if num_str:
|
||||
try:
|
||||
num = int(num_str)
|
||||
# 修改了题目数量验证逻辑
|
||||
if 10 <= num <= 30:
|
||||
self.controller.frames["QuizPage"].setup_quiz(level, num)
|
||||
self.controller.show_frame("QuizPage")
|
||||
else:
|
||||
# 可以添加一个提示,告知用户输入范围错误
|
||||
pass
|
||||
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}%")
|
||||
@ -0,0 +1,14 @@
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,225 @@
|
||||
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<Question> 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<Question> basicQuestions = generateBasicQuestions(questionCount);
|
||||
for (Question question : basicQuestions) {
|
||||
currentPaper.addQuestion(question);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<Question> generateQuestionsWithTimeout(QuestionGenerator generator, int count, long timeoutMs) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
List<Question> questions = new ArrayList<>();
|
||||
|
||||
try {
|
||||
List<Question> 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<Question> basicQuestions = generateBasicQuestions(needed);
|
||||
questions.addAll(basicQuestions);
|
||||
}
|
||||
|
||||
return questions;
|
||||
}
|
||||
|
||||
|
||||
private List<Question> generateBasicQuestions(int count) {
|
||||
System.out.println("生成 " + count + " 道基础题目");
|
||||
List<Question> 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<String> 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<String> optionList = new ArrayList<>(options);
|
||||
Collections.shuffle(optionList);
|
||||
String optionsStr = String.join(",", optionList);
|
||||
|
||||
basicQuestions.add(new Question(expression + " = ?", optionsStr, String.valueOf(answer)));
|
||||
}
|
||||
|
||||
return basicQuestions;
|
||||
}
|
||||
|
||||
|
||||
public boolean saveCurrentPaper() {
|
||||
if (currentPaper == null || currentUser == null) {
|
||||
System.err.println("无法保存试卷: 试卷或用户信息为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
return ExamFileUtil.savePaperToFile(currentUser, currentPaper);
|
||||
}
|
||||
|
||||
private int checkForDuplicates(List<Question> questions) {
|
||||
Set<String> 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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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<Question> 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<Question> getQuestions() { return questions; }
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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 + "}";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
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<String, User> usersByEmail;
|
||||
private Map<String, User> 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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
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");
|
||||
properties.setProperty("email.smtp.port", "465");
|
||||
properties.setProperty("email.smtp.ssl", "true");
|
||||
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());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
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 {
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
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<String, User> loadUsersByEmail() {
|
||||
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_EMAIL_FILE))) {
|
||||
return (Map<String, User>) ois.readObject();
|
||||
} catch (FileNotFoundException e) {
|
||||
return new HashMap<>();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
System.err.println("加载用户数据失败: " + e.getMessage());
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Map<String, User> loadUsersByUsername() {
|
||||
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(USER_DATA_USERNAME_FILE))) {
|
||||
return (Map<String, User>) ois.readObject();
|
||||
} catch (FileNotFoundException e) {
|
||||
return new HashMap<>();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
System.err.println("加载用户数据失败: " + e.getMessage());
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveUsers(Map<String, User> usersByEmail, Map<String, User> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,363 @@
|
||||
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;
|
||||
|
||||
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<Boolean, String> worker = new SwingWorker<Boolean, String>() {
|
||||
@Override
|
||||
protected Boolean doInBackground() throws Exception {
|
||||
publish("正在初始化生成器...");
|
||||
Thread.sleep(100);
|
||||
|
||||
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<String> 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));
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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("<html><div style='text-align: center;'>" + currentQuestion.getContent() + "</div></html>");
|
||||
|
||||
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("<html><div style='text-align: center; color: red;'>题目加载失败,请返回重试</div></html>");
|
||||
}
|
||||
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridx = 1; gbc.gridy = 2;
|
||||
middleButton = new JButton("初中");
|
||||
middleButton.setFont(new Font("微软雅黑", Font.PLAIN, 18));
|
||||
middleButton.addActionListener(e -> navigationController.showQuestionCountView("初中"));
|
||||
add(middleButton, gbc);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridy = 4;
|
||||
logoutButton = new JButton("退出登录");
|
||||
logoutButton.addActionListener(e -> navigationController.showLoginView());
|
||||
add(logoutButton, gbc);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridx = 0; gbc.gridy = 2;
|
||||
add(new JLabel("密码:"), gbc);
|
||||
|
||||
gbc.gridx = 1; gbc.gridy = 2;
|
||||
passwordField = new JPasswordField(20);
|
||||
add(passwordField, gbc);
|
||||
|
||||
gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2;
|
||||
loginButton = new JButton("登录");
|
||||
loginButton.addActionListener(e -> handleLogin());
|
||||
add(loginButton, gbc);
|
||||
|
||||
gbc.gridy = 4;
|
||||
registerButton = new JButton("注册新账号");
|
||||
registerButton.addActionListener(e -> navigationController.showRegisterView());
|
||||
add(registerButton, gbc);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridy = 2;
|
||||
JLabel instructionLabel = new JLabel("请输入要生成的题目数量 (10-30):", JLabel.CENTER);
|
||||
add(instructionLabel, gbc);
|
||||
|
||||
gbc.gridy = 3;
|
||||
countField = new JTextField(10);
|
||||
countField.setHorizontalAlignment(JTextField.CENTER);
|
||||
add(countField, gbc);
|
||||
|
||||
gbc.gridy = 4;
|
||||
submitButton = new JButton("生成试卷");
|
||||
submitButton.addActionListener(e -> handleSubmit());
|
||||
add(submitButton, gbc);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,194 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridx = 0; gbc.gridy = 2;
|
||||
add(new JLabel("邮箱地址:"), gbc);
|
||||
|
||||
gbc.gridx = 1; gbc.gridy = 2;
|
||||
emailField = new JTextField(20);
|
||||
add(emailField, gbc);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridy = 6;
|
||||
statusLabel = new JLabel("", JLabel.CENTER);
|
||||
statusLabel.setForeground(Color.BLUE);
|
||||
add(statusLabel, gbc);
|
||||
|
||||
gbc.gridy = 7;
|
||||
verifyButton = new JButton("验证并继续");
|
||||
verifyButton.addActionListener(e -> handleVerify());
|
||||
add(verifyButton, gbc);
|
||||
|
||||
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<String, Void> worker = new SwingWorker<String, Void>() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridy = 2;
|
||||
JLabel levelLabel = new JLabel("级别: " + level, JLabel.CENTER);
|
||||
levelLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
||||
add(levelLabel, gbc);
|
||||
|
||||
gbc.gridy = 3;
|
||||
scoreLabel = new JLabel("得分: " + score + " / 100", JLabel.CENTER);
|
||||
scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 20));
|
||||
scoreLabel.setForeground(getScoreColor());
|
||||
add(scoreLabel, gbc);
|
||||
|
||||
gbc.gridy = 4;
|
||||
commentLabel = new JLabel(getScoreComment(), JLabel.CENTER);
|
||||
commentLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
||||
add(commentLabel, gbc);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridy = 6;
|
||||
retryButton = new JButton("重新答题");
|
||||
retryButton.addActionListener(e -> navigationController.showExamView(level, totalQuestions));
|
||||
add(retryButton, gbc);
|
||||
|
||||
gbc.gridy = 7;
|
||||
newExamButton = new JButton("新的测试");
|
||||
newExamButton.addActionListener(e -> navigationController.showLevelSelectionView(userIdentifier, email));
|
||||
add(newExamButton, gbc);
|
||||
|
||||
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 "不及格!需要认真学习!";
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@ -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
|
||||
@ -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
|
||||
@ -1,4 +0,0 @@
|
||||
from app import MathApp
|
||||
if __name__ == "__main__":
|
||||
app = MathApp()
|
||||
app.mainloop()
|
||||
Binary file not shown.
@ -1,6 +0,0 @@
|
||||
{
|
||||
"3589843135@qq.com": {
|
||||
"password_hash": "1ee2d6f5cd5c2c82cf93ac817a0fdbaf78a632343a2d12d4d04cb072b07c5c98",
|
||||
"salt": "22399dc52bc6b25b6b4357f0b9260189"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue