diff --git a/src/main/java/com/mathgenerator/controller/LoginController.java b/src/main/java/com/mathgenerator/controller/LoginController.java new file mode 100644 index 0000000..7c7150d --- /dev/null +++ b/src/main/java/com/mathgenerator/controller/LoginController.java @@ -0,0 +1,113 @@ +package com.mathgenerator.controller; + +import com.mathgenerator.model.User; +import com.mathgenerator.service.UserService; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +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.Optional; + +public class LoginController { + + // 依赖注入后端服务 + private final UserService userService = new UserService(); + + // @FXML注解将FXML文件中的控件与这里的变量关联起来 + @FXML + private TextField usernameField; + + @FXML + private PasswordField passwordField; + + @FXML + private Button loginButton; + + @FXML + private Button registerButton; + + @FXML + private Label statusLabel; + + /** + * 处理登录按钮点击事件。 + * @param event 事件对象 + */ + @FXML + private void handleLoginButtonAction(ActionEvent event) { + String username = usernameField.getText(); + String password = passwordField.getText(); + + if (username.isEmpty() || password.isEmpty()) { + statusLabel.setText("用户名和密码不能为空!"); + return; + } + + Optional userOptional = userService.login(username, password); + + if (userOptional.isPresent()) { + statusLabel.setText("登录成功!"); + // 登录成功,调用新方法跳转到主菜单 + loadMainMenu(userOptional.get()); + } else { + statusLabel.setText("登录失败:用户名或密码错误。"); + } + } + + /** + * 处理注册按钮点击事件,跳转到注册界面。 + * @param event 事件对象 + */ + @FXML + private void handleRegisterButtonAction(ActionEvent event) { + loadScene("/com/mathgenerator/view/RegisterView.fxml"); + } + + /** + * 加载主菜单界面,并传递用户信息。 + * @param user 登录成功的用户对象 + */ + private void loadMainMenu(User user) { + try { + // 1. 加载 FXML 文件 + FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/MainMenuView.fxml")); + Parent root = loader.load(); + + // 2. 获取新界面的控制器 + MainMenuController controller = loader.getController(); + + // 3. 调用控制器的方法,传递数据 + controller.initData(user); + + // 4. 显示新场景 + Stage stage = (Stage) loginButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle("主菜单"); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 切换到简单场景的辅助方法(如注册页)。 + * @param fxmlPath FXML文件的路径 + */ + private void loadScene(String fxmlPath) { + try { + Parent root = FXMLLoader.load(getClass().getResource(fxmlPath)); + Stage stage = (Stage) loginButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mathgenerator/controller/MainMenuController.java b/src/main/java/com/mathgenerator/controller/MainMenuController.java new file mode 100644 index 0000000..867deff --- /dev/null +++ b/src/main/java/com/mathgenerator/controller/MainMenuController.java @@ -0,0 +1,101 @@ +package com.mathgenerator.controller; + +import com.mathgenerator.model.Level; +import com.mathgenerator.model.User; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.stage.Stage; +import java.io.IOException; + +public class MainMenuController { + + private User currentUser; + + @FXML private Label welcomeLabel; + @FXML private TextField questionCountField; + @FXML private Label statusLabel; + @FXML private Button logoutButton; + + /** + * 初始化控制器,接收登录成功的用户信息。 + * @param user 当前登录的用户 + */ + public void initData(User user) { + this.currentUser = user; + welcomeLabel.setText("欢迎, " + currentUser.username() + "!"); + } + + @FXML + private void handlePrimaryAction(ActionEvent event) { + startQuiz(Level.PRIMARY); + } + + @FXML + private void handleJuniorHighAction(ActionEvent event) { + startQuiz(Level.JUNIOR_HIGH); + } + + @FXML + private void handleSeniorHighAction(ActionEvent event) { + startQuiz(Level.SENIOR_HIGH); + } + + @FXML + private void handleChangePasswordAction(ActionEvent event) { + // TODO: 跳转到修改密码界面 + statusLabel.setText("修改密码功能待实现。"); + } + + @FXML + private void handleLogoutAction(ActionEvent event) { + // 跳转回登录界面 + loadScene("/com/mathgenerator/view/LoginView.fxml"); + } + + /** + * 验证输入并准备开始答题。 + * @param level 选择的难度 + */ + private void startQuiz(Level level) { + try { + int count = Integer.parseInt(questionCountField.getText()); + if (count < 1 || count > 50) { + statusLabel.setText("题目数量必须在 1 到 50 之间!"); + return; + } + + // 加载答题界面,并传递数据 + FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/QuizView.fxml")); + Parent root = loader.load(); + + QuizController controller = loader.getController(); + controller.initData(currentUser, level, count); + + Stage stage = (Stage) logoutButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle(level.getChineseName() + " - 答题中"); + + } catch (NumberFormatException e) { + statusLabel.setText("请输入有效的题目数量!"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void loadScene(String fxmlPath) { + try { + Stage stage = (Stage) logoutButton.getScene().getWindow(); + Parent root = FXMLLoader.load(getClass().getResource(fxmlPath)); + stage.setScene(new Scene(root)); + stage.setTitle("用户登录"); // 返回登录时重置标题 + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mathgenerator/controller/QuizController.java b/src/main/java/com/mathgenerator/controller/QuizController.java new file mode 100644 index 0000000..abce998 --- /dev/null +++ b/src/main/java/com/mathgenerator/controller/QuizController.java @@ -0,0 +1,144 @@ +package com.mathgenerator.controller; + +import com.mathgenerator.model.ChoiceQuestion; +import com.mathgenerator.model.Level; +import com.mathgenerator.model.User; +import com.mathgenerator.service.PaperService; +import com.mathgenerator.service.strategy.MixedDifficultyStrategy; +import com.mathgenerator.storage.FileManager; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class QuizController { + + // --- 后端服务 --- + private final PaperService paperService; + + // --- 答题状态 --- + private User currentUser; + private List questions; + private List userAnswers = new ArrayList<>(); + private int currentQuestionIndex = 0; + + // --- FXML 控件 --- + @FXML private Label questionNumberLabel; + @FXML private ProgressBar progressBar; + @FXML private Label questionTextLabel; + @FXML private ToggleGroup optionsGroup; + @FXML private RadioButton option1, option2, option3, option4; + @FXML private Button submitButton; + @FXML private Label statusLabel; + + /** + * 构造函数,初始化后端服务 + */ + public QuizController() { + // 这是依赖注入的一种简化形式,在真实项目中会使用框架管理 + FileManager fileManager = new FileManager(); + MixedDifficultyStrategy strategy = new MixedDifficultyStrategy(); + this.paperService = new PaperService(fileManager, strategy); + } + + /** + * 接收从主菜单传递过来的数据,并开始答题 + */ + public void initData(User user, Level level, int questionCount) { + this.currentUser = user; + // 调用后端服务生成题目 + this.questions = paperService.createPaper(user, questionCount, level); + displayCurrentQuestion(); + } + + /** + * 显示当前的题目和选项 + */ + private void displayCurrentQuestion() { + ChoiceQuestion currentQuestion = questions.get(currentQuestionIndex); + + questionNumberLabel.setText(String.format("第 %d / %d 题", currentQuestionIndex + 1, questions.size())); + progressBar.setProgress((double) (currentQuestionIndex + 1) / questions.size()); + questionTextLabel.setText(currentQuestion.questionText()); + + List radioButtons = List.of(option1, option2, option3, option4); + for (int i = 0; i < radioButtons.size(); i++) { + radioButtons.get(i).setText(currentQuestion.options().get(i)); + } + + optionsGroup.selectToggle(null); // 清除上一次的选择 + statusLabel.setText(""); // 清除状态提示 + + if (currentQuestionIndex == questions.size() - 1) { + submitButton.setText("完成答题"); + } + } + + /** + * 处理提交按钮的点击事件 + */ + @FXML + private void handleSubmitButtonAction(ActionEvent event) { + RadioButton selectedRadioButton = (RadioButton) optionsGroup.getSelectedToggle(); + if (selectedRadioButton == null) { + statusLabel.setText("请选择一个答案!"); + return; + } + + // 记录用户答案的索引 + List radioButtons = List.of(option1, option2, option3, option4); + userAnswers.add(radioButtons.indexOf(selectedRadioButton)); + + // 移动到下一题或结束答题 + currentQuestionIndex++; + if (currentQuestionIndex < questions.size()) { + displayCurrentQuestion(); + } else { + // 答题结束,计算分数并跳转到分数界面 + calculateScoreAndShowResults(); + } + } + + /** + * 计算分数并准备跳转到结果页面 + */ + private void calculateScoreAndShowResults() { + int correctCount = 0; + for (int i = 0; i < questions.size(); i++) { + if (userAnswers.get(i) == questions.get(i).correctOptionIndex()) { + correctCount++; + } + } + double score = (double) correctCount / questions.size() * 100; + + // 禁用当前页面的按钮 + submitButton.setDisable(true); + statusLabel.setText("答题已完成,正在为您计算分数..."); + + // 加载分数界面并传递数据 + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/ScoreView.fxml")); + Parent root = loader.load(); + + ScoreController controller = loader.getController(); + controller.initData(currentUser, score); // 将用户和分数传递过去 + + Stage stage = (Stage) submitButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle("答题结果"); + + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mathgenerator/controller/RegisterController.java b/src/main/java/com/mathgenerator/controller/RegisterController.java new file mode 100644 index 0000000..c1a2835 --- /dev/null +++ b/src/main/java/com/mathgenerator/controller/RegisterController.java @@ -0,0 +1,95 @@ +package com.mathgenerator.controller; + +import com.mathgenerator.service.UserService; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +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 RegisterController { + + private final UserService userService = new UserService(); + private String sentCode; // 用于存储已发送的验证码 + + @FXML private TextField usernameField; + @FXML private TextField emailField; + @FXML private TextField verificationCodeField; + @FXML private PasswordField passwordField; + @FXML private PasswordField confirmPasswordField; + @FXML private Button sendCodeButton; + @FXML private Button registerButton; + @FXML private Button backToLoginButton; + @FXML private Label statusLabel; + + @FXML + private void handleSendCodeAction(ActionEvent event) { + String email = emailField.getText(); + if (email.isEmpty() || !email.contains("@")) { + statusLabel.setText("请输入一个有效的邮箱地址!"); + return; + } + // 调用后端服务发送验证码(模拟) + this.sentCode = userService.sendVerificationCode(email); + statusLabel.setText("验证码已发送(请查看控制台输出)。"); + sendCodeButton.setDisable(true); // 防止重复点击 + } + + @FXML + private void handleRegisterAction(ActionEvent event) { + // 1. 字段校验 + if (usernameField.getText().isEmpty() || emailField.getText().isEmpty() || + verificationCodeField.getText().isEmpty() || passwordField.getText().isEmpty()) { + statusLabel.setText("所有字段都不能为空!"); + return; + } + if (!passwordField.getText().equals(confirmPasswordField.getText())) { + statusLabel.setText("两次输入的密码不匹配!"); + return; + } + if (this.sentCode == null || !this.sentCode.equals(verificationCodeField.getText())) { + statusLabel.setText("验证码错误!"); + return; + } + if (!UserService.isPasswordValid(passwordField.getText())) { + statusLabel.setText("密码格式错误!必须为6-10位,且包含大小写字母和数字。"); + return; + } + + // 2. 调用后端服务进行注册 + boolean success = userService.register( + usernameField.getText(), + emailField.getText(), + passwordField.getText() + ); + + // 3. 根据结果更新UI + if (success) { + statusLabel.setText("注册成功!请返回登录。"); + registerButton.setDisable(true); + } else { + statusLabel.setText("注册失败:用户名或邮箱已被占用。"); + } + } + + @FXML + private void handleBackToLoginAction(ActionEvent event) { + loadScene("/com/mathgenerator/view/LoginView.fxml"); + } + + private void loadScene(String fxmlPath) { + try { + Parent root = FXMLLoader.load(getClass().getResource(fxmlPath)); + Stage stage = (Stage) backToLoginButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/mathgenerator/controller/ScoreController.java b/src/main/java/com/mathgenerator/controller/ScoreController.java new file mode 100644 index 0000000..dbcba69 --- /dev/null +++ b/src/main/java/com/mathgenerator/controller/ScoreController.java @@ -0,0 +1,77 @@ +package com.mathgenerator.controller; + +import com.mathgenerator.model.User; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.stage.Stage; +import java.io.IOException; + +public class ScoreController { + + private User currentUser; + + @FXML private Label scoreLabel; + @FXML private Label resultMessageLabel; + @FXML private Button tryAgainButton; + @FXML private Button logoutButton; + + /** + * 初始化控制器,接收答题结果数据 + * @param user 当前用户 + * @param score 最终得分 + */ + public void initData(User user, double score) { + this.currentUser = user; + scoreLabel.setText(String.format("%.2f", score)); + + // 根据分数显示不同的鼓励语 + if (score == 100.0) { + resultMessageLabel.setText("太棒了!你答对了所有题目!"); + } else if (score >= 80) { + resultMessageLabel.setText("非常不错!继续努力!"); + } else if (score >= 60) { + resultMessageLabel.setText("成绩合格,再接再厉!"); + } else { + resultMessageLabel.setText("别灰心,下次会更好的!"); + } + } + + /** + * 处理“再做一组”按钮事件,返回主菜单 + */ + @FXML + private void handleTryAgainAction(ActionEvent event) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/MainMenuView.fxml")); + Parent root = loader.load(); + MainMenuController controller = loader.getController(); + controller.initData(currentUser); // 将用户信息传回主菜单 + + Stage stage = (Stage) tryAgainButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle("主菜单"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 处理“退出登录”按钮事件,返回登录界面 + */ + @FXML + private void handleLogoutAction(ActionEvent event) { + try { + Parent root = FXMLLoader.load(getClass().getResource("/com/mathgenerator/view/LoginView.fxml")); + Stage stage = (Stage) logoutButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.setTitle("用户登录"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file