wangjiawei_branch
parent
04d4f2745c
commit
adf512bee2
@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: com.mathlearning.Main
|
||||
|
||||
@ -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}%")
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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<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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前试卷到文件
|
||||
* @return 是否保存成功
|
||||
*/
|
||||
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,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;
|
||||
}
|
||||
}
|
||||
@ -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,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 + "}";
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
@ -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,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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<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,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 "不及格!需要认真学习!";
|
||||
}
|
||||
}
|
||||
@ -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,5 +0,0 @@
|
||||
from app import MathApp
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = MathApp()
|
||||
app.mainloop()
|
||||
@ -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("无效的等级")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue