diff --git a/README.md b/README.md
deleted file mode 100644
index 8a4459c..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# partner_project
-
diff --git a/src/fxml/css/1.jpg b/src/fxml/css/1.jpg
new file mode 100644
index 0000000..2672684
Binary files /dev/null and b/src/fxml/css/1.jpg differ
diff --git a/src/fxml/css/2.gif b/src/fxml/css/2.gif
new file mode 100644
index 0000000..6383347
Binary files /dev/null and b/src/fxml/css/2.gif differ
diff --git a/src/fxml/css/3.png b/src/fxml/css/3.png
new file mode 100644
index 0000000..22c53bf
Binary files /dev/null and b/src/fxml/css/3.png differ
diff --git a/src/fxml/css/main.css b/src/fxml/css/main.css
new file mode 100644
index 0000000..31935f0
--- /dev/null
+++ b/src/fxml/css/main.css
@@ -0,0 +1,170 @@
+/* 根容器样式 */
+.root {
+ -fx-font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
+ -fx-background-image: url("1.jpg");
+ -fx-background-repeat: no-repeat;
+ -fx-background-size: cover;
+}
+
+/* 通用按钮样式 */
+.button {
+ -fx-background-color: #4CAF50;
+ -fx-text-fill: white;
+ -fx-font-size: 14px;
+ -fx-font-weight: bold;
+ -fx-padding: 10px 20px;
+ -fx-background-radius: 25px;
+ -fx-border-radius: 25px;
+ -fx-cursor: hand;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 5, 0, 0, 2);
+ -fx-transition: all 0.3s;
+}
+
+.button:hover {
+ -fx-background-color: #45a049;
+ -fx-scale-x: 1.05;
+ -fx-scale-y: 1.05;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 8, 0, 0, 3);
+}
+
+.button:pressed {
+ -fx-background-color: #3d8b40;
+ -fx-scale-x: 0.95;
+ -fx-scale-y: 0.95;
+}
+
+/* 次要按钮样式 */
+.button-secondary {
+ -fx-background-color: #2196F3;
+}
+
+.button-secondary:hover {
+ -fx-background-color: #1976D2;
+}
+
+.button-secondary:pressed {
+ -fx-background-color: #0D47A1;
+}
+
+/* 警告按钮样式 */
+.button-warning {
+ -fx-background-color: #ff9800;
+}
+
+.button-warning:hover {
+ -fx-background-color: #f57c00;
+}
+
+/* 危险按钮样式 */
+.button-danger {
+ -fx-background-color: #f44336;
+}
+
+.button-danger:hover {
+ -fx-background-color: #d32f2f;
+}
+
+/* 标签样式 */
+.label {
+ -fx-text-fill: #333333;
+ -fx-font-size: 14px;
+}
+
+.label-title {
+ -fx-font-size: 24px;
+ -fx-font-weight: bold;
+ -fx-text-fill: #2c3e50;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 3, 0, 1, 1);
+}
+
+.label-subtitle {
+ -fx-font-size: 18px;
+ -fx-font-weight: bold;
+ -fx-text-fill: #34495e;
+}
+
+.label-info {
+ -fx-text-fill: #7f8c8d;
+ -fx-font-size: 12px;
+}
+
+/* 输入框样式 */
+.text-field, .password-field {
+ -fx-background-color: white;
+ -fx-border-color: #bdc3c7;
+ -fx-border-radius: 15px;
+ -fx-background-radius: 15px;
+ -fx-padding: 10px 15px;
+ -fx-font-size: 14px;
+ -fx-effect: innershadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2);
+}
+
+.text-field:focused, .password-field:focused {
+ -fx-border-color: #3498db;
+ -fx-effect: dropshadow(three-pass-box, rgba(52, 152, 219, 0.3), 10, 0, 0, 3);
+}
+
+/* 下拉框样式 */
+.combo-box {
+ -fx-background-color: white;
+ -fx-border-color: #bdc3c7;
+ -fx-border-radius: 15px;
+ -fx-background-radius: 15px;
+ -fx-padding: 5px 15px;
+}
+
+.combo-box .arrow-button {
+ -fx-background-color: transparent;
+}
+
+.combo-box .list-cell {
+ -fx-background-color: white;
+ -fx-text-fill: #333333;
+}
+
+.combo-box .list-view {
+ -fx-background-color: white;
+ -fx-border-color: #bdc3c7;
+ -fx-border-radius: 10px;
+ -fx-background-radius: 10px;
+}
+
+/* 单选按钮样式 */
+.radio-button {
+ -fx-text-fill: #333333;
+ -fx-font-size: 14px;
+ -fx-padding: 5px;
+}
+
+.radio-button .radio {
+ -fx-background-color: white;
+ -fx-border-color: #bdc3c7;
+ -fx-border-radius: 50%;
+ -fx-background-radius: 50%;
+}
+
+.radio-button:selected .radio {
+ -fx-background-color: #3498db;
+ -fx-border-color: #2980b9;
+}
+
+.radio-button .dot {
+ -fx-background-color: white;
+ -fx-background-radius: 50%;
+}
+
+/* 容器样式 */
+.vbox {
+ -fx-background-color: rgba(255, 255, 255, 0.5);
+ -fx-background-radius: 16px;
+}
+
+
+.container {
+ -fx-background-color: white;
+ -fx-background-radius: 20px;
+ -fx-border-radius: 20px;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 15, 0, 0, 5);
+ -fx-padding: 30px;
+}
+
diff --git a/src/fxml/difficulty-selection.fxml b/src/fxml/difficulty-selection.fxml
new file mode 100644
index 0000000..2ad8916
--- /dev/null
+++ b/src/fxml/difficulty-selection.fxml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fxml/exam-problem.fxml b/src/fxml/exam-problem.fxml
new file mode 100644
index 0000000..ce33760
--- /dev/null
+++ b/src/fxml/exam-problem.fxml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fxml/login.fxml b/src/fxml/login.fxml
new file mode 100644
index 0000000..3e12856
--- /dev/null
+++ b/src/fxml/login.fxml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fxml/modify-password.fxml b/src/fxml/modify-password.fxml
new file mode 100644
index 0000000..057dcce
--- /dev/null
+++ b/src/fxml/modify-password.fxml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fxml/register-code.fxml b/src/fxml/register-code.fxml
new file mode 100644
index 0000000..78742ce
--- /dev/null
+++ b/src/fxml/register-code.fxml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fxml/register-email.fxml b/src/fxml/register-email.fxml
new file mode 100644
index 0000000..7cb815c
--- /dev/null
+++ b/src/fxml/register-email.fxml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fxml/register-password.fxml b/src/fxml/register-password.fxml
new file mode 100644
index 0000000..739cddb
--- /dev/null
+++ b/src/fxml/register-password.fxml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fxml/score-screen.fxml b/src/fxml/score-screen.fxml
new file mode 100644
index 0000000..0b3da02
--- /dev/null
+++ b/src/fxml/score-screen.fxml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ui/ExamController.java b/src/ui/ExamController.java
new file mode 100644
index 0000000..9ec1bfe
--- /dev/null
+++ b/src/ui/ExamController.java
@@ -0,0 +1,442 @@
+package ui;
+
+import auth.User;
+import generator.Problem;
+import service.ExamService;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import auth.AuthService;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+public class ExamController {
+
+ @FXML private Label titleLabel;
+ @FXML private ComboBox difficultyCombo;
+ @FXML private TextField countField;
+ @FXML private Label problemText;
+ @FXML private VBox optionsBox;
+ @FXML private Button nextButton;
+ @FXML private Label scoreLabel;
+ @FXML private Label finalScoreLabel;
+
+ private Stage primaryStage;
+ private User currentUser;
+ private final ExamService examService = new ExamService();
+ private List currentExam;
+ private List userAnswers;
+ private int currentProblemIndex = 0;
+ private Map> optionMappings = new HashMap<>();
+
+ // 添加setter方法用于在界面间传递数据
+ public void setCurrentExam(List currentExam) {
+ this.currentExam = currentExam;
+ }
+
+ public void setUserAnswers(List userAnswers) {
+ this.userAnswers = userAnswers;
+ }
+
+ @FXML
+ public void initialize() {
+ if (currentUser != null) {
+ System.out.println("当前用户: " + currentUser.getUsername() + ", 类型: " + currentUser.getType());
+ }
+ }
+
+
+ public void setPrimaryStage(Stage primaryStage) {
+ this.primaryStage = primaryStage;
+ }
+
+ public void setCurrentUser(User user) {
+ this.currentUser = user;
+ initializeDifficultySelection();
+ }
+
+ private void initializeDifficultySelection() {
+
+ if (titleLabel != null) {
+ titleLabel.setText("难度(当前: " + currentUser.getType() + ")");
+ }
+
+ if (difficultyCombo != null) {
+ // 清空并重新添加选项
+ difficultyCombo.getItems().clear();
+ difficultyCombo.getItems().addAll("小学", "初中", "高中");
+
+ // 设置当前用户类型为默认值
+ difficultyCombo.setValue(currentUser.getType());
+
+ }
+ }
+
+ @FXML
+ private void handleSwitchDifficulty() {
+
+ if (difficultyCombo == null) {
+ showAlert("错误", "界面初始化失败,请重新登录");
+ return;
+ }
+
+ String newDifficulty = difficultyCombo.getValue();
+
+ if (newDifficulty == null) {
+ showAlert("错误", "请先选择难度");
+ return;
+ }
+
+ if (currentUser == null) {
+ System.err.println("错误: currentUser 为 null");
+ showAlert("错误", "用户信息丢失,请重新登录");
+ return;
+ }
+
+
+ if (newDifficulty.equals(currentUser.getType())) {
+ showAlert("提示", "已经是" + newDifficulty + "难度");
+ return;
+ }
+
+ try {
+ // 创建 AuthService 实例
+ AuthService authService = new AuthService();
+
+ // 更新数据库中的难度
+ boolean success = authService.updateDifficulty(currentUser.getUsername(), newDifficulty);
+
+ if (success) {
+ // 验证更新是否成功 - 重新加载用户数据
+ User updatedUser = authService.login(currentUser.getUsername(), currentUser.getPassword());
+
+ if (updatedUser != null) {
+ currentUser = updatedUser;
+ showAlert("成功", "难度已成功切换为: " + newDifficulty);
+
+ // 更新界面显示
+ if (titleLabel != null) {
+ titleLabel.setText("难度选择 (当前: " + currentUser.getType() + ")");
+ }
+
+ // 重新初始化下拉框
+ initializeDifficultySelection();
+
+ } else {
+ System.err.println("错误: 重新加载用户信息失败");
+ showAlert("错误", "用户信息验证失败");
+ }
+ } else {
+ System.err.println("错误: 文件更新失败");
+ showAlert("错误", "难度切换失败,请检查文件权限或联系管理员");
+ }
+ } catch (Exception e) {
+ System.err.println("难度切换异常: " + e.getMessage());
+ e.printStackTrace();
+ showAlert("错误", "难度切换异常: " + e.getMessage());
+ }
+
+ }
+
+ @FXML
+ private void handleStartExam() {
+ try {
+ int count = Integer.parseInt(countField.getText());
+ if (count < 10 || count > 30) {
+ throw new NumberFormatException();
+ }
+ generateAndShowExam(count);
+ } catch (NumberFormatException ex) {
+ showAlert("错误", "请输入有效的题目数量 (10-30)。");
+ } catch (Exception ex) {
+ showAlert("错误", "生成试卷失败: " + ex.getMessage());
+ }
+ }
+
+ @FXML
+ private void handleNextProblem() {
+ saveCurrentAnswer();
+ currentProblemIndex++;
+ if (currentProblemIndex < currentExam.size()) {
+ showProblem(currentProblemIndex);
+ } else {
+ showScoreScreen();
+ }
+ }
+
+ @FXML
+ private void handleContinue() {
+ showDifficultySelection();
+ }
+
+ @FXML
+ private void handleExit() {
+ primaryStage.close();
+ }
+
+ @FXML
+ private void handleLogout() {
+ showLoginScreen();
+ }
+
+ private void generateAndShowExam(int count) throws Exception {
+ currentExam = examService.generateExam(currentUser, count);
+
+ // 检查试卷是否成功生成
+ if (currentExam == null || currentExam.isEmpty()) {
+ throw new Exception("无法生成试卷,请稍后重试");
+ }
+
+ // 初始化所有数据结构
+ userAnswers = new ArrayList<>(currentExam.size());
+ optionMappings = new HashMap<>();
+
+ for (int i = 0; i < currentExam.size(); i++) {
+ userAnswers.add(null);
+ }
+
+ currentProblemIndex = 0;
+ showProblem(currentProblemIndex);
+ }
+
+ private void showProblem(int index) {
+ try {
+ // 检查索引是否有效
+ if (currentExam == null || index < 0 || index >= currentExam.size()) {
+ showAlert("错误", "题目索引无效,无法显示题目");
+ return;
+ }
+
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/exam-problem.fxml"));
+ Parent root = loader.load();
+
+ ExamController controller = loader.getController();
+ controller.setPrimaryStage(primaryStage);
+ controller.setCurrentUser(currentUser);
+ controller.setCurrentExam(currentExam);
+ controller.setUserAnswers(userAnswers);
+ controller.setOptionMappings(optionMappings); // 传递选项映射
+ controller.setCurrentProblemIndex(index); // 设置当前题目索引
+ controller.displayProblem(currentExam.get(index), index);
+
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载题目界面失败: " + e.getMessage());
+ }
+ }
+
+ public void setOptionMappings(Map> optionMappings) {
+ this.optionMappings = optionMappings;
+ }
+
+ public void setCurrentProblemIndex(int currentProblemIndex) {
+ this.currentProblemIndex = currentProblemIndex;
+ }
+
+ // 修改 displayProblem 方法,确保每次都正确初始化
+ public void displayProblem(Problem problem, int index) {
+ // 确保当前索引正确设置
+ this.currentProblemIndex = index;
+
+ // 确保 optionMappings 不为 null
+ if (optionMappings == null) {
+ optionMappings = new HashMap<>();
+ }
+
+ if (problemText != null) {
+ problemText.setText((index + 1) + ". " + problem.getQuestionText());
+ }
+
+ if (optionsBox != null) {
+ optionsBox.getChildren().clear();
+ ToggleGroup group = new ToggleGroup();
+ List options = problem.getOptions();
+ char optionChar = 'A';
+
+ // 创建或更新当前题目的选项映射
+ Map currentOptionMap = new HashMap<>();
+ optionMappings.put(index, currentOptionMap);
+
+ for (int i = 0; i < options.size(); i++) {
+ String optionLabel = String.valueOf(optionChar);
+ String optionValue = options.get(i);
+ currentOptionMap.put(optionLabel, optionValue);
+
+ RadioButton rb = new RadioButton(optionLabel + ". " + optionValue);
+ rb.setUserData(optionLabel); // 存储选项字母 A, B, C, D
+ rb.setToggleGroup(group);
+ rb.setStyle("-fx-font-size: 12px; -fx-font-weight: bold;");
+ optionsBox.getChildren().add(rb);
+
+ // 恢复选择 - 使用选项字母进行比较
+ if (userAnswers != null && index < userAnswers.size() &&
+ userAnswers.get(index) != null &&
+ userAnswers.get(index).equals(optionLabel)) {
+ rb.setSelected(true);
+ }
+ optionChar++;
+ }
+ }
+
+ if (nextButton != null) {
+ nextButton.setText(index < currentExam.size() - 1 ? "下一题" : "提交并评分");
+ }
+
+ }
+
+ private void saveCurrentAnswer() {
+ if (optionsBox == null || userAnswers == null || currentProblemIndex >= userAnswers.size()) {
+ return;
+ }
+
+ // 查找选中的单选按钮
+ for (javafx.scene.Node node : optionsBox.getChildren()) {
+ if (node instanceof RadioButton) {
+ RadioButton rb = (RadioButton) node;
+ if (rb.isSelected()) {
+ String selectedOption = (String) rb.getUserData();
+ userAnswers.set(currentProblemIndex, selectedOption);
+ return;
+ }
+ }
+ }
+ // 如果没有选择任何选项,设置为null
+ userAnswers.set(currentProblemIndex, null);
+ }
+
+ private void showScoreScreen() {
+ try {
+ // 保存最后一题的答案
+ saveCurrentAnswer();
+
+ // 计算分数 - 使用新的评分方法
+ int score = calculateScore();
+ int correctCount = getCorrectCount();
+
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/score-screen.fxml"));
+ Parent root = loader.load();
+
+ ExamController controller = loader.getController();
+ controller.setPrimaryStage(primaryStage);
+ controller.setCurrentUser(currentUser);
+ controller.displayScore(score, currentExam.size(), correctCount);
+
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载评分界面失败: " + e.getMessage());
+ }
+ }
+
+ // 添加计算正确数量的方法
+ private int getCorrectCount() {
+ int correctCount = 0;
+ for (int i = 0; i < currentExam.size(); i++) {
+ Problem problem = currentExam.get(i);
+ String userAnswerLetter = userAnswers.get(i);
+
+ if (userAnswerLetter != null && optionMappings.containsKey(i)) {
+ String userSelectedValue = optionMappings.get(i).get(userAnswerLetter);
+ String correctAnswerValue = problem.getCorrectAnswerOption();
+
+ if (userSelectedValue != null && userSelectedValue.equals(correctAnswerValue)) {
+ correctCount++;
+ }
+ }
+ }
+ return correctCount;
+ }
+
+ // 修改 displayScore 方法显示更详细的信息
+ public void displayScore(int score, int totalQuestions, int correctCount) {
+ if (finalScoreLabel != null) {
+ finalScoreLabel.setText(String.format("最终得分: %d / 100\n正确题数: %d / %d",
+ score, correctCount, totalQuestions));
+ }
+ }
+
+ private void showDifficultySelection() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/difficulty-selection.fxml"));
+ Parent root = loader.load();
+
+ ExamController controller = loader.getController();
+ controller.setPrimaryStage(primaryStage);
+ controller.setCurrentUser(currentUser);
+
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载难度选择界面失败: " + e.getMessage());
+ }
+ }
+
+
+ private void showLoginScreen() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
+ Parent root = loader.load();
+
+ LoginController controller = loader.getController();
+ controller.setPrimaryStage(primaryStage);
+
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载登录界面失败: " + e.getMessage());
+ }
+ }
+
+ private void showAlert(String title, String message) {
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle(title);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
+
+ // 在 ExamController 中添加评分方法
+ private int calculateScore() {
+ if (currentExam == null || userAnswers == null ||
+ currentExam.size() != userAnswers.size()) {
+ System.err.println("评分错误: 数据不完整");
+ return 0;
+ }
+
+ int correctCount = 0;
+ for (int i = 0; i < currentExam.size(); i++) {
+ Problem problem = currentExam.get(i);
+ String userAnswerLetter = userAnswers.get(i);
+ if (userAnswerLetter != null && optionMappings != null && optionMappings.containsKey(i)) {
+ // 获取用户选择的选项对应的数值
+ String userSelectedValue = optionMappings.get(i).get(userAnswerLetter);
+ // 获取正确答案
+ String correctAnswerValue = problem.getCorrectAnswerOption();
+
+ boolean isCorrect = userSelectedValue != null &&
+ userSelectedValue.equals(correctAnswerValue);
+
+ if (isCorrect) {
+ correctCount++;
+ }
+
+ }
+ }
+
+ double score = ((double) correctCount / currentExam.size()) * 100;
+ int finalScore = (int) Math.round(score);
+ return finalScore;
+ }
+
+}
\ No newline at end of file
diff --git a/src/ui/LoginController.java b/src/ui/LoginController.java
new file mode 100644
index 0000000..bf76d60
--- /dev/null
+++ b/src/ui/LoginController.java
@@ -0,0 +1,125 @@
+package ui;
+
+import auth.AuthService;
+import auth.User;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Label;
+import javafx.scene.control.PasswordField;
+import javafx.scene.control.TextField;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+public class LoginController {
+
+ @FXML private TextField usernameField;
+ @FXML private PasswordField passwordField;
+ @FXML private Label messageLabel;
+
+ private final AuthService authService = new AuthService();
+ private Stage primaryStage;
+
+ public void setPrimaryStage(Stage primaryStage) {
+ this.primaryStage = primaryStage;
+ }
+
+ @FXML
+ private void handleLogin() {
+ String username = usernameField.getText();
+ String password = passwordField.getText();
+ User user = authService.login(username, password);
+
+ if (user != null) {
+ messageLabel.setText("登录成功!");
+ showExamSelectScreen(user);
+ } else {
+ messageLabel.setText("登录失败,用户名或密码错误。");
+ }
+ }
+
+ @FXML
+ private void handleRegister() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-email.fxml"));
+ Parent root = loader.load();
+
+ RegisterController controller = loader.getController();
+
+ if (primaryStage != null) {
+ controller.setPrimaryStage(primaryStage);
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } else {
+ Stage currentStage = (Stage) usernameField.getScene().getWindow();
+ controller.setPrimaryStage(currentStage);
+ Scene scene = new Scene(root);
+ currentStage.setScene(scene);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载注册界面失败: " + e.getMessage());
+ }
+ }
+
+ @FXML
+ private void handleModifyPassword() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/modify-password.fxml"));
+ Parent root = loader.load();
+
+ ModifyPasswordController controller = loader.getController();
+
+ // 确保primaryStage不为null
+ if (primaryStage != null) {
+ controller.setPrimaryStage(primaryStage);
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } else {
+ // 备选方案:从当前场景获取stage
+ Stage currentStage = (Stage) usernameField.getScene().getWindow();
+ controller.setPrimaryStage(currentStage);
+ Scene scene = new Scene(root);
+ currentStage.setScene(scene);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载修改密码界面失败: " + e.getMessage());
+ }
+ }
+
+ private void showExamSelectScreen(User user) {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/difficulty-selection.fxml"));
+ Parent root = loader.load();
+
+ ExamController controller = loader.getController();
+
+ if (primaryStage != null) {
+ controller.setPrimaryStage(primaryStage);
+ controller.setCurrentUser(user);
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } else {
+ Stage currentStage = (Stage) usernameField.getScene().getWindow();
+ controller.setPrimaryStage(currentStage);
+ controller.setCurrentUser(user);
+ Scene scene = new Scene(root);
+ currentStage.setScene(scene);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载考试选择界面失败: " + e.getMessage());
+ }
+ }
+
+ private void showAlert(String title, String message) {
+ Alert alert = new Alert(Alert.AlertType.ERROR);
+ alert.setTitle(title);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
+}
\ No newline at end of file
diff --git a/src/ui/MainApplication.java b/src/ui/MainApplication.java
new file mode 100644
index 0000000..6022caf
--- /dev/null
+++ b/src/ui/MainApplication.java
@@ -0,0 +1,35 @@
+package ui;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import javafx.scene.image.Image;
+
+public class MainApplication extends Application {
+
+ @Override
+ public void start(Stage primaryStage) throws Exception {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
+ Parent root = loader.load();
+
+ // 获取控制器并设置primaryStage
+ LoginController controller = loader.getController();
+ controller.setPrimaryStage(primaryStage);
+
+ Scene scene = new Scene(root);
+
+ // 设置窗口图标
+ try {
+ Image icon = new Image(getClass().getResourceAsStream("/fxml/css/3.png"));
+ primaryStage.getIcons().add(icon);
+ } catch (Exception e) {
+ System.err.println("无法加载图标文件: " + e.getMessage());
+ }
+
+ primaryStage.setTitle("小初高数学学习软件");
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+}
\ No newline at end of file
diff --git a/src/ui/ModifyPasswordController.java b/src/ui/ModifyPasswordController.java
new file mode 100644
index 0000000..51ad50d
--- /dev/null
+++ b/src/ui/ModifyPasswordController.java
@@ -0,0 +1,109 @@
+package ui;
+
+import auth.AuthService;
+import auth.User;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Label;
+import javafx.scene.control.PasswordField;
+import javafx.scene.control.TextField;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+public class ModifyPasswordController {
+
+ @FXML private TextField usernameField;
+ @FXML private PasswordField oldPasswordField;
+ @FXML private PasswordField newPasswordField;
+ @FXML private PasswordField confirmPasswordField;
+ @FXML private Label messageLabel;
+
+ private final AuthService authService = new AuthService();
+ private Stage primaryStage;
+
+ public void setPrimaryStage(Stage primaryStage) {
+ this.primaryStage = primaryStage;
+ }
+
+ @FXML
+ private void handleModifyPassword() {
+ String username = usernameField.getText().trim();
+ String oldPassword = oldPasswordField.getText();
+ String newPassword = newPasswordField.getText();
+ String confirmPassword = confirmPasswordField.getText();
+
+ User user = authService.login(username, oldPassword);
+ if (user == null) {
+ messageLabel.setText("错误:用户名或旧密码不正确。");
+ return;
+ }
+
+ if (!newPassword.equals(confirmPassword)) {
+ messageLabel.setText("错误:两次输入的新密码不一致。");
+ return;
+ }
+
+ if (!AuthService.isPasswordValid(newPassword)) {
+ messageLabel.setText("错误:新密码不符合要求 (6-10位, 需含大小写字母和数字)。");
+ return;
+ }
+
+ if (authService.updatePassword(username, newPassword)) {
+ showAlert("成功", "密码修改成功,请重新登录。");
+ showLoginScreen();
+ } else {
+ messageLabel.setText("错误:密码修改失败,请稍后重试。");
+ }
+ }
+
+ @FXML
+ private void handleBack() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
+ Parent root = loader.load();
+
+ LoginController controller = loader.getController();
+
+ if (primaryStage != null) {
+ controller.setPrimaryStage(primaryStage);
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } else {
+ Stage currentStage = (Stage) usernameField.getScene().getWindow();
+ controller.setPrimaryStage(currentStage);
+ Scene scene = new Scene(root);
+ currentStage.setScene(scene);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载登录界面失败: " + e.getMessage());
+ }
+ }
+
+ private void showLoginScreen() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
+ Parent root = loader.load();
+
+ LoginController controller = loader.getController();
+ controller.setPrimaryStage(primaryStage);
+
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载登录界面失败: " + e.getMessage());
+ }
+ }
+
+ private void showAlert(String title, String message) {
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle(title);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
+}
\ No newline at end of file
diff --git a/src/ui/RegisterController.java b/src/ui/RegisterController.java
new file mode 100644
index 0000000..1e75bda
--- /dev/null
+++ b/src/ui/RegisterController.java
@@ -0,0 +1,206 @@
+package ui;
+
+import auth.AuthService;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Label;
+import javafx.scene.control.PasswordField;
+import javafx.scene.control.TextField;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RegisterController {
+
+ @FXML private TextField emailField;
+ @FXML private TextField codeField;
+ @FXML private TextField usernameField;
+ @FXML private PasswordField passwordField;
+ @FXML private PasswordField confirmPasswordField;
+ @FXML private Label messageLabel;
+ @FXML private Label emailLabel;
+ @FXML private Label titleLabel;
+
+ private final AuthService authService = new AuthService();
+ private final Map pendingRegistrations = new HashMap<>();
+ private Stage primaryStage;
+ private String currentEmail;
+
+ public void setPrimaryStage(Stage primaryStage) {
+ this.primaryStage = primaryStage;
+ }
+
+ @FXML
+ private void handleSendCode() {
+ String email = emailField.getText().trim();
+ if (!authService.validateEmail(email)) {
+ messageLabel.setText("错误:邮箱格式不正确。");
+ return;
+ }
+ if (authService.isUsernameOrEmailTaken(null, email)) {
+ messageLabel.setText("错误:该邮箱已被注册。");
+ return;
+ }
+
+ String code = authService.sendVerificationCode(email);
+ if (code != null) {
+ pendingRegistrations.put(email, code);
+ currentEmail = email;
+ showAlert("成功", "验证码已发送到您的邮箱,请查收!");
+ showRegisterCodeScreen();
+ } else {
+ messageLabel.setText("错误:验证码发送失败,请检查邮箱或服务器设置。");
+ }
+ }
+
+ @FXML
+ private void handleVerifyCode() {
+ String inputCode = codeField.getText().trim();
+ String storedCode = pendingRegistrations.get(currentEmail);
+
+ if (inputCode.equals(storedCode)) {
+ messageLabel.setText("验证成功!");
+ showRegisterPasswordScreen();
+ } else {
+ messageLabel.setText("错误:验证码不正确。");
+ }
+ }
+
+ @FXML
+ private void handleResendCode() {
+ String newCode = authService.sendVerificationCode(currentEmail);
+ if (newCode != null) {
+ pendingRegistrations.put(currentEmail, newCode);
+ messageLabel.setText("新的验证码已发送。");
+ } else {
+ messageLabel.setText("错误:重发失败。");
+ }
+ }
+
+ @FXML
+ private void handleCompleteRegistration() {
+ String username = usernameField.getText().trim();
+ String password = passwordField.getText();
+ String confirmPassword = confirmPasswordField.getText();
+
+ if (!validatePasswordSetup(username, password, confirmPassword)) {
+ return;
+ }
+
+ if (authService.registerUser(username, password, currentEmail)) {
+ pendingRegistrations.remove(currentEmail);
+ showAlert("成功", "注册成功,请登录。");
+ showLoginScreen();
+ } else {
+ messageLabel.setText("致命错误:注册失败,请联系管理员。");
+ }
+ }
+
+ @FXML
+ private void handleBack() {
+ showLoginScreen();
+ }
+
+ private boolean validatePasswordSetup(String username, String password, String confirmPassword) {
+ if (authService.isUsernameOrEmailTaken(username, null)) {
+ messageLabel.setText("错误:该用户名已被占用。");
+ return false;
+ }
+ if (!password.equals(confirmPassword)) {
+ messageLabel.setText("错误:两次密码输入不一致。");
+ return false;
+ }
+ if (!AuthService.isPasswordValid(password)) {
+ messageLabel.setText("错误:密码不符合要求 (6-10位, 需含大小写字母和数字)。");
+ return false;
+ }
+ return true;
+ }
+
+ private void showRegisterCodeScreen() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-code.fxml"));
+ Parent root = loader.load();
+
+ RegisterController controller = loader.getController();
+ controller.setPrimaryStage(primaryStage);
+ controller.setCurrentEmail(currentEmail);
+ controller.setPendingRegistrations(pendingRegistrations);
+
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载验证码界面失败: " + e.getMessage());
+ }
+ }
+
+ private void showRegisterPasswordScreen() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-password.fxml"));
+ Parent root = loader.load();
+
+ RegisterController controller = loader.getController();
+ controller.setPrimaryStage(primaryStage);
+ controller.setCurrentEmail(currentEmail);
+ controller.setPendingRegistrations(pendingRegistrations);
+
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载密码设置界面失败: " + e.getMessage());
+ }
+ }
+
+ private void showLoginScreen() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
+ Parent root = loader.load();
+
+ LoginController controller = loader.getController();
+
+ if (primaryStage != null) {
+ controller.setPrimaryStage(primaryStage);
+ Scene scene = new Scene(root);
+ primaryStage.setScene(scene);
+ } else {
+ Stage currentStage = (Stage) emailField.getScene().getWindow();
+ controller.setPrimaryStage(currentStage);
+ Scene scene = new Scene(root);
+ currentStage.setScene(scene);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ showAlert("错误", "加载登录界面失败: " + e.getMessage());
+ }
+ }
+
+ // Setters for sharing data between different registration screens
+ public void setCurrentEmail(String email) {
+ this.currentEmail = email;
+ if (emailLabel != null) {
+ emailLabel.setText("已发送到: " + email);
+ }
+ if (titleLabel != null) {
+ titleLabel.setText("注册 (3/3) - 设置密码 (邮箱: " + email + ")");
+ }
+ }
+
+ public void setPendingRegistrations(Map pendingRegistrations) {
+ this.pendingRegistrations.clear();
+ this.pendingRegistrations.putAll(pendingRegistrations);
+ }
+
+ private void showAlert(String title, String message) {
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle(title);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
+}
\ No newline at end of file