diff --git a/README.md b/README.md
index 98d86a0..0e20bb8 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,8 @@ mvn exec:java -Dexec.mainClass="com.example.mathsystemtogether.HelloApplication"
## 👤 用户账号
+### 预设测试账号
+
系统预设了9个测试账号:
| 用户名 | 密码 | 难度级别 | 题目类型 |
@@ -45,12 +47,54 @@ mvn exec:java -Dexec.mainClass="com.example.mathsystemtogether.HelloApplication"
| 王五2 | 123 | 高中 | 三角函数运算 |
| 王五3 | 123 | 高中 | 三角函数运算 |
+### 新用户注册
+
+系统支持新用户注册功能:
+
+1. **点击注册按钮**:在登录界面点击"📝 注册"按钮
+2. **填写注册信息**:
+ - 用户名(至少3个字符)
+ - 密码(至少6个字符)
+ - 确认密码
+ - 邮箱地址
+ - 难度级别选择
+3. **邮箱验证**:
+ - 点击"📤 发送验证码"获取验证码
+ - 输入收到的6位数字验证码
+ - 验证码有效期为5分钟
+4. **完成注册**:点击"🚀 注册"完成账户创建
+5. **自动跳转**:注册成功后自动返回登录界面
+
+**注意**:
+- 用户名不能重复
+- 邮箱地址格式必须正确
+- 验证码通过真实邮件发送
+- 用户数据保存在本地 `user_data.txt` 文件中
+
+### 邮件服务配置
+
+系统支持真实的邮箱验证码功能,已配置QQ邮箱:
+
+- **邮箱地址**:1961004835@qq.com
+- **SMTP服务器**:smtp.qq.com:587
+- **配置文件**:`src/main/resources/mail.properties`
+
+如需修改邮箱配置,请编辑 `mail.properties` 文件。
+
## 🎮 使用流程
-### 1. 用户登录
+### 1. 用户登录或注册
+
+**登录现有账户:**
- 输入用户名(如:张三1)
- 输入密码(123)
-- 点击"登录"按钮
+- 点击"🚀 登录"按钮
+
+**注册新账户:**
+- 点击"📝 注册"按钮
+- 填写注册信息(用户名、密码、邮箱、难度级别)
+- 获取并输入邮箱验证码
+- 点击"🚀 注册"完成账户创建
### 2. 设置考试
- 选择难度级别(小学/初中/高中)
@@ -125,10 +169,16 @@ MathSystemTogether/
├── src/main/java/com/example/mathsystemtogether/
│ ├── HelloApplication.java # 主应用程序入口
│ ├── ExamController.java # 考试系统控制器
+│ ├── RegisterController.java # 注册功能控制器
+│ ├── EmailService.java # 邮件服务类
│ ├── Question.java # 选择题数据模型
│ └── ChoiceQuestionGenerator.java # 选择题生成器
├── src/main/resources/com/example/mathsystemtogether/
-│ └── exam-view.fxml # 考试界面布局文件
+│ ├── exam-view.fxml # 考试界面布局文件
+│ └── register-view.fxml # 注册界面布局文件
+├── src/main/resources/
+│ └── mail.properties # 邮件服务配置文件
+├── user_data.txt # 用户数据存储文件
├── papers/ # 题目文件存储目录
│ ├── 张三1/ # 用户文件夹
│ ├── 李四1/
@@ -140,6 +190,12 @@ MathSystemTogether/
## 🔧 核心功能
+### 用户管理功能
+- **用户注册**:支持新用户注册,包含邮箱验证
+- **用户登录**:支持预设账号和注册账号登录
+- **数据存储**:用户信息保存在本地文件中
+- **数据验证**:用户名唯一性、邮箱格式验证
+
### 题目生成算法
- **小学**:基础四则运算(+、-、×、÷)
- **初中**:平方运算(²)、开方运算(√)
@@ -156,6 +212,14 @@ MathSystemTogether/
- 进度实时更新
- 结果自动统计
+### 邮箱验证功能
+- **真实邮件发送**:通过SMTP服务器发送验证码邮件
+- **验证码生成**:6位数字随机验证码
+- **时效控制**:验证码5分钟有效期
+- **防重复发送**:60秒倒计时限制
+- **格式验证**:邮箱地址格式检查
+- **HTML邮件**:美观的HTML格式验证码邮件
+
## 📊 考试结果
系统会显示:
diff --git a/pom.xml b/pom.xml
index 96bff82..7ba6d4d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,16 @@
${junit.version}
test
+
+ com.sun.mail
+ jakarta.mail
+ 2.0.1
+
+
+ jakarta.activation
+ jakarta.activation-api
+ 2.1.3
+
@@ -51,6 +61,14 @@
21
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+ com.example.mathsystemtogether.HelloApplication
+
+
org.openjfx
javafx-maven-plugin
diff --git a/run_gui.bat b/run_gui.bat
index c9ec487..c25cf96 100644
--- a/run_gui.bat
+++ b/run_gui.bat
@@ -7,11 +7,22 @@ echo 系统功能:
echo - 支持小学、初中、高中三个难度级别
echo - 选择题形式答题
echo - 自动评分和结果统计
+echo - 用户注册功能(邮箱验证码)
+echo - 本地用户数据存储
echo.
echo 测试账号:
echo - 小学:张三1/123, 张三2/123, 张三3/123
echo - 初中:李四1/123, 李四2/123, 李四3/123
echo - 高中:王五1/123, 王五2/123, 王五3/123
echo.
+echo 新用户注册:
+echo - 点击"注册"按钮创建新账户
+echo - 使用邮箱获取验证码
+echo - 选择适合的难度级别
+echo.
+echo 邮件服务配置:
+echo - 已配置QQ邮箱:1961004835@qq.com
+echo - 配置文件:src/main/resources/mail.properties
+echo.
mvn exec:java -Dexec.mainClass="com.example.mathsystemtogether.HelloApplication"
pause
diff --git a/src/main/java/com/example/mathsystemtogether/ExamController.java b/src/main/java/com/example/mathsystemtogether/ExamController.java
index 3bd9d97..cb60676 100644
--- a/src/main/java/com/example/mathsystemtogether/ExamController.java
+++ b/src/main/java/com/example/mathsystemtogether/ExamController.java
@@ -1,24 +1,30 @@
package com.example.mathsystemtogether;
import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
-
import java.util.*;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
/**
* 考试系统控制器
*/
public class ExamController {
-
+
// 登录界面控件
@FXML private TextField usernameField;
@FXML private PasswordField passwordField;
@FXML private Button loginButton;
+ @FXML private Button registerButton;
@FXML private Label loginStatusLabel;
-
+
// 考试设置界面控件
@FXML private VBox examSetupPanel;
@FXML private Label welcomeLabel;
@@ -27,54 +33,27 @@ public class ExamController {
@FXML private Button startExamButton;
@FXML private Button logoutButton;
@FXML private Label statusLabel;
-
- // 考试界面控件
- @FXML private VBox examPanel;
- @FXML private Label questionNumberLabel;
- @FXML private Label questionTextLabel;
- @FXML private RadioButton optionA;
- @FXML private RadioButton optionB;
- @FXML private RadioButton optionC;
- @FXML private RadioButton optionD;
- @FXML private ToggleGroup answerGroup;
- @FXML private Button submitButton;
- @FXML private Button nextButton;
- @FXML private Label progressLabel;
- @FXML private Button exitExamButton;
-
- // 结果界面控件
- @FXML private VBox resultPanel;
- @FXML private Label resultTitleLabel;
- @FXML private Label scoreLabel;
- @FXML private Label correctCountLabel;
- @FXML private Label totalCountLabel;
- @FXML private TextArea resultDetailsArea;
- @FXML private Button restartButton;
- @FXML private Button backToLoginButton;
-
+
// 数据成员
private Account currentAccount;
private List examQuestions;
- private int currentQuestionIndex = 0;
- private Map userAnswers = new HashMap<>();
private ChoiceQuestionGenerator questionGenerator;
private final Map userMap = new HashMap<>();
-
+ private static final String USER_DATA_FILE = "user_data.txt";
+
// 常量定义
private static final int MIN_QUESTIONS = 5;
private static final int MAX_QUESTIONS = 20;
-
+
@FXML
public void initialize() {
initAccounts();
+ loadUserDataFromFile();
setupLevelComboBox();
- setupAnswerGroup();
examSetupPanel.setVisible(false);
- examPanel.setVisible(false);
- resultPanel.setVisible(false);
questionCountField.setText("10");
}
-
+
private void initAccounts() {
// 小学三个账号
userMap.put("张三1", new Account("张三1", "123", Level.小学));
@@ -89,32 +68,53 @@ public class ExamController {
userMap.put("王五2", new Account("王五2", "123", Level.高中));
userMap.put("王五3", new Account("王五3", "123", Level.高中));
}
-
+
private void setupLevelComboBox() {
ObservableList levels = FXCollections.observableArrayList("小学", "初中", "高中");
levelComboBox.setItems(levels);
levelComboBox.setValue("小学");
}
-
- private void setupAnswerGroup() {
- answerGroup = new ToggleGroup();
- optionA.setToggleGroup(answerGroup);
- optionB.setToggleGroup(answerGroup);
- optionC.setToggleGroup(answerGroup);
- optionD.setToggleGroup(answerGroup);
+
+ private void loadUserDataFromFile() {
+ try {
+ if (!Files.exists(Paths.get(USER_DATA_FILE))) {
+ return;
+ }
+
+ List lines = Files.readAllLines(Paths.get(USER_DATA_FILE));
+ for (String line : lines) {
+ String[] parts = line.split("\\|");
+ if (parts.length >= 4) {
+ String username = parts[0];
+ String password = parts[1];
+ String levelStr = parts[3];
+
+ Level level;
+ try {
+ level = Level.valueOf(levelStr);
+ } catch (IllegalArgumentException e) {
+ level = Level.小学; // 默认级别
+ }
+
+ userMap.put(username, new Account(username, password, level));
+ }
+ }
+ } catch (IOException e) {
+ // 加载用户数据失败,静默处理
+ }
}
-
+
@FXML
private void handleLogin() {
String username = usernameField.getText().trim();
String password = passwordField.getText();
-
+
if (username.isEmpty() || password.isEmpty()) {
loginStatusLabel.setText("请输入用户名和密码");
loginStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
-
+
Account account = userMap.get(username);
if (account != null && account.password.equals(password)) {
currentAccount = account;
@@ -128,22 +128,36 @@ public class ExamController {
loginStatusLabel.setStyle("-fx-text-fill: red;");
}
}
-
+
+ @FXML
+ private void handleRegister() {
+ try {
+ // 打开注册界面
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("register-view.fxml"));
+ Scene scene = new Scene(loader.load(), 800, 800);
+ Stage registerStage = new Stage();
+ registerStage.setTitle("数学考试系统 - 用户注册");
+ registerStage.setScene(scene);
+ registerStage.setResizable(false);
+ registerStage.show();
+
+ } catch (Exception e) {
+ loginStatusLabel.setText("打开注册界面失败:" + e.getMessage());
+ loginStatusLabel.setStyle("-fx-text-fill: red;");
+ }
+ }
+
@FXML
private void handleLogout() {
currentAccount = null;
examQuestions = null;
- currentQuestionIndex = 0;
- userAnswers.clear();
examSetupPanel.setVisible(false);
- examPanel.setVisible(false);
- resultPanel.setVisible(false);
usernameField.clear();
passwordField.clear();
loginStatusLabel.setText("");
statusLabel.setText("");
}
-
+
@FXML
private void handleStartExam() {
if (currentAccount == null) {
@@ -151,7 +165,7 @@ public class ExamController {
statusLabel.setStyle("-fx-text-fill: red;");
return;
}
-
+
try {
int count = Integer.parseInt(questionCountField.getText());
if (count < MIN_QUESTIONS || count > MAX_QUESTIONS) {
@@ -159,177 +173,71 @@ public class ExamController {
statusLabel.setStyle("-fx-text-fill: red;");
return;
}
-
+
// 获取选择的级别
String selectedLevel = levelComboBox.getValue();
ChoiceQuestionGenerator.Level level = ChoiceQuestionGenerator.Level.valueOf(selectedLevel);
-
+
// 生成考试题目
questionGenerator = new ChoiceQuestionGenerator();
examQuestions = questionGenerator.generateQuestions(level, count);
-
+
if (examQuestions.isEmpty()) {
statusLabel.setText("无法生成题目,请重试");
statusLabel.setStyle("-fx-text-fill: red;");
return;
}
-
+
// 开始考试
startExam();
-
+
} catch (NumberFormatException e) {
statusLabel.setText("请输入有效的数字");
statusLabel.setStyle("-fx-text-fill: red;");
}
}
-
+
private void startExam() {
- currentQuestionIndex = 0;
- userAnswers.clear();
- examSetupPanel.setVisible(false);
- examPanel.setVisible(true);
- resultPanel.setVisible(false);
- displayCurrentQuestion();
- }
-
- private void displayCurrentQuestion() {
- if (currentQuestionIndex >= examQuestions.size()) {
- showResults();
- return;
- }
-
- Question question = examQuestions.get(currentQuestionIndex);
- questionNumberLabel.setText("第 " + (currentQuestionIndex + 1) + " 题 / 共 " + examQuestions.size() + " 题");
- questionTextLabel.setText(question.getQuestionText());
-
- optionA.setText("A. " + question.getOptionA());
- optionB.setText("B. " + question.getOptionB());
- optionC.setText("C. " + question.getOptionC());
- optionD.setText("D. " + question.getOptionD());
-
- // 清除之前的选择
- answerGroup.selectToggle(null);
-
- // 更新按钮状态
- if (currentQuestionIndex == examQuestions.size() - 1) {
- submitButton.setText("提交并完成考试");
- nextButton.setVisible(false);
- } else {
- submitButton.setText("提交答案");
- nextButton.setVisible(true);
- }
-
- progressLabel.setText("进度: " + (currentQuestionIndex + 1) + "/" + examQuestions.size());
- }
-
- @FXML
- private void handleSubmitAnswer() {
- RadioButton selectedOption = (RadioButton) answerGroup.getSelectedToggle();
- if (selectedOption == null) {
- statusLabel.setText("请选择一个答案");
+ try {
+ // 打开专门的考试界面
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("exam-taking-view.fxml"));
+ Scene scene = new Scene(loader.load(), 1000, 950);
+ Stage examStage = new Stage();
+ examStage.setTitle("数学考试 - " + currentAccount.username);
+ examStage.setScene(scene);
+ examStage.setResizable(false);
+
+ // 设置考试数据
+ ExamTakingController examController = loader.getController();
+ examController.setExamData(examQuestions, currentAccount.username, levelComboBox.getValue());
+
+ // 显示考试窗口
+ examStage.show();
+
+ // 隐藏主窗口
+ Stage mainStage = (Stage) startExamButton.getScene().getWindow();
+ mainStage.hide();
+
+ } catch (Exception e) {
+ statusLabel.setText("启动考试失败:" + e.getMessage());
statusLabel.setStyle("-fx-text-fill: red;");
- return;
}
-
- String answer = selectedOption.getText().substring(0, 1); // 获取A、B、C、D
- userAnswers.put(currentQuestionIndex, answer);
-
- if (currentQuestionIndex == examQuestions.size() - 1) {
- // 最后一题,显示结果
- showResults();
- } else {
- // 下一题
- currentQuestionIndex++;
- displayCurrentQuestion();
- }
- }
-
- @FXML
- private void handleNextQuestion() {
- currentQuestionIndex++;
- displayCurrentQuestion();
}
-
- @FXML
- private void handleExitExam() {
- if (showExitConfirmation()) {
- examPanel.setVisible(false);
- examSetupPanel.setVisible(true);
- currentQuestionIndex = 0;
- userAnswers.clear();
- }
- }
-
- private boolean showExitConfirmation() {
- Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
- alert.setTitle("确认退出");
- alert.setHeaderText("确定要退出考试吗?");
- alert.setContentText("退出后当前进度将丢失。");
- return alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK;
- }
-
- private void showResults() {
- examPanel.setVisible(false);
- resultPanel.setVisible(true);
-
- int correctCount = 0;
- StringBuilder resultDetails = new StringBuilder();
-
- for (int i = 0; i < examQuestions.size(); i++) {
- Question question = examQuestions.get(i);
- String userAnswer = userAnswers.get(i);
- boolean isCorrect = question.isCorrect(userAnswer);
-
- if (isCorrect) {
- correctCount++;
- }
-
- resultDetails.append("第").append(i + 1).append("题: ");
- resultDetails.append(question.getQuestionText()).append("\n");
- resultDetails.append("你的答案: ").append(userAnswer != null ? userAnswer : "未作答").append(" ");
- resultDetails.append("正确答案: ").append(question.getCorrectAnswer()).append(" ");
- resultDetails.append(isCorrect ? "✓" : "✗").append("\n\n");
- }
-
- int score = (int) Math.round((double) correctCount / examQuestions.size() * 100);
-
- resultTitleLabel.setText("考试完成!");
- scoreLabel.setText("得分: " + score + "分");
- correctCountLabel.setText("正确: " + correctCount + "题");
- totalCountLabel.setText("总计: " + examQuestions.size() + "题");
- resultDetailsArea.setText(resultDetails.toString());
- }
-
- @FXML
- private void handleRestart() {
- resultPanel.setVisible(false);
- examSetupPanel.setVisible(true);
- currentQuestionIndex = 0;
- userAnswers.clear();
- }
-
- @FXML
- private void handleBackToLogin() {
- resultPanel.setVisible(false);
- examSetupPanel.setVisible(false);
- examPanel.setVisible(false);
- handleLogout();
- }
-
+
// 内部类
static class Account {
final String username;
- final String password;
+ String password;
final Level level;
-
+
Account(String username, String password, Level level) {
this.username = username;
this.password = password;
this.level = level;
}
}
-
+
enum Level {
小学, 初中, 高中
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/mathsystemtogether/ExamTakingController.java b/src/main/java/com/example/mathsystemtogether/ExamTakingController.java
new file mode 100644
index 0000000..be77e36
--- /dev/null
+++ b/src/main/java/com/example/mathsystemtogether/ExamTakingController.java
@@ -0,0 +1,267 @@
+package com.example.mathsystemtogether;
+
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.animation.Timeline;
+import javafx.animation.KeyFrame;
+import javafx.util.Duration;
+
+import java.util.*;
+
+/**
+ * 考试进行界面控制器
+ */
+public class ExamTakingController {
+
+ // 界面控件
+ @FXML private Label examInfoLabel;
+ @FXML private Label questionNumberLabel;
+ @FXML private Label timeLabel;
+ @FXML private Label questionTextLabel;
+ @FXML private RadioButton optionA;
+ @FXML private RadioButton optionB;
+ @FXML private RadioButton optionC;
+ @FXML private RadioButton optionD;
+ @FXML private ToggleGroup answerGroup;
+ @FXML private Button submitButton;
+ @FXML private Button nextButton;
+ @FXML private Button exitExamButton;
+ @FXML private ProgressBar progressBar;
+ @FXML private Label progressLabel;
+
+ // 数据成员
+ private List examQuestions;
+ private int currentQuestionIndex = 0;
+ private Map userAnswers = new HashMap<>();
+ private ChoiceQuestionGenerator questionGenerator;
+ private String username;
+ private String level;
+ private int totalQuestions;
+ private Timeline timer;
+ private int remainingSeconds;
+
+ @FXML
+ public void initialize() {
+ setupAnswerGroup();
+ setupTimer();
+ }
+
+ private void setupAnswerGroup() {
+ answerGroup = new ToggleGroup();
+ optionA.setToggleGroup(answerGroup);
+ optionB.setToggleGroup(answerGroup);
+ optionC.setToggleGroup(answerGroup);
+ optionD.setToggleGroup(answerGroup);
+ }
+
+ private void setupTimer() {
+ // 设置30分钟倒计时
+ remainingSeconds = 30 * 60; // 30分钟 = 1800秒
+ timer = new Timeline(new KeyFrame(Duration.seconds(1), e -> updateTimer()));
+ timer.setCycleCount(Timeline.INDEFINITE);
+ timer.play();
+ }
+
+ private void updateTimer() {
+ remainingSeconds--;
+ if (remainingSeconds <= 0) {
+ timer.stop();
+ handleTimeUp();
+ return;
+ }
+
+ int minutes = remainingSeconds / 60;
+ int seconds = remainingSeconds % 60;
+ timeLabel.setText(String.format("⏰ 剩余时间: %02d:%02d", minutes, seconds));
+ }
+
+ private void handleTimeUp() {
+ Alert alert = new Alert(Alert.AlertType.WARNING);
+ alert.setTitle("时间到");
+ alert.setHeaderText("考试时间已到!");
+ alert.setContentText("系统将自动提交您的答案。");
+ alert.showAndWait();
+ showResults();
+ }
+
+ public void setExamData(List questions, String username, String level) {
+ this.examQuestions = questions;
+ this.username = username;
+ this.level = level;
+ this.totalQuestions = questions.size();
+
+ examInfoLabel.setText(String.format("👤 %s | 📚 %s | 📝 %d题", username, level, totalQuestions));
+ displayCurrentQuestion();
+ updateProgress();
+ }
+
+ private void displayCurrentQuestion() {
+ if (currentQuestionIndex >= examQuestions.size()) {
+ showResults();
+ return;
+ }
+
+ Question question = examQuestions.get(currentQuestionIndex);
+ questionNumberLabel.setText(String.format("第 %d 题 / 共 %d 题",
+ currentQuestionIndex + 1, examQuestions.size()));
+ questionTextLabel.setText(question.getQuestionText());
+
+ optionA.setText("A. " + question.getOptionA());
+ optionB.setText("B. " + question.getOptionB());
+ optionC.setText("C. " + question.getOptionC());
+ optionD.setText("D. " + question.getOptionD());
+
+ // 清除之前的选择
+ answerGroup.selectToggle(null);
+
+ // 更新按钮状态
+ if (currentQuestionIndex == examQuestions.size() - 1) {
+ submitButton.setText("✅ 提交并完成考试");
+ nextButton.setVisible(false);
+ } else {
+ submitButton.setText("✅ 提交答案");
+ nextButton.setVisible(true);
+ }
+
+ updateProgress();
+ }
+
+ private void updateProgress() {
+ double progress = (double) currentQuestionIndex / examQuestions.size();
+ progressBar.setProgress(progress);
+ progressLabel.setText(String.format("进度: %d/%d (%.1f%%)",
+ currentQuestionIndex + 1, examQuestions.size(), progress * 100));
+ }
+
+ @FXML
+ private void handleSubmitAnswer() {
+ RadioButton selectedOption = (RadioButton) answerGroup.getSelectedToggle();
+ if (selectedOption == null) {
+ showAlert("请选择一个答案", "请先选择答案再提交");
+ return;
+ }
+
+ String answer = getAnswerValue(selectedOption.getText().substring(0, 1)); // 获取答案
+ userAnswers.put(currentQuestionIndex, answer);
+
+ if (currentQuestionIndex == examQuestions.size() - 1) {
+ // 最后一题,显示结果
+ timer.stop();
+ showResults();
+ } else {
+ // 下一题
+ currentQuestionIndex++;
+ displayCurrentQuestion();
+ }
+ }
+ // 添加辅助方法获取选项对应的实际值
+ private String getAnswerValue(String optionLetter) {
+ Question currentQuestion = examQuestions.get(currentQuestionIndex);
+ switch (optionLetter) {
+ case "A": return currentQuestion.getOptionA();
+ case "B": return currentQuestion.getOptionB();
+ case "C": return currentQuestion.getOptionC();
+ case "D": return currentQuestion.getOptionD();
+ default: return "";
+ }
+ }
+ @FXML
+ private void handleNextQuestion() {
+ currentQuestionIndex++;
+ displayCurrentQuestion();
+ }
+
+ @FXML
+ private void handleExitExam() {
+ if (showExitConfirmation()) {
+ timer.stop();
+ returnToMainMenu();
+ }
+ }
+
+ private boolean showExitConfirmation() {
+ Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+ alert.setTitle("确认退出");
+ alert.setHeaderText("确定要退出考试吗?");
+ alert.setContentText("退出后当前进度将丢失。");
+ return alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK;
+ }
+
+ private void showAlert(String title, String message) {
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle(title);
+ alert.setHeaderText(null);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
+
+ private void showResults() {
+ try {
+ // 停止计时器
+ timer.stop();
+
+ // 计算成绩
+ int correctCount = 0;
+ StringBuilder resultDetails = new StringBuilder();
+
+ for (int i = 0; i < examQuestions.size(); i++) {
+ Question question = examQuestions.get(i);
+ String userAnswer = userAnswers.get(i);
+ boolean isCorrect = question.isCorrect(userAnswer);
+
+ if (isCorrect) {
+ correctCount++;
+ }
+
+ resultDetails.append("第").append(i + 1).append("题: ");
+ resultDetails.append(question.getQuestionText()).append("\n");
+ resultDetails.append("你的答案: ").append(userAnswer != null ? userAnswer : "未作答").append(" ");
+ resultDetails.append("正确答案: ").append(question.getCorrectAnswer()).append(" ");
+ resultDetails.append(isCorrect ? "✓" : "✗").append("\n\n");
+ }
+
+ int score = (int) Math.round((double) correctCount / examQuestions.size() * 100);
+
+ // 显示结果对话框
+ Alert resultAlert = new Alert(Alert.AlertType.INFORMATION);
+ resultAlert.setTitle("考试完成");
+ resultAlert.setHeaderText("🎉 考试结果");
+ resultAlert.setContentText(String.format(
+ "得分: %d分\n正确: %d题\n总计: %d题\n正确率: %.1f%%",
+ score, correctCount, examQuestions.size(), (double) correctCount / examQuestions.size() * 100
+ ));
+ resultAlert.showAndWait();
+
+ // 返回主菜单
+ returnToMainMenu();
+
+ } catch (Exception e) {
+ showAlert("错误", "显示结果时出错:" + e.getMessage());
+ }
+ }
+
+ private void returnToMainMenu() {
+ try {
+ // 关闭当前窗口
+ Stage currentStage = (Stage) submitButton.getScene().getWindow();
+ currentStage.close();
+
+ // 打开主菜单
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("exam-view.fxml"));
+ Scene scene = new Scene(loader.load(), 1000, 900);
+ Stage stage = new Stage();
+ stage.setTitle("数学考试系统");
+ stage.setScene(scene);
+ stage.show();
+
+ } catch (Exception e) {
+ showAlert("错误", "返回主菜单时出错:" + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/com/example/mathsystemtogether/HelloApplication.java b/src/main/java/com/example/mathsystemtogether/HelloApplication.java
index 9cf2187..91183f8 100644
--- a/src/main/java/com/example/mathsystemtogether/HelloApplication.java
+++ b/src/main/java/com/example/mathsystemtogether/HelloApplication.java
@@ -11,7 +11,7 @@ public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("exam-view.fxml"));
- Scene scene = new Scene(fxmlLoader.load(), 900, 800);
+ Scene scene = new Scene(fxmlLoader.load(), 900, 900);
stage.setTitle("数学考试系统");
stage.setResizable(true);
stage.setScene(scene);
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 30fc570..9c6a55e 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,6 +1,8 @@
module com.example.mathsystemtogether {
requires javafx.controls;
requires javafx.fxml;
+ requires jakarta.mail;
+ requires jakarta.activation;
opens com.example.mathsystemtogether to javafx.fxml;
diff --git a/src/main/resources/com/example/mathsystemtogether/exam-taking-view.fxml b/src/main/resources/com/example/mathsystemtogether/exam-taking-view.fxml
new file mode 100644
index 0000000..41c7385
--- /dev/null
+++ b/src/main/resources/com/example/mathsystemtogether/exam-taking-view.fxml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/com/example/mathsystemtogether/exam-view.fxml b/src/main/resources/com/example/mathsystemtogether/exam-view.fxml
index abec30f..7f86954 100644
--- a/src/main/resources/com/example/mathsystemtogether/exam-view.fxml
+++ b/src/main/resources/com/example/mathsystemtogether/exam-view.fxml
@@ -4,162 +4,112 @@
+
+
+
-
+
-
+
-
+
-
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
-
+
+
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
+
+
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
+
+