javadoc补全 #15

Merged
hnu202326010318 merged 13 commits from liguolin_branch into develop 3 months ago

@ -61,25 +61,21 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.mathgenerator.MainApplication</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.mathgenerator.Launcher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

@ -0,0 +1,22 @@
package com.mathgenerator;
/**
*
* <p>
* 使 Maven Shade Plugin JavaFX
* "fat JAR"
* <p>
* {@code main} {@link MainApplication#main(String[])}
* JavaFX
*/
public class Launcher {
/**
*
*
* @param args
*/
public static void main(String[] args) {
MainApplication.main(args);
}
}

@ -4,21 +4,56 @@ import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import java.io.IOException;
/**
* JavaFX
* <p>
* {@link Application} JavaFX
* (Stage) (LoginView.fxml)
*
*/
public class MainApplication extends Application {
/**
* JavaFX
* <p>
* JavaFX
*
* @param primaryStage JavaFX
* @throws IOException FXML I/O
*/
@Override
public void start(Stage primaryStage) throws IOException {
// 启动时加载登录界面
Parent root = FXMLLoader.load(getClass().getResource("/com/mathgenerator/view/LoginView.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("/com/mathgenerator/styles/styles.css").toExternalForm());
primaryStage.setTitle("中小学数学学习软件");
primaryStage.setScene(new Scene(root));
try {
Image appIcon = new Image(getClass().getResourceAsStream("/com/mathgenerator/images/icon.png"));
primaryStage.getIcons().add(appIcon);
} catch (Exception e) {
System.err.println("错误:无法加载应用程序图标!请检查 'icon.png' 是否在 images 文件夹中。");
e.printStackTrace();
}
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
/**
* main
* <p>
* Java {@link #launch(String...)} JavaFX
*
* @param args
*/
public static void main(String[] args) {
launch(args);
}

@ -13,6 +13,10 @@ import javafx.scene.control.PasswordField;
import javafx.stage.Stage;
import java.io.IOException;
/**
* (ChangePasswordView.fxml) FXML
*
*/
public class ChangePasswordController {
private final UserService userService = new UserService();
@ -26,51 +30,96 @@ public class ChangePasswordController {
@FXML private Label statusLabel;
/**
*
*
* FXML
*
* @param user User
*/
public void initData(User user) {
this.currentUser = user;
}
/**
*
*
*
* @param event ActionEvent
*/
@FXML
private void handleConfirmAction(ActionEvent event) {
// 1. 获取输入
String oldPassword = oldPasswordField.getText();
String newPassword = newPasswordField.getText();
String confirmNewPassword = confirmNewPasswordField.getText();
// 2. 输入校验
if (oldPassword.isEmpty() || newPassword.isEmpty() || confirmNewPassword.isEmpty()) {
statusLabel.setText("所有密码字段都不能为空!");
showStatusMessage("所有密码字段都不能为空!", true);
return;
}
if (!newPassword.equals(confirmNewPassword)) {
statusLabel.setText("两次输入的新密码不匹配!");
showStatusMessage("两次输入的新密码不匹配!", true);
return;
}
// 新增校验:新密码不能与当前密码相同
if (oldPassword.equals(newPassword)) {
showStatusMessage("新密码不能与当前密码相同!", true);
return;
}
if (!UserService.isPasswordValid(newPassword)) {
statusLabel.setText("新密码格式错误必须为6-10位且包含大小写字母和数字。");
showStatusMessage("新密码格式错误必须为6-10位且包含大小写字母和数字。", true);
return;
}
// 3. 调用后端服务修改密码
boolean success = userService.changePassword(
currentUser.username(),
oldPassword,
newPassword
);
// 4. 更新UI反馈
if (success) {
statusLabel.setText("密码修改成功!请返回主菜单。");
showStatusMessage("密码修改成功!请返回主菜单。", true);
confirmButton.setDisable(true); // 防止重复点击
} else {
statusLabel.setText("修改失败:当前密码错误。");
showStatusMessage("修改失败:当前密码错误。", true);
}
}
/**
* (statusLabel)
*
* @param message
* @param hasContent
*/
private void showStatusMessage(String message, boolean hasContent) {
statusLabel.setText(message);
updateStatusLabelStyle(hasContent);
}
/**
* CSS
*
* @param hasContent true
*/
private void updateStatusLabelStyle(boolean hasContent) {
if (hasContent) {
// 移除空样式,添加有内容样式
statusLabel.getStyleClass().removeAll("password-status-label");
if (!statusLabel.getStyleClass().contains("password-status-label-with-text")) {
statusLabel.getStyleClass().add("password-status-label-with-text");
}
} else {
// 移除有内容样式,添加空样式
statusLabel.getStyleClass().removeAll("password-status-label-with-text");
if (!statusLabel.getStyleClass().contains("password-status-label")) {
statusLabel.getStyleClass().add("password-status-label");
}
}
}
/**
*
*
*
*
* @param event ActionEvent
*/
@FXML
private void handleBackAction(ActionEvent event) {

@ -16,6 +16,11 @@ import javafx.stage.Stage;
import java.io.IOException;
import java.util.Optional;
import com.mathgenerator.util.ValidationUtils;
/**
* (LoginView.fxml) FXML
*
*/
public class LoginController {
// 依赖注入后端服务
@ -38,8 +43,10 @@ public class LoginController {
private Label statusLabel;
/**
*
* @param event
*
*
*
* @param event ActionEvent
*/
@FXML
private void handleLoginButtonAction(ActionEvent event) {
@ -69,8 +76,10 @@ public class LoginController {
}
/**
*
* @param event
*
*
*
* @param event ActionEvent
*/
@FXML
private void handleRegisterButtonAction(ActionEvent event) {
@ -78,8 +87,10 @@ public class LoginController {
}
/**
*
* @param user
*
* MainMenuController
*
* @param user User
*/
private void loadMainMenu(User user) {
try {
@ -104,8 +115,9 @@ public class LoginController {
}
/**
*
* @param fxmlPath FXML
*
*
* @param fxmlPath FXML
*/
private void loadScene(String fxmlPath) {
try {

@ -13,6 +13,11 @@ import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
/**
* (MainMenuView.fxml) FXML
*
*/
public class MainMenuController {
private User currentUser;
@ -23,68 +28,109 @@ public class MainMenuController {
@FXML private Button logoutButton;
/**
*
* @param user
*
*
* @param user
*/
public void initData(User user) {
this.currentUser = user;
welcomeLabel.setText("欢迎, " + currentUser.username() + "!");
welcomeLabel.setText("🎉 欢迎, " + currentUser.username() + "!"); // 添加图标
// 初始化状态标签
clearStatus();
}
/**
*
*
* @param event
*/
@FXML
private void handlePrimaryAction(ActionEvent event) {
startQuiz(Level.PRIMARY);
}
/**
*
*
* @param event
*/
@FXML
private void handleJuniorHighAction(ActionEvent event) {
startQuiz(Level.JUNIOR_HIGH);
}
/**
*
*
* @param event
*/
@FXML
private void handleSeniorHighAction(ActionEvent event) {
startQuiz(Level.SENIOR_HIGH);
}
/**
*
*
* @param event
*/
@FXML
private void handleChangePasswordAction(ActionEvent event) {
try {
// 1\. 加载 FXML 文件
// 1. 加载 FXML 文件
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/ChangePasswordView.fxml"));
Parent root = loader.load();
// 2\. 获取新界面的控制器
// 2. 获取新界面的控制器
ChangePasswordController controller = loader.getController();
// 3\. 调用控制器的方法,传递当前用户信息
// 3. 调用控制器的方法,传递当前用户信息
controller.initData(currentUser);
// 4\. 显示新场景
// 4. 显示新场景
Stage stage = (Stage) logoutButton.getScene().getWindow();
stage.setScene(new Scene(root));
stage.setTitle("修改密码");
} catch (IOException e) {
e.printStackTrace();
setErrorStatus("加载修改密码界面失败!");
}
}
@FXML
/**
* 退
*
* @param event
*/
@FXML
private void handleLogoutAction(ActionEvent event) {
// 跳转回登录界面
loadScene("/com/mathgenerator/view/LoginView.fxml");
}
/**
*
* @param level
*
*
*
* @param level
*/
private void startQuiz(Level level) {
try {
int count = Integer.parseInt(questionCountField.getText());
if (count < 1 || count > 50) {
statusLabel.setText("题目数量必须在 1 到 50 之间!");
String countText = questionCountField.getText().trim();
// 检查是否为空
if (countText.isEmpty()) {
setErrorStatus("请输入题目数量!");
return;
}
int count = Integer.parseInt(countText);
// 修改范围检查从1-50改为10-30
if (count < 10 || count > 30) {
setErrorStatus("题目数量必须在 10 到 30 之间!");
return;
}
// 清除状态信息
clearStatus();
// 加载答题界面,并传递数据
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/QuizView.fxml"));
Parent root = loader.load();
@ -97,12 +143,36 @@ public class MainMenuController {
stage.setTitle(level.getChineseName() + " - 答题中");
} catch (NumberFormatException e) {
statusLabel.setText("请输入有效的题目数量");
setErrorStatus("请输入有效的数字");
} catch (IOException e) {
e.printStackTrace();
setErrorStatus("加载答题界面失败,请重试!");
}
}
/**
*
*
* @param message
*/
private void setErrorStatus(String message) {
statusLabel.setText(message);
statusLabel.getStyleClass().setAll("status-label");
}
/**
*
*/
private void clearStatus() {
statusLabel.setText("");
statusLabel.getStyleClass().clear();
}
/**
*
*
* @param fxmlPath FXML
*/
private void loadScene(String fxmlPath) {
try {
Stage stage = (Stage) logoutButton.getScene().getWindow();

@ -21,18 +21,18 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* (QuizView.fxml) FXML
*
*/
public class QuizController {
// --- 后端服务 ---
private final PaperService paperService;
// --- 答题状态 ---
private User currentUser;
private List<ChoiceQuestion> questions;
private List<Integer> userAnswers = new ArrayList<>();
private int currentQuestionIndex = 0;
// --- FXML 控件 ---
@FXML private Label questionNumberLabel;
@FXML private ProgressBar progressBar;
@FXML private Label questionTextLabel;
@ -42,34 +42,32 @@ public class QuizController {
@FXML private Label statusLabel;
/**
*
*
* PaperService
*/
public QuizController() {
// 这是依赖注入的一种简化形式,在真实项目中会使用框架管理
FileManager fileManager = new FileManager();
MixedDifficultyStrategy strategy = new MixedDifficultyStrategy();
this.paperService = new PaperService(fileManager, strategy);
}
/**
*
*
*
* @param user
* @param level
* @param questionCount
*/
public void initData(User user, Level level, int questionCount) {
this.currentUser = user;
// 1. 调用后端服务生成题目
this.questions = paperService.createPaper(user, questionCount, level);
// --- 核心修改在这里 ---
// 2. 立即调用后端服务,在后台自动保存生成的试卷
paperService.savePaper(user.username(), this.questions);
// 3. 正常显示第一道题
displayCurrentQuestion();
}
/**
* (ABCD)
*
*
*/
private void displayCurrentQuestion() {
ChoiceQuestion currentQuestion = questions.get(currentQuestionIndex);
@ -79,14 +77,13 @@ public class QuizController {
questionTextLabel.setText(currentQuestion.questionText());
List<RadioButton> radioButtons = List.of(option1, option2, option3, option4);
String[] prefixes = {"A. ", "B. ", "C. ", "D. "}; // 定义选项前缀
String[] prefixes = {"A. ", "B. ", "C. ", "D. "};
for (int i = 0; i < radioButtons.size(); i++) {
// 将前缀和选项文本结合起来
radioButtons.get(i).setText(prefixes[i] + currentQuestion.options().get(i));
}
optionsGroup.selectToggle(null); // 清除上一次的选择
statusLabel.setText(""); // 清除状态提示
optionsGroup.selectToggle(null);
updateStatusLabelStyle(false);
if (currentQuestionIndex == questions.size() - 1) {
submitButton.setText("完成答题");
@ -94,32 +91,54 @@ public class QuizController {
}
/**
*
*
*
*
* @param event
*/
@FXML
private void handleSubmitButtonAction(ActionEvent event) {
RadioButton selectedRadioButton = (RadioButton) optionsGroup.getSelectedToggle();
if (selectedRadioButton == null) {
statusLabel.setText("请选择一个答案!");
updateStatusLabelStyle(true);
return;
}
// 记录用户答案的索引
updateStatusLabelStyle(false);
List<RadioButton> radioButtons = List.of(option1, option2, option3, option4);
userAnswers.add(radioButtons.indexOf(selectedRadioButton));
// 移动到下一题或结束答题
currentQuestionIndex++;
if (currentQuestionIndex < questions.size()) {
displayCurrentQuestion();
} else {
// 答题结束,计算分数并跳转到分数界面
calculateScoreAndShowResults();
}
}
/**
*
* CSS
*
* @param hasContent true
*/
private void updateStatusLabelStyle(boolean hasContent) {
if (hasContent) {
statusLabel.getStyleClass().removeAll("quiz-status-label-empty");
if (!statusLabel.getStyleClass().contains("quiz-status-label-with-text")) {
statusLabel.getStyleClass().add("quiz-status-label-with-text");
}
} else {
statusLabel.getStyleClass().removeAll("quiz-status-label-with-text");
if (!statusLabel.getStyleClass().contains("quiz-status-label-empty")) {
statusLabel.getStyleClass().add("quiz-status-label-empty");
}
}
}
/**
*
*/
private void calculateScoreAndShowResults() {
int correctCount = 0;
@ -130,22 +149,19 @@ public class QuizController {
}
double score = (double) correctCount / questions.size() * 100;
// 禁用当前页面的按钮
submitButton.setDisable(true);
statusLabel.setText("答题已完成,正在为您计算分数...");
updateStatusLabelStyle(true);
// 加载分数界面并传递数据
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/ScoreView.fxml"));
Parent root = loader.load();
ScoreController controller = loader.getController();
controller.initData(currentUser, score); // 将用户和分数传递过去
controller.initData(currentUser, score);
Stage stage = (Stage) submitButton.getScene().getWindow();
stage.setScene(new Scene(root));
stage.setTitle("答题结果");
} catch (IOException e) {
e.printStackTrace();
}

@ -1,6 +1,9 @@
package com.mathgenerator.controller;
import com.mathgenerator.service.UserService;
import com.mathgenerator.util.ValidationUtils;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
@ -11,12 +14,20 @@ import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
import com.mathgenerator.util.ValidationUtils;
/**
* (RegisterView.fxml) FXML
*
*/
public class RegisterController {
private final UserService userService = new UserService();
private String sentCode; // 用于存储已发送的验证码
private String sentCode;
private Timeline countdownTimeline;
private int countdownSeconds = 60;
@FXML private TextField usernameField;
@FXML private TextField emailField;
@ -28,65 +39,123 @@ public class RegisterController {
@FXML private Button backToLoginButton;
@FXML private Label statusLabel;
/**
*
* 60
*
* @param event
*/
@FXML
private void handleSendCodeAction(ActionEvent event) {
String email = emailField.getText();
if (email.isEmpty() || !email.contains("@")) {
statusLabel.setText("请输入一个有效的邮箱地址!");
if (!ValidationUtils.isEmailValid(email)) {
showStatusMessage("请输入一个有效的邮箱地址!", true);
return;
}
// 调用后端服务发送验证码
this.sentCode = userService.sendVerificationCode(email);
// 处理发送结果
if (this.sentCode != null) {
statusLabel.setText("验证码已成功发送,请查收您的邮箱。");
sendCodeButton.setDisable(true); // 防止重复点击
showStatusMessage("验证码已成功发送,请查收您的邮箱。", true);
startCountdown();
} else {
statusLabel.setText("验证码发送失败!请检查配置或联系管理员。");
showStatusMessage("验证码发送失败!请检查配置或联系管理员。", true);
}
}
/**
* 60
*
*/
private void startCountdown() {
sendCodeButton.setDisable(true);
countdownTimeline = new Timeline(new KeyFrame(Duration.seconds(1), e -> {
countdownSeconds--;
sendCodeButton.setText(countdownSeconds + "s后重试");
if (countdownSeconds <= 0) {
countdownTimeline.stop();
sendCodeButton.setDisable(false);
sendCodeButton.setText("发送验证码");
countdownSeconds = 60;
}
}));
countdownTimeline.setCycleCount(60);
countdownTimeline.play();
}
/**
*
*
*
* @param event
*/
@FXML
private void handleRegisterAction(ActionEvent event) {
// 1. 字段校验 (已简化,不再校验密码)
String username = usernameField.getText();
String email = emailField.getText();
if (!ValidationUtils.isUsernameValid(username) || !ValidationUtils.isEmailValid(email) ||
verificationCodeField.getText().isEmpty()) {
statusLabel.setText("所有字段都不能为空且格式正确!");
showStatusMessage("所有字段都不能为空且格式正确!", true);
return;
}
if (this.sentCode == null || !this.sentCode.equals(verificationCodeField.getText())) {
statusLabel.setText("验证码错误!");
showStatusMessage("验证码错误!", true);
return;
}
// 2. 调用后端服务进行无密码注册
boolean success = userService.register(username, email);
// 3. 根据结果更新UI或跳转
if (success) {
statusLabel.setText("注册成功!请设置您的密码。");
// 成功后,加载设置密码界面,并传递用户名
showStatusMessage("注册成功!请设置您的密码。", true);
loadSetPasswordScene(username);
} else {
statusLabel.setText("注册失败:用户名或邮箱已被占用。");
showStatusMessage("注册失败:用户名或邮箱已被占用。", true);
}
}
/**
* ()
*
*
* @param message
* @param hasContent true
*/
private void showStatusMessage(String message, boolean hasContent) {
statusLabel.setText(message);
updateStatusLabelStyle(hasContent);
}
/**
* CSS
*
* @param hasContent true
*/
private void updateStatusLabelStyle(boolean hasContent) {
if (hasContent) {
statusLabel.getStyleClass().removeAll("register-status-label");
if (!statusLabel.getStyleClass().contains("register-status-label-with-text")) {
statusLabel.getStyleClass().add("register-status-label-with-text");
}
} else {
statusLabel.getStyleClass().removeAll("register-status-label-with-text");
if (!statusLabel.getStyleClass().contains("register-status-label")) {
statusLabel.getStyleClass().add("register-status-label");
}
}
}
/**
*
*
* @param username
*/
private void loadSetPasswordScene(String username) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/SetPasswordView.fxml"));
Parent root = loader.load();
SetPasswordController controller = loader.getController();
controller.initData(username); // 将用户名传递给新界面的控制器
controller.initData(username);
Stage stage = (Stage) registerButton.getScene().getWindow();
stage.setScene(new Scene(root));
@ -96,11 +165,21 @@ public class RegisterController {
}
}
/**
*
*
* @param event
*/
@FXML
private void handleBackToLoginAction(ActionEvent event) {
loadScene("/com/mathgenerator/view/LoginView.fxml");
}
/**
* FXML
*
* @param fxmlPath FXML
*/
private void loadScene(String fxmlPath) {
try {
Parent root = FXMLLoader.load(getClass().getResource(fxmlPath));
@ -110,4 +189,13 @@ public class RegisterController {
e.printStackTrace();
}
}
/**
*
*/
public void cleanup() {
if (countdownTimeline != null) {
countdownTimeline.stop();
}
}
}

@ -11,6 +11,10 @@ import javafx.scene.control.Label;
import javafx.stage.Stage;
import java.io.IOException;
/**
* (ScoreView.fxml) FXML
*
*/
public class ScoreController {
private User currentUser;
@ -21,15 +25,15 @@ public class ScoreController {
@FXML private Button logoutButton;
/**
*
* @param user
* @param score
*
*
* @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) {
@ -42,7 +46,10 @@ public class ScoreController {
}
/**
*
*
* 便
*
* @param event ActionEvent
*/
@FXML
private void handleTryAgainAction(ActionEvent event) {
@ -50,7 +57,7 @@ public class ScoreController {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/MainMenuView.fxml"));
Parent root = loader.load();
MainMenuController controller = loader.getController();
controller.initData(currentUser); // 将用户信息传回主菜单
controller.initData(currentUser);
Stage stage = (Stage) tryAgainButton.getScene().getWindow();
stage.setScene(new Scene(root));
@ -61,7 +68,10 @@ public class ScoreController {
}
/**
* 退
* 退
*
*
* @param event ActionEvent
*/
@FXML
private void handleLogoutAction(ActionEvent event) {

@ -15,6 +15,10 @@ import javafx.stage.Stage;
import java.io.IOException;
/**
* (SetPasswordView.fxml) FXML
*
*/
public class SetPasswordController {
private final UserService userService = new UserService();
@ -27,48 +31,88 @@ public class SetPasswordController {
@FXML private Label statusLabel;
/**
*
*
*
* @param username
*/
public void initData(String username) {
this.username = username;
promptLabel.setText("为您的账户 " + username + " 设置密码");
}
/**
*
*
*
*
* @param event
*/
@FXML
private void handleConfirmAction(ActionEvent event) {
String newPassword = newPasswordField.getText();
String confirmPassword = confirmPasswordField.getText();
if (!newPassword.equals(confirmPassword)) {
statusLabel.setText("两次输入的密码不匹配!");
showStatusMessage("两次输入的密码不匹配!", true);
return;
}
if (!ValidationUtils.isPasswordValid(newPassword)) {
statusLabel.setText("新密码格式错误必须为6-10位且包含大小写字母和数字。");
showStatusMessage("新密码格式错误必须为6-10位且包含大小写字母和数字。", true);
return;
}
// 调用后端服务设置密码
boolean success = userService.setPassword(this.username, newPassword);
if (success) {
statusLabel.setText("密码设置成功!正在进入主菜单...");
// 密码设置成功后,获取完整的用户信息并直接跳转到主菜单
showStatusMessage("密码设置成功!正在进入主菜单...", true);
userService.findUserByUsername(this.username).ifPresent(this::loadMainMenu);
} else {
statusLabel.setText("密码设置失败,请稍后重试或重新注册。");
showStatusMessage("密码设置失败,请稍后重试或重新注册。", true);
}
}
/**
*
*
* @param message
* @param hasContent true
*/
private void showStatusMessage(String message, boolean hasContent) {
statusLabel.setText(message);
updateStatusLabelStyle(hasContent);
}
/**
* CSS
*
* @param hasContent true
*/
private void updateStatusLabelStyle(boolean hasContent) {
if (hasContent) {
statusLabel.getStyleClass().removeAll("setpassword-status-label");
if (!statusLabel.getStyleClass().contains("setpassword-status-label-with-text")) {
statusLabel.getStyleClass().add("setpassword-status-label-with-text");
}
} else {
statusLabel.getStyleClass().removeAll("setpassword-status-label-with-text");
if (!statusLabel.getStyleClass().contains("setpassword-status-label")) {
statusLabel.getStyleClass().add("setpassword-status-label");
}
}
}
/**
* ()
*
*
* @param user User
*/
private void loadMainMenu(User user) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/mathgenerator/view/MainMenuView.fxml"));
Parent root = loader.load();
MainMenuController controller = loader.getController();
controller.initData(user); // 将完整的User对象传递给主菜单
controller.initData(user);
Stage stage = (Stage) confirmButton.getScene().getWindow();
stage.setScene(new Scene(root));

@ -6,58 +6,56 @@ import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* ( - )
*
*
* <p>
* {@link PrimarySchoolGenerator}
*
*/
public class JuniorHighSchoolGenerator extends PrimarySchoolGenerator {
private static final int[] PERFECT_SQUARES = {1, 4, 9, 16, 25, 36, 49, 64, 81, 100};
/**
*
* <p>
*
*
* 退
*
* @return {@link ChoiceQuestion}
*/
@Override
public ChoiceQuestion generateSingleQuestion() {
ThreadLocalRandom random = ThreadLocalRandom.current();
int operandCount = random.nextInt(2, 5); // 2到4个操作数
int operandCount = random.nextInt(2, 5);
// 1. 生成基础的表达式组件列表
List<String> parts = new ArrayList<>();
// 使用 getOperand() 和 getRandomOperator() 这些继承自父类的方法
parts.add(String.valueOf(getOperand()));
for (int i = 1; i < operandCount; i++) {
parts.add(getRandomOperator());
parts.add(String.valueOf(getOperand()));
}
// 2. 结构化地插入初中特色运算
int modificationIndex = random.nextInt(operandCount) * 2; // 随机选择一个操作数的位置
int modificationIndex = random.nextInt(operandCount) * 2;
boolean useSquare = random.nextBoolean();
if (useSquare) {
// 平方策略:直接在数字后附加平方符号
parts.set(modificationIndex, parts.get(modificationIndex) + "²");
} else {
// 开根号策略:用一个完美的开根号表达式替换整个数字
int perfectSquare = PERFECT_SQUARES[random.nextInt(PERFECT_SQUARES.length)];
parts.set(modificationIndex, "√" + perfectSquare);
}
// 3. (可选)为增强后的表达式添加括号
if (operandCount > 2 && random.nextBoolean()) {
super.addParentheses(parts); // 调用父类的protected方法
super.addParentheses(parts);
}
String finalQuestionText = String.join(" ", parts);
// 4. 计算答案
double finalCorrectAnswer;
try {
Object result = evaluateExpression(finalQuestionText);
finalCorrectAnswer = ((Number) result).doubleValue();
} catch (Exception e) {
// 发生意外,安全返回一个小学题
return super.generateSingleQuestion();
}
// 5. 生成选项
List<String> options = generateDecimalOptions(finalCorrectAnswer);
int correctIndex = options.indexOf(formatNumber(finalCorrectAnswer));

@ -1,6 +1,7 @@
package com.mathgenerator.generator;
import com.mathgenerator.model.ChoiceQuestion; // 导入新模型
import com.mathgenerator.model.ChoiceQuestion;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@ -10,11 +11,14 @@ import java.util.concurrent.ThreadLocalRandom;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.text.DecimalFormat;
/**
*
* + - * / ()
*
* <p>
*
*
*
* @see QuestionGenerator
*/
public class PrimarySchoolGenerator implements QuestionGenerator {
@ -22,7 +26,11 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
/**
*
* @return ChoiceQuestion
* <p>
* 24
* {@link ChoiceQuestion}
*
* @return {@code ChoiceQuestion}
*/
@Override
public ChoiceQuestion generateSingleQuestion() {
@ -65,9 +73,13 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
}
/**
* (13)
* @param correctAnswer
* @return
*
* <p>
* 110
*
*
* @param correctAnswer
* @return
*/
protected List<String> generateOptions(int correctAnswer) {
ThreadLocalRandom random = ThreadLocalRandom.current();
@ -88,28 +100,28 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
}
/**
* 使JVM ()
* @param expression
* @return (IntegerDouble)
* @throws ScriptException
* 使JVM
* <p>
* Rhino
*
* @param expression " (3 + 5) * 2 "
* @return {@code Integer} {@code Double}
* @throws ScriptException
* @throws IllegalStateException Rhino JavaScript
*/
protected Object evaluateExpression(String expression) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
// --- 核心修改在这里 ---
// 使用 "rhino" 作为引擎名称这是Rhino引擎的官方名称
ScriptEngine engine = manager.getEngineByName("rhino");
if (engine == null) {
// 增加一个健壮性检查,如果引擎还是没找到,就给出清晰的错误提示
throw new IllegalStateException("错误找不到Rhino JavaScript引擎。请检查pom.xml中是否已添加rhino-engine的依赖。");
}
// Rhino不需要预定义函数可以直接计算
// 预处理表达式以兼容Rhino引擎的数学函数
String script = expression.replaceAll("(\\d+(\\.\\d+)?)²", "Math.pow($1, 2)")
.replaceAll("√(\\d+(\\.\\d+)?)", "Math.sqrt($1)")
.replaceAll("(\\d+)°", " * (Math.PI / 180)"); // Rhino对角度计算的语法要求更严格
.replaceAll("(\\d+)°", " * (Math.PI / 180)");
// 为了让sin/cos/tan能正确计算需要特殊处理
script = script.replaceAll("sin\\(", "Math.sin(")
.replaceAll("cos\\(", "Math.cos(")
.replaceAll("tan\\(", "Math.tan(");
@ -118,24 +130,27 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
}
/**
*
* @param number
* @return
* double
* <p>
*
*
* @param number
* @return
*/
protected String formatNumber(double number) {
if (number == (long) number) {
return String.format("%d", (long) number); // 如果是整数,不显示小数位
return String.format("%d", (long) number);
} else {
// 使用DecimalFormat来去除末尾多余的0
DecimalFormat df = new DecimalFormat("#.##");
return df.format(number);
}
}
/**
*
* @param correctAnswer
* @return
*
*
* @param correctAnswer
* @return
*/
protected List<String> generateDecimalOptions(double correctAnswer) {
ThreadLocalRandom random = ThreadLocalRandom.current();
@ -143,8 +158,7 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
options.add(formatNumber(correctAnswer));
while (options.size() < 4) {
double delta = random.nextDouble(1, 11); // 答案加减1-10之间的随机小数
// 随机决定是加还是减
double delta = random.nextDouble(1, 11);
double distractor = random.nextBoolean() ? correctAnswer + delta : correctAnswer - delta;
options.add(formatNumber(distractor));
}
@ -154,14 +168,29 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
return sortedOptions;
}
/**
* 1100
*
* @return
*/
protected int getOperand() {
return ThreadLocalRandom.current().nextInt(1, 101);
}
/**
* {@code OPERATORS}
*
* @return "+""-""*" "/"
*/
protected String getRandomOperator() {
return OPERATORS[ThreadLocalRandom.current().nextInt(OPERATORS.length)];
}
/**
*
*
* @param parts
*/
protected void addParentheses(List<String> parts) {
ThreadLocalRandom random = ThreadLocalRandom.current();
int startOperandIndex = random.nextInt(parts.size() / 2);
@ -171,7 +200,4 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
parts.add(endIndex + 1, ")");
parts.add(startIndex, "(");
}
}

@ -1,14 +1,22 @@
package com.mathgenerator.generator;
import com.mathgenerator.model.ChoiceQuestion; // 导入新模型
import com.mathgenerator.model.ChoiceQuestion;
/**
*
*
* <p>
*
*
*/
public interface QuestionGenerator {
/**
*
* @return ChoiceQuestion
* <p>
*
* {@link ChoiceQuestion}
*
* @return {@code ChoiceQuestion}
*/
ChoiceQuestion generateSingleQuestion();
}

@ -7,18 +7,24 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
*
*
* <p>
*
*
*
* @see QuestionGenerator
*/
public class SafePrimarySchoolGenerator implements QuestionGenerator {
/**
*
* @return ChoiceQuestion
*
* <p>
* {@code generateSafeExpression}
* {@link ChoiceQuestion}
*
* @return {@code ChoiceQuestion}
*/
@Override
public ChoiceQuestion generateSingleQuestion() {
@ -34,12 +40,21 @@ public class SafePrimarySchoolGenerator implements QuestionGenerator {
}
/**
*
* (Record)
*
* @param parts
* @param value
* @param operandsUsed 使
*/
private record Term(List<String> parts, int value, int operandsUsed) {}
/**
*
*
* <p>
*
*
* @param operandBudget
* @return {@code Term}
*/
private Term generateSafeExpression(int operandBudget) {
ThreadLocalRandom random = ThreadLocalRandom.current();
@ -72,7 +87,12 @@ public class SafePrimarySchoolGenerator implements QuestionGenerator {
}
/**
* (Term)
* (Term)
* <p>
*
*
* @param operandsRemaining
* @return {@code Term}
*/
private Term generateTerm(int operandsRemaining) {
ThreadLocalRandom random = ThreadLocalRandom.current();
@ -91,7 +111,12 @@ public class SafePrimarySchoolGenerator implements QuestionGenerator {
}
/**
*
*
* <p>
*
*
* @param operandsRemaining
* @return {@code Term}
*/
private Term generateSimpleTerm(int operandsRemaining) {
ThreadLocalRandom random = ThreadLocalRandom.current();
@ -118,6 +143,12 @@ public class SafePrimarySchoolGenerator implements QuestionGenerator {
return new Term(parts, termValue, operandsUsed);
}
/**
*
*
* @param number
* @return
*/
private List<Integer> getDivisors(int number) {
List<Integer> divisors = new ArrayList<>();
for (int i = 1; i <= number; i++) {
@ -127,7 +158,12 @@ public class SafePrimarySchoolGenerator implements QuestionGenerator {
}
/**
* (13)
*
* <p>
*
*
* @param correctAnswer
* @return
*/
private List<String> generateOptions(int correctAnswer) {
ThreadLocalRandom random = ThreadLocalRandom.current();
@ -137,8 +173,7 @@ public class SafePrimarySchoolGenerator implements QuestionGenerator {
while (options.size() < 4) {
int delta = random.nextInt(1, 11);
int distractor = random.nextBoolean() ? correctAnswer + delta : correctAnswer - delta;
// 确保干扰项非负
if (distractor >= 0) {
if (distractor >= 0) { // 确保干扰项非负
options.add(String.valueOf(distractor));
}
}

@ -6,8 +6,10 @@ import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
/**
* ( - )
*
*
* <p>
* {@link JuniorHighSchoolGenerator}
*
*/
public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator {
@ -24,6 +26,14 @@ public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator {
// 将Map的键转换为数组方便随机选取
private static final String[] TRIG_KEYS = TRIG_TERMS.keySet().toArray(new String[0]);
/**
*
* <p>
*
*
*
* @return {@link ChoiceQuestion}
*/
@Override
public ChoiceQuestion generateSingleQuestion() {
// 1. 先生成一个保证可计算的、高性能的初中选择题,作为基础

@ -3,10 +3,14 @@ package com.mathgenerator.model;
import java.util.List;
/**
*
* @param questionText
* @param options
* @param correctOptionIndex (0-3)
* (Record)
* <p>
* (Record)
*
*
* @param questionText
* @param options (List)
* @param correctOptionIndex {@code options} 0 3
*/
public record ChoiceQuestion(String questionText, List<String> options, int correctOptionIndex) {
}

@ -1,14 +1,29 @@
package com.mathgenerator.model;
/**
*
* (Enum)
* <p>
*
*
*/
public enum Level {
/**
*
*/
PRIMARY("小学"),
/**
*
*/
JUNIOR_HIGH("初中"),
/**
*
*/
SENIOR_HIGH("高中");
private final String chineseName;
/**
*
*
@ -17,10 +32,11 @@ public enum Level {
Level(String chineseName) {
this.chineseName = chineseName;
}
/**
*
*
*
* @return
* @return (, "小学")
*/
public String getChineseName() {
return chineseName;

@ -1,10 +1,14 @@
package com.mathgenerator.model;
/**
* (Record)
* @param username
* @param email ()
* @param password
* (Record)
* <p>
* (Record)
*
*
* @param username
* @param email
* @param password {@code null}
*/
public record User(String username, String email, String password) {
}

@ -5,6 +5,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
*
* <p>
* {@code config.properties}
* SMTP
*
*/
public class EmailConfig {
private static final Properties properties = new Properties();
@ -17,18 +24,38 @@ public class EmailConfig {
}
}
/**
* SMTP
*
* @return SMTP
*/
public static String getHost() {
return properties.getProperty("smtp.host");
}
/**
* SMTP
*
* @return SMTP
*/
public static int getPort() {
return Integer.parseInt(properties.getProperty("smtp.port"));
}
/**
* SMTP
*
* @return SMTP
*/
public static String getUsername() {
return properties.getProperty("smtp.username");
}
/**
* SMTP
*
* @return SMTP
*/
public static String getPassword() {
return properties.getProperty("smtp.password");
}

@ -2,7 +2,7 @@ package com.mathgenerator.service;
import com.mathgenerator.model.User;
import com.mathgenerator.model.Level;
import com.mathgenerator.model.ChoiceQuestion; // 导入新模型
import com.mathgenerator.model.ChoiceQuestion;
import com.mathgenerator.service.strategy.PaperStrategy;
import com.mathgenerator.storage.FileManager;
import java.io.IOException;
@ -12,37 +12,48 @@ import java.util.List;
import java.util.Set;
/**
* ChoiceQuestion
*
* <p>
*
*
*/
public class PaperService {
private final FileManager fileManager;
private final PaperStrategy paperStrategy;
/**
* PaperService
*
* @param fileManager {@link FileManager}
* @param paperStrategy {@link PaperStrategy}
*/
public PaperService(FileManager fileManager, PaperStrategy paperStrategy) {
this.fileManager = fileManager;
this.paperStrategy = paperStrategy;
}
/**
*
* @param user
* @param count
* @param currentLevel
* @return
*
* <p>
*
*
*
*
* @param user
* @param count
* @param currentLevel
* @return {@link ChoiceQuestion}
*/
public List<ChoiceQuestion> createPaper(User user, int count, Level currentLevel) {
// 查重集合现在存储题干字符串
Set<String> existingQuestionTexts = fileManager.loadExistingQuestions(user.username());
List<ChoiceQuestion> newPaper = new ArrayList<>();
Set<String> generatedInSession = new HashSet<>();
System.out.println("正在根据策略生成选择题,请稍候...");
while (newPaper.size() < count) {
// 1. 生成的是ChoiceQuestion对象
ChoiceQuestion question = paperStrategy.selectGenerator(currentLevel).generateSingleQuestion();
String questionText = question.questionText(); // 提取题干用于查重
String questionText = question.questionText();
// 2. 使用题干进行查重
if (!existingQuestionTexts.contains(questionText) && !generatedInSession.contains(questionText)) {
newPaper.add(question);
generatedInSession.add(questionText);
@ -52,9 +63,10 @@ public class PaperService {
}
/**
*
* @param username
* @param paper
*
*
* @param username
* @param paper {@link ChoiceQuestion}
*/
public void savePaper(String username, List<ChoiceQuestion> paper) {
try {

@ -1,10 +1,13 @@
package com.mathgenerator.service;
import com.google.gson.Gson;
import java.util.Objects;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.mathgenerator.model.User;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
@ -17,25 +20,35 @@ import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;
/**
*
* <p>
*
* JSON
*/
public class UserService {
private static final Path USER_FILE_PATH = Paths.get("users.json");
// 密码策略: 6-10位, 必须包含大小写字母和数字
private static final Pattern PASSWORD_PATTERN =
Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{6,10}$");
private Map<String, User> userDatabase;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
/**
* UserService
* {@code users.json}
*/
public UserService() {
this.userDatabase = loadUsersFromFile();
}
/**
* {@code users.json} Map
* Map
*
* @return {@code ConcurrentHashMap}
*/
private Map<String, User> loadUsersFromFile() {
// 如果文件不存在直接返回一个空的Map不再创建默认用户
if (!Files.exists(USER_FILE_PATH)) {
return new ConcurrentHashMap<>();
}
@ -43,7 +56,6 @@ public class UserService {
try (FileReader reader = new FileReader(USER_FILE_PATH.toFile())) {
Type type = new TypeToken<Map<String, User>>() {}.getType();
Map<String, User> loadedUsers = gson.fromJson(reader, type);
// 如果文件为空或格式错误也返回一个空的Map
return loadedUsers != null ? new ConcurrentHashMap<>(loadedUsers) : new ConcurrentHashMap<>();
} catch (IOException e) {
System.err.println("错误:加载用户文件失败 - " + e.getMessage());
@ -51,6 +63,9 @@ public class UserService {
}
}
/**
* {@code users.json}
*/
private void saveUsers() {
try (FileWriter writer = new FileWriter(USER_FILE_PATH.toFile())) {
gson.toJson(this.userDatabase, writer);
@ -59,70 +74,72 @@ public class UserService {
}
}
/**
*
*
* @param username
* @return {@link User} {@code Optional}
*/
public Optional<User> findUserByUsername(String username) {
return Optional.ofNullable(this.userDatabase.get(username));
}
/**
*
*
* @param username
* @param password
* @return {@link User} {@code Optional} {@code Optional}
*/
public Optional<User> login(String username, String password) {
return findUserByUsername(username)
.filter(user -> user.password().equals(password));
}
/**
* ()
* @param email
* @return 6, null
* 6
*
* @param email
* @return 6 {@code null}
*/
public String sendVerificationCode(String email) {
String code = String.format("%06d", ThreadLocalRandom.current().nextInt(100000, 1000000));
try {
Email mail = new SimpleEmail();
// 1. 设置SMTP服务器信息
mail.setHostName(EmailConfig.getHost());
mail.setSmtpPort(EmailConfig.getPort());
mail.setAuthentication(EmailConfig.getUsername(), EmailConfig.getPassword());
mail.setSSLOnConnect(true); // 开启SSL加密
// 2. 设置邮件内容
mail.setFrom(EmailConfig.getUsername()); // 发件人
mail.setSubject("【数学学习软件】您的注册验证码"); // 邮件主题
mail.setMsg("您好!\n\n感谢您注册数学学习软件。您的验证码是" + code + "\n\n请在5分钟内使用。"); // 邮件正文
mail.addTo(email); // 收件人
// 3. 发送邮件
mail.setSSLOnConnect(true);
mail.setFrom(EmailConfig.getUsername());
mail.setSubject("【数学学习软件】您的注册验证码");
mail.setMsg("您好!\n\n感谢您注册数学学习软件。您的验证码是" + code + "\n\n请在5分钟内使用。");
mail.addTo(email);
mail.send();
System.out.println("验证码邮件已成功发送至: " + email);
return code;
} catch (EmailException e) {
System.err.println("错误:发送验证码邮件失败!请检查您的 config.properties 配置或网络连接。");
e.printStackTrace();
return null; // 发送失败
return null;
}
}
/**
* ()
* @param username
* @param email
* @return true, false
*
*
* @param username
* @param email
* @return {@code true} {@code false}
*/
public boolean register(String username, String email) {
if (userDatabase.containsKey(username)) {
return false; // 用户名已存在
}
// 检查数据库中已存在的用户的email是否与新email相同
// 使用 email.equals(u.email()) 可以安全地处理 u.email() 为 null 的情况
if (userDatabase.values().stream()
.anyMatch(u -> email.equals(u.email()))) {
if (userDatabase.values().stream().anyMatch(u -> email.equals(u.email()))) {
return false; // 邮箱已存在
}
// --- 核心修正在这里 ---
// 创建用户时,密码字段设为 null表示该用户处于“待设置密码”状态
User newUser = new User(username, email, null);
userDatabase.put(username, newUser);
saveUsers();
@ -130,36 +147,43 @@ public class UserService {
}
/**
* ()
* @param username
* @param password
* @return true, false
*
* {@code null}
*
* @param username
* @param password
* @return {@code true} {@code false}
*/
public boolean setPassword(String username, String password) {
return findUserByUsername(username)
.map(user -> {
// 只有当用户当前密码为 null 时才允许设置
if (user.password() == null) {
User updatedUser = new User(user.username(), user.email(), password);
userDatabase.put(username, updatedUser);
saveUsers();
return true;
}
return false; // 用户已经有密码,不能通过此方法设置
return false;
}).orElse(false);
}
/**
*
* @param password
* @return true
*
*
* @param password
* @return {@code true} {@code false}
*/
public static boolean isPasswordValid(String password) {
return password != null && PASSWORD_PATTERN.matcher(password).matches();
}
/**
*
* @return true
*
*
* @param username
* @param oldPassword
* @param newPassword
* @return {@code true} {@code false}
*/
public boolean changePassword(String username, String oldPassword, String newPassword) {
return findUserByUsername(username)

@ -6,29 +6,39 @@ import java.util.concurrent.ThreadLocalRandom;
/**
*
* ()
* <p>
* 使
*
*
*/
public class MixedDifficultyStrategy implements PaperStrategy {
// 持有所有可能的生成器
// 持有所有可能的生成器实例
private final QuestionGenerator primaryGenerator = new PrimarySchoolGenerator();
private final QuestionGenerator safePrimaryGenerator = new SafePrimarySchoolGenerator(); // 新增
private final QuestionGenerator juniorHighGenerator = new JuniorHighSchoolGenerator();
private final QuestionGenerator seniorHighGenerator = new SeniorHighSchoolGenerator();
/**
*
* <p>
* - ****: 100% 使 {@link SafePrimarySchoolGenerator}
* - ****: 70% 使 {@link JuniorHighSchoolGenerator}30% 使 {@link PrimarySchoolGenerator}
* - ****: 60% 使 {@link SeniorHighSchoolGenerator}30% 使 {@link JuniorHighSchoolGenerator}10% 使 {@link PrimarySchoolGenerator}
*
* @param mainLevel
* @return {@link QuestionGenerator}
*/
@Override
public QuestionGenerator selectGenerator(Level mainLevel) {
double randomValue = ThreadLocalRandom.current().nextDouble();
return switch (mainLevel) {
// 当主难度是小学时100%使用“安全”的生成器
case PRIMARY -> safePrimaryGenerator;
case JUNIOR_HIGH -> {
// 初中试卷70%初中难度30%使用“不安全”的小学难度(允许负数)
if (randomValue < 0.7) yield juniorHighGenerator;
else yield primaryGenerator;
}
case SENIOR_HIGH -> {
// 高中试卷60%高中30%初中10%使用“不安全”的小学难度
if (randomValue < 0.6) yield seniorHighGenerator;
else if (randomValue < 0.9) yield juniorHighGenerator;
else yield primaryGenerator;

@ -5,14 +5,17 @@ import com.mathgenerator.model.Level;
/**
*
*
* <p>
*
*
*/
public interface PaperStrategy {
/**
*
*
*
* @param mainLevel
* @return QuestionGenerator
* @param mainLevel ({@link Level})
* @return {@link QuestionGenerator}
*/
QuestionGenerator selectGenerator(Level mainLevel);
}

@ -18,12 +18,30 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Stream
*
* <p>
*
* <ul>
* <li></li>
* <li></li>
* </ul>
* Stream
*/
public class FileManager {
private static final Path BASE_PATH = Paths.get("generated_papers");
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
/**
*
* <p>
* {@code .txt}
*
*
* @param username
* @param paperContent {@link ChoiceQuestion}
* @return
* @throws IOException I/O
*/
public String savePaper(String username, List<ChoiceQuestion> paperContent) throws IOException {
Path userDir = BASE_PATH.resolve(username);
Files.createDirectories(userDir);
@ -55,6 +73,15 @@ public class FileManager {
return filePath.toString();
}
/**
*
* <p>
* {@code .txt}
* Set
*
* @param username
* @return {@code Set<String>}
*/
public Set<String> loadExistingQuestions(String username) {
Path userDir = BASE_PATH.resolve(username);
if (!Files.exists(userDir)) {
@ -64,8 +91,6 @@ public class FileManager {
try (Stream<Path> stream = Files.walk(userDir)) {
return stream
.filter(file -> !Files.isDirectory(file) && file.toString().endsWith(".txt"))
// --- 核心修改在这里 (Part 1) ---
// flatMap现在操作的是一个由List生成的、全新的、开放的流
.flatMap(file -> readQuestionTextsFromTxtFile(file).stream())
.collect(Collectors.toSet());
} catch (IOException e) {
@ -75,13 +100,14 @@ public class FileManager {
}
/**
* () .txt
* @param file (Path)
* @return (List)
*
* <p>
* 使 "数字."
*
* @param file {@link Path}
* @return (List)
*/
private List<String> readQuestionTextsFromTxtFile(Path file) {
// --- 核心修改在这里 (Part 2) ---
// try-with-resources现在可以安全地关闭流因为它在方法返回前已经被 .collect() 完全消耗了
try (Stream<String> lines = Files.lines(file)) {
Pattern questionPattern = Pattern.compile("^\\d+\\.\\s+(.*)");
return lines
@ -89,10 +115,10 @@ public class FileManager {
.map(questionPattern::matcher)
.filter(Matcher::matches)
.map(matcher -> matcher.group(1).trim())
.collect(Collectors.toList()); // 将流的结果收集到一个List中
.collect(Collectors.toList());
} catch (IOException e) {
System.err.println("错误:读取文件 " + file + " 失败 - " + e.getMessage());
return Collections.emptyList(); // 发生错误时返回一个空列表
return Collections.emptyList();
}
}
}

@ -4,7 +4,10 @@ import java.util.regex.Pattern;
/**
*
*
* <p>
*
*
*
*/
public final class ValidationUtils {
@ -16,14 +19,27 @@ public final class ValidationUtils {
private static final Pattern USERNAME_NO_WHITESPACE_PATTERN =
Pattern.compile("^\\S+$");
// 私有构造函数,防止这个工具类被实例化
// 邮箱策略:使用标准的正则表达式进行格式校验
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$");
/**
*
*/
private ValidationUtils() {}
/**
*
*
* @param username
* @return true, false
* <p>
*
* <ul>
* <li></li>
* <li></li>
* </ul>
*
* @param username
* @return {@code true} {@code false}
*/
public static boolean isUsernameValid(String username) {
if (username == null || username.isEmpty()) {
@ -33,20 +49,33 @@ public final class ValidationUtils {
}
/**
*
* @param password
* @return true
*
* <p>
*
* <ul>
* <li> 6 10 </li>
* <li></li>
* </ul>
*
* @param password
* @return {@code true} {@code false}
*/
public static boolean isPasswordValid(String password) {
return password != null && PASSWORD_PATTERN.matcher(password).matches();
}
/**
* ()
* @param email
* @return true
*
* <p>
* 使 (, "user@example.com")
*
* @param email
* @return {@code true} {@code false}
*/
public static boolean isEmailValid(String email) {
return email != null && !email.isEmpty() && email.contains("@");
if (email == null || email.isEmpty()) {
return false;
}
return EMAIL_PATTERN.matcher(email).matches();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

@ -0,0 +1,955 @@
/* ===== 统一界面尺寸 ===== */
.root-container {
-fx-pref-width: 400px;
-fx-pref-height: 700px;
-fx-min-width: 400px;
-fx-min-height: 700px;
-fx-max-width: 400px;
-fx-max-height: 700px;
}
/* ===== 登录界面专用样式 - 淡黄色和淡紫色主题 ===== */
.login-background {
-fx-background-image: url('../images/background.png'); /* 修改 */
-fx-background-size: cover;
-fx-background-position: center;
-fx-background-repeat: no-repeat;
-fx-background-color: linear-gradient(to bottom right, #fff9c4, #f3e5f5);
}
.login-glass-panel {
-fx-background-color: rgba(255, 255, 255, 0.85);
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-border-color: rgba(186, 104, 200, 0.3);
-fx-border-width: 1px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.15), 20, 0.3, 0, 6);
}
.login-title {
-fx-text-fill: #7b1fa2;
-fx-font-weight: bold;
-fx-font-size: 26px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.2), 4, 0.5, 2, 2);
}
.login-textfield {
-fx-background-color: rgba(255, 255, 255, 0.95);
-fx-border-color: rgba(186, 104, 200, 0.5);
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 12px 16px;
-fx-font-size: 14px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 6, 0.2, 2, 2);
}
.login-textfield:focused {
-fx-border-color: rgba(156, 39, 176, 0.8);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.3), 8, 0.3, 2, 2);
}
.login-primary-button {
-fx-background-color: linear-gradient(to bottom, #ba68c8, #ab47bc);
-fx-text-fill: white;
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-border-color: #ab47bc;
-fx-border-width: 1px;
-fx-padding: 12px 24px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.4), 8, 0.3, 2, 2);
}
.login-primary-button:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ba68c8);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.6), 10, 0.4, 3, 3);
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
}
.login-secondary-button {
-fx-background-color: linear-gradient(to bottom, #fff59d, #fff176);
-fx-text-fill: #7b1fa2;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-border-color: #fff176;
-fx-border-width: 1px;
-fx-padding: 10px 20px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(255, 245, 157, 0.4), 6, 0.3, 2, 2);
}
.login-secondary-button:hover {
-fx-background-color: linear-gradient(to bottom, #fff9c4, #fff59d);
-fx-effect: dropshadow(gaussian, rgba(255, 245, 157, 0.6), 8, 0.4, 2, 2);
}
.login-status-label {
-fx-text-fill: #7b1fa2;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.1);
-fx-background-radius: 8px;
-fx-padding: 8px 12px;
-fx-border-color: rgba(186, 104, 200, 0.2);
-fx-border-radius: 8px;
-fx-border-width: 1px;
}
/* ===== 注册界面专用样式 - 修复版本 ===== */
.register-background-new {
-fx-background-image: url('../images/register-bg.png'); /* 修改 */
-fx-background-size: cover;
-fx-background-position: center;
-fx-background-repeat: no-repeat;
-fx-background-color: linear-gradient(to bottom right, #fff9c4, #f3e5f5);
}
.register-glass-panel {
-fx-background-color: rgba(255, 255, 255, 0.85);
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-border-color: rgba(186, 104, 200, 0.3);
-fx-border-width: 1px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.15), 20, 0.3, 0, 6);
}
.register-title {
-fx-text-fill: #7b1fa2;
-fx-font-weight: bold;
-fx-font-size: 26px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.2), 4, 0.5, 2, 2);
}
.register-textfield {
-fx-background-color: rgba(255, 255, 255, 0.95);
-fx-border-color: rgba(186, 104, 200, 0.5);
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 12px 16px;
-fx-font-size: 14px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 6, 0.2, 2, 2);
}
.register-textfield:focused {
-fx-border-color: rgba(156, 39, 176, 0.8);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.3), 8, 0.3, 2, 2);
}
.register-primary-button {
-fx-background-color: linear-gradient(to bottom, #ba68c8, #ab47bc);
-fx-text-fill: white;
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-border-color: #ab47bc;
-fx-border-width: 1px;
-fx-padding: 12px 24px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.4), 8, 0.3, 2, 2);
}
.register-primary-button:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ba68c8);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.6), 10, 0.4, 3, 3);
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
}
.register-secondary-button {
-fx-background-color: linear-gradient(to bottom, #fff59d, #fff176);
-fx-text-fill: #7b1fa2;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-border-color: #fff176;
-fx-border-width: 1px;
-fx-padding: 10px 20px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(255, 245, 157, 0.4), 6, 0.3, 2, 2);
}
.register-secondary-button:hover {
-fx-background-color: linear-gradient(to bottom, #fff9c4, #fff59d);
-fx-effect: dropshadow(gaussian, rgba(255, 245, 157, 0.6), 8, 0.4, 2, 2);
}
.register-security-tip {
-fx-text-fill: #7b1fa2;
-fx-font-size: 12px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.1);
-fx-background-radius: 8px;
-fx-padding: 8px 12px;
-fx-border-color: rgba(186, 104, 200, 0.2);
-fx-border-radius: 8px;
-fx-border-width: 1px;
}
.register-code-container {
-fx-background-color: rgba(255, 255, 255, 0.9);
-fx-background-radius: 15px;
-fx-border-radius: 15px;
-fx-border-color: rgba(186, 104, 200, 0.4);
-fx-border-width: 1px;
-fx-padding: 8px 12px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 6, 0.2, 2, 2);
}
/* 修复验证码按钮大小 */
.register-code-button {
-fx-background-color: linear-gradient(to bottom, #ba68c8, #ab47bc);
-fx-text-fill: white;
-fx-font-size: 12px;
-fx-font-weight: bold;
-fx-background-radius: 10px;
-fx-border-radius: 10px;
-fx-border-color: #ab47bc;
-fx-border-width: 1px;
-fx-padding: 10px 16px;
-fx-min-width: 100px;
-fx-min-height: 40px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.3), 6, 0.3, 2, 2);
}
.register-code-button:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ba68c8);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.5), 8, 0.4, 2, 2);
}
/* ===== 注册界面状态标签样式 - 修复长句子版本 ===== */
.register-status-label {
-fx-text-fill: transparent;
-fx-font-size: 12px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: transparent;
-fx-background-radius: 8px;
-fx-padding: 0px;
-fx-border-color: transparent;
-fx-border-radius: 8px;
-fx-border-width: 0px;
-fx-alignment: center;
-fx-text-alignment: center;
-fx-min-height: 0px;
-fx-pref-height: 0px;
-fx-max-width: 280px;
-fx-line-spacing: 2px;
}
.register-status-label-with-text {
-fx-text-fill: #7b1fa2;
-fx-font-size: 12px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.15);
-fx-background-radius: 10px;
-fx-padding: 12px 15px;
-fx-border-color: rgba(186, 104, 200, 0.3);
-fx-border-radius: 10px;
-fx-border-width: 1px;
-fx-alignment: center;
-fx-text-alignment: center;
-fx-min-height: 70px;
-fx-pref-height: 70px;
-fx-max-width: 280px;
-fx-line-spacing: 2px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 6, 0.3, 2, 2);
}
/* ===== 主菜单界面专用样式 - 浅紫色为主,浅黄色为辅 ===== */
.mainmenu-background-new {
-fx-background-image: url('../images/mainmenu-bg.png'); /* 修改 */
-fx-background-size: cover;
-fx-background-position: center center;
-fx-background-repeat: no-repeat;
-fx-background-color: linear-gradient(to bottom right, #f3e5f5, #e1bee7);
}
.mainmenu-glass-panel {
-fx-background-color: rgba(255, 255, 255, 0.92);
-fx-background-radius: 30px;
-fx-border-radius: 30px;
-fx-border-color: rgba(186, 104, 200, 0.5);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.25), 25, 0.5, 0, 8);
}
/* ===== 主菜单欢迎标题样式 - 修复长用户名显示 ===== */
.mainmenu-welcome-title {
-fx-text-fill: #7b1fa2;
-fx-font-weight: bold;
-fx-font-size: 24px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.25), 4, 0.6, 2, 2);
-fx-wrap-text: true;
-fx-text-alignment: center;
-fx-alignment: center;
-fx-max-width: 360px;
}
.mainmenu-difficulty-label {
-fx-text-fill: #7b1fa2;
-fx-font-size: 20px;
-fx-font-weight: bold;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.2), 2, 0.4, 1, 1);
}
/* ===== 难度按钮样式 - 浅紫色质感 ===== */
.mainmenu-difficulty-button {
-fx-background-color: linear-gradient(to bottom, #e1bee7, #ba68c8);
-fx-text-fill: white;
-fx-font-size: 18px;
-fx-font-weight: bold;
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-border-color: #ba68c8;
-fx-border-width: 2px;
-fx-padding: 15px 30px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.5), 12, 0.5, 3, 3);
}
.mainmenu-difficulty-button:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ab47bc);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.7), 15, 0.6, 4, 4);
-fx-scale-x: 1.08;
-fx-scale-y: 1.08;
}
.mainmenu-difficulty-button:pressed {
-fx-background-color: linear-gradient(to bottom, #ab47bc, #8e24aa);
}
/* ===== 数量标签样式 ===== */
.mainmenu-count-label {
-fx-text-fill: #7b1fa2;
-fx-font-size: 16px;
-fx-font-weight: bold;
}
/* ===== 数量输入框样式 ===== */
.mainmenu-count-textfield {
-fx-background-color: rgba(255, 255, 255, 0.95);
-fx-border-color: rgba(186, 104, 200, 0.6);
-fx-border-radius: 10px;
-fx-background-radius: 10px;
-fx-padding: 8px 12px;
-fx-font-size: 14px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.3), 6, 0.2, 2, 2);
-fx-alignment: center;
}
.mainmenu-count-textfield:focused {
-fx-border-color: rgba(156, 39, 176, 0.8);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.4), 8, 0.3, 2, 2);
}
/* ===== 功能按钮样式 - 浅黄色辅助 ===== */
.mainmenu-function-button {
-fx-background-color: linear-gradient(to bottom, #fff59d, #ffeb3b);
-fx-text-fill: #7b1fa2;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-border-color: #ffeb3b;
-fx-border-width: 1px;
-fx-padding: 10px 25px;
-fx-min-width: 140px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(255, 235, 59, 0.4), 8, 0.3, 2, 2);
}
.mainmenu-function-button:hover {
-fx-background-color: linear-gradient(to bottom, #fff9c4, #fff59d);
-fx-effect: dropshadow(gaussian, rgba(255, 235, 59, 0.6), 10, 0.4, 3, 3);
-fx-text-fill: #4a148c;
}
/* ===== 退出按钮样式 - 浅紫色 ===== */
.mainmenu-logout-button {
-fx-background-color: linear-gradient(to bottom, #ba68c8, #ab47bc);
-fx-text-fill: white;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-border-color: #ab47bc;
-fx-border-width: 1px;
-fx-padding: 10px 25px;
-fx-min-width: 140px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.4), 8, 0.3, 2, 2);
}
.mainmenu-logout-button:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ba68c8);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.6), 10, 0.4, 3, 3);
}
/* ===== 状态标签样式 ===== */
.mainmenu-status-label {
-fx-text-fill: #7b1fa2;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.15);
-fx-background-radius: 8px;
-fx-padding: 8px 12px;
-fx-border-color: rgba(186, 104, 200, 0.3);
-fx-border-radius: 8px;
-fx-border-width: 1px;
-fx-alignment: center;
}
/* ===== 答题界面专用样式 - 优化布局版本 ===== */
.quiz-background-new {
-fx-background-image: url('../images/quiz-bg.png'); /* 修改 */
-fx-background-size: cover;
-fx-background-position: center center;
-fx-background-repeat: no-repeat;
-fx-background-color: linear-gradient(to bottom right, #fff9c4, #f3e5f5);
}
.quiz-title-new {
-fx-text-fill: #7b1fa2;
-fx-font-weight: bold;
-fx-font-size: 18px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.25), 4, 0.5, 2, 2);
-fx-alignment: center;
}
/* ===== 进度条样式 ===== */
.quiz-progress-bar-new {
-fx-accent: #ba68c8;
-fx-background-color: #f3e5f5;
-fx-background-radius: 8px;
-fx-border-radius: 8px;
-fx-padding: 2px;
}
.quiz-progress-bar-new .track {
-fx-background-color: #f3e5f5;
-fx-background-radius: 8px;
}
.quiz-progress-bar-new .bar {
-fx-background-color: linear-gradient(to right, #ba68c8, #ab47bc);
-fx-background-radius: 8px;
}
/* ===== 题目内容样式 - 减小高度 ===== */
.quiz-question-text {
-fx-text-fill: #4a148c;
-fx-font-weight: bold;
-fx-font-size: 18px;
-fx-wrap-text: true;
-fx-background-color: rgba(255, 255, 255, 0.92);
-fx-background-radius: 15px;
-fx-padding: 15px 20px;
-fx-border-color: rgba(186, 104, 200, 0.6);
-fx-border-radius: 15px;
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.3), 10, 0.4, 3, 3);
-fx-alignment: center;
-fx-text-alignment: center;
-fx-min-height: 80px;
-fx-pref-height: 80px;
}
/* ===== 选项容器样式 - 减小高度 ===== */
.quiz-options-container {
-fx-background-color: rgba(255, 255, 255, 0.88);
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-border-color: rgba(186, 104, 200, 0.4);
-fx-border-width: 2px;
-fx-padding: 15px 20px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.25), 12, 0.4, 3, 3);
-fx-min-height: 180px;
-fx-pref-height: 180px;
}
/* ===== 单选按钮样式 - 减小间距 ===== */
.quiz-radio-button-new {
-fx-text-fill: #4a148c;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-cursor: hand;
-fx-padding: 8px 0px;
}
.quiz-radio-button-new .radio {
-fx-background-color: white;
-fx-border-color: #ba68c8;
-fx-border-radius: 12px;
-fx-background-radius: 12px;
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 4, 0.3, 1, 1);
}
.quiz-radio-button-new:selected .radio {
-fx-background-color: #ba68c8;
-fx-border-color: #7b1fa2;
-fx-border-width: 3px;
}
.quiz-radio-button-new:selected .dot {
-fx-background-color: white;
}
.quiz-radio-button-new:hover .radio {
-fx-border-color: #7b1fa2;
-fx-border-width: 3px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.3), 6, 0.4, 2, 2);
}
/* ===== 提交按钮样式 ===== */
.quiz-submit-button {
-fx-background-color: linear-gradient(to bottom, #ba68c8, #ab47bc);
-fx-text-fill: white;
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-border-color: #ab47bc;
-fx-border-width: 2px;
-fx-padding: 10px 25px;
-fx-min-width: 150px;
-fx-min-height: 45px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.4), 8, 0.4, 2, 2);
}
.quiz-submit-button:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ba68c8);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.6), 10, 0.5, 3, 3);
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
}
.quiz-submit-button:pressed {
-fx-background-color: linear-gradient(to bottom, #ab47bc, #8e24aa);
}
/* ===== 答题状态标签样式 - 固定位置 ===== */
.quiz-status-label-empty {
-fx-text-fill: transparent;
-fx-font-size: 13px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: transparent;
-fx-background-radius: 8px;
-fx-padding: 0px;
-fx-border-color: transparent;
-fx-border-radius: 8px;
-fx-border-width: 0px;
-fx-alignment: center;
-fx-min-height: 0px;
-fx-pref-height: 0px;
-fx-max-height: 0px;
}
.quiz-status-label-with-text {
-fx-text-fill: #7b1fa2;
-fx-font-size: 13px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.15);
-fx-background-radius: 8px;
-fx-padding: 8px 12px;
-fx-border-color: rgba(186, 104, 200, 0.3);
-fx-border-radius: 8px;
-fx-border-width: 1px;
-fx-alignment: center;
-fx-min-height: 35px;
-fx-pref-height: 35px;
-fx-max-height: 35px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 4, 0.3, 1, 1);
}
/* ===== 分数界面专用样式 - 修复文字换行 ===== */
.score-background-new {
-fx-background-image: url('../images/score-bg.png'); /* 修改 */
-fx-background-size: cover;
-fx-background-position: center center;
-fx-background-repeat: no-repeat;
-fx-background-color: linear-gradient(to bottom right, #fff9c4, #f3e5f5);
}
.score-glass-panel-new {
-fx-background-color: rgba(255, 255, 255, 0.9);
-fx-background-radius: 35px;
-fx-border-radius: 35px;
-fx-border-color: rgba(186, 104, 200, 0.4);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.25), 30, 0.6, 0, 10);
-fx-padding: 30px 25px;
}
.score-complete-title-new {
-fx-text-fill: #7b1fa2;
-fx-font-weight: bold;
-fx-font-size: 32px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.3), 6, 0.6, 3, 3);
-fx-wrap-text: true;
-fx-text-alignment: center;
-fx-alignment: center;
}
.score-label-new {
-fx-text-fill: linear-gradient(to bottom, #ba68c8, #7b1fa2);
-fx-font-weight: bold;
-fx-font-size: 60px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.4), 8, 0.7, 4, 4);
-fx-alignment: center;
}
.score-description-new {
-fx-text-fill: #7b1fa2;
-fx-font-size: 18px;
-fx-font-weight: bold;
-fx-alignment: center;
}
/* ===== 结果消息样式 - 完全修复文字换行 ===== */
.result-message-new {
-fx-text-fill: #7b1fa2;
-fx-font-size: 18px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(255, 245, 157, 0.3);
-fx-background-radius: 15px;
-fx-padding: 12px 20px;
-fx-border-color: rgba(255, 235, 59, 0.4);
-fx-border-radius: 15px;
-fx-border-width: 1px;
-fx-effect: dropshadow(gaussian, rgba(255, 235, 59, 0.2), 8, 0.3, 2, 2);
-fx-alignment: center;
-fx-text-alignment: center;
-fx-max-width: 300px;
-fx-min-height: 60px;
-fx-pref-height: 60px;
}
/* ===== 庆祝按钮样式 ===== */
.celebrate-button-new {
-fx-background-color: linear-gradient(to bottom, #ba68c8, #ab47bc);
-fx-text-fill: white;
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-border-color: #ab47bc;
-fx-border-width: 2px;
-fx-padding: 12px 30px;
-fx-min-width: 160px;
-fx-min-height: 50px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.4), 10, 0.4, 3, 3);
}
.celebrate-button-new:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ba68c8);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.6), 12, 0.5, 4, 4);
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
}
.celebrate-button-new:pressed {
-fx-background-color: linear-gradient(to bottom, #ab47bc, #8e24aa);
}
/* ===== 退出按钮样式 ===== */
.score-logout-button-new {
-fx-background-color: linear-gradient(to bottom, #fff59d, #ffeb3b);
-fx-text-fill: #7b1fa2;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-border-color: #ffeb3b;
-fx-border-width: 1px;
-fx-padding: 10px 25px;
-fx-min-width: 140px;
-fx-min-height: 45px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(255, 235, 59, 0.4), 8, 0.3, 2, 2);
}
.score-logout-button-new:hover {
-fx-background-color: linear-gradient(to bottom, #fff9c4, #fff59d);
-fx-effect: dropshadow(gaussian, rgba(255, 235, 59, 0.6), 10, 0.4, 3, 3);
-fx-text-fill: #4a148c;
}
/* ===== 修改密码界面专用样式 - 淡黄色和淡紫色主题 ===== */
.password-background-new {
-fx-background-image: url('../images/password-bg.png'); /* 修改 */
-fx-background-size: cover;
-fx-background-position: center center;
-fx-background-repeat: no-repeat;
-fx-background-color: linear-gradient(to bottom right, #fff9c4, #f3e5f5);
}
.password-glass-panel {
-fx-background-color: rgba(255, 255, 255, 0.88);
-fx-background-radius: 30px;
-fx-border-radius: 30px;
-fx-border-color: rgba(186, 104, 200, 0.4);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.2), 25, 0.5, 0, 8);
}
.password-title {
-fx-text-fill: #7b1fa2;
-fx-font-weight: bold;
-fx-font-size: 26px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.25), 4, 0.6, 2, 2);
}
.password-security-tip {
-fx-text-fill: #7b1fa2;
-fx-font-size: 13px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.1);
-fx-background-radius: 10px;
-fx-padding: 10px 15px;
-fx-border-color: rgba(186, 104, 200, 0.2);
-fx-border-radius: 10px;
-fx-border-width: 1px;
-fx-alignment: center;
}
.password-textfield {
-fx-background-color: rgba(255, 255, 255, 0.95);
-fx-border-color: rgba(186, 104, 200, 0.5);
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 12px 16px;
-fx-font-size: 14px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 6, 0.2, 2, 2);
}
.password-textfield:focused {
-fx-border-color: rgba(156, 39, 176, 0.8);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.3), 8, 0.3, 2, 2);
}
.password-primary-button {
-fx-background-color: linear-gradient(to bottom, #ba68c8, #ab47bc);
-fx-text-fill: white;
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-border-color: #ab47bc;
-fx-border-width: 2px;
-fx-padding: 12px 24px;
-fx-min-width: 150px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.4), 8, 0.3, 2, 2);
}
.password-primary-button:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ba68c8);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.6), 10, 0.4, 3, 3);
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
}
.password-secondary-button {
-fx-background-color: linear-gradient(to bottom, #fff59d, #ffeb3b);
-fx-text-fill: #7b1fa2;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-border-color: #ffeb3b;
-fx-border-width: 1px;
-fx-padding: 10px 20px;
-fx-min-width: 150px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(255, 235, 59, 0.4), 6, 0.3, 2, 2);
}
.password-secondary-button:hover {
-fx-background-color: linear-gradient(to bottom, #fff9c4, #fff59d);
-fx-effect: dropshadow(gaussian, rgba(255, 235, 59, 0.6), 8, 0.4, 2, 2);
}
/* ===== 修改密码界面状态标签样式 - 修复换行版本 ===== */
.password-status-label {
-fx-text-fill: transparent;
-fx-font-size: 13px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: transparent;
-fx-background-radius: 10px;
-fx-padding: 0px;
-fx-border-color: transparent;
-fx-border-radius: 10px;
-fx-border-width: 0px;
-fx-alignment: center;
-fx-text-alignment: center;
-fx-min-height: 0px;
-fx-pref-height: 0px;
-fx-max-width: 300px;
}
.password-status-label-with-text {
-fx-text-fill: #7b1fa2;
-fx-font-size: 13px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.15);
-fx-background-radius: 10px;
-fx-padding: 10px 15px;
-fx-border-color: rgba(186, 104, 200, 0.3);
-fx-border-radius: 10px;
-fx-border-width: 1px;
-fx-alignment: center;
-fx-text-alignment: center;
-fx-min-height: 60px;
-fx-pref-height: 60px;
-fx-max-width: 300px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 6, 0.3, 2, 2);
}
/* ===== 设置密码界面专用样式 - 淡黄色和淡紫色主题 ===== */
.setpassword-background {
-fx-background-image: url('../images/background.png');
-fx-background-size: cover;
-fx-background-position: center center;
-fx-background-repeat: no-repeat;
-fx-background-color: linear-gradient(to bottom right, #fff9c4, #f3e5f5);
}
.setpassword-glass-panel {
-fx-background-color: rgba(255, 255, 255, 0.88);
-fx-background-radius: 30px;
-fx-border-radius: 30px;
-fx-border-color: rgba(186, 104, 200, 0.4);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.2), 25, 0.5, 0, 8);
}
.setpassword-title {
-fx-text-fill: #7b1fa2;
-fx-font-weight: bold;
-fx-font-size: 26px;
-fx-effect: dropshadow(gaussian, rgba(123, 31, 162, 0.25), 4, 0.6, 2, 2);
}
.setpassword-prompt {
-fx-text-fill: #7b1fa2;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.1);
-fx-background-radius: 10px;
-fx-padding: 10px 15px;
-fx-border-color: rgba(186, 104, 200, 0.2);
-fx-border-radius: 10px;
-fx-border-width: 1px;
-fx-alignment: center;
}
.setpassword-textfield {
-fx-background-color: rgba(255, 255, 255, 0.95);
-fx-border-color: rgba(186, 104, 200, 0.5);
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 12px 16px;
-fx-font-size: 14px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 6, 0.2, 2, 2);
}
.setpassword-textfield:focused {
-fx-border-color: rgba(156, 39, 176, 0.8);
-fx-border-width: 2px;
-fx-effect: dropshadow(gaussian, rgba(156, 39, 176, 0.3), 8, 0.3, 2, 2);
}
.setpassword-primary-button {
-fx-background-color: linear-gradient(to bottom, #ba68c8, #ab47bc);
-fx-text-fill: white;
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-border-color: #ab47bc;
-fx-border-width: 2px;
-fx-padding: 12px 24px;
-fx-min-width: 150px;
-fx-cursor: hand;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.4), 8, 0.3, 2, 2);
}
.setpassword-primary-button:hover {
-fx-background-color: linear-gradient(to bottom, #ce93d8, #ba68c8);
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.6), 10, 0.4, 3, 3);
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
}
/* ===== 设置密码界面状态标签样式 - 修复页面滑动 ===== */
.setpassword-status-label {
-fx-text-fill: transparent;
-fx-font-size: 12px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: transparent;
-fx-background-radius: 10px;
-fx-padding: 0px;
-fx-border-color: transparent;
-fx-border-radius: 10px;
-fx-border-width: 0px;
-fx-alignment: center;
-fx-text-alignment: center;
-fx-min-height: 70px;
-fx-pref-height: 70px;
-fx-max-width: 280px;
-fx-line-spacing: 2px;
}
.setpassword-status-label-with-text {
-fx-text-fill: #7b1fa2;
-fx-font-size: 12px;
-fx-font-weight: bold;
-fx-wrap-text: true;
-fx-background-color: rgba(186, 104, 200, 0.15);
-fx-background-radius: 10px;
-fx-padding: 12px 15px;
-fx-border-color: rgba(186, 104, 200, 0.3);
-fx-border-radius: 10px;
-fx-border-width: 1px;
-fx-alignment: center;
-fx-text-alignment: center;
-fx-min-height: 70px;
-fx-pref-height: 70px;
-fx-max-width: 280px;
-fx-line-spacing: 2px;
-fx-effect: dropshadow(gaussian, rgba(186, 104, 200, 0.2), 6, 0.3, 2, 2);
}
/* ===== 表单字段标签 (新增) ===== */
.form-label {
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-text-fill: #7b1fa2; /* 使用与标题一致的紫色 */
-fx-padding: 0 0 4px 8px; /* 在标签下方和左侧留出一点空间 */
}

@ -7,25 +7,85 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="450.0" spacing="15.0" style="-fx-background-color: #f4f4f4;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mathgenerator.controller.ChangePasswordController">
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0"
prefWidth="400.0" spacing="18.0" styleClass="password-background-new, root-container"
xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.mathgenerator.controller.ChangePasswordController"
stylesheets="@/com/mathgenerator/styles/styles.css">
<children>
<Label text="修改密码">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<PasswordField fx:id="oldPasswordField" maxWidth="300.0" promptText="当前密码" />
<PasswordField fx:id="newPasswordField" maxWidth="300.0" promptText="新密码 (6-10位, 含大小写字母和数字)" />
<PasswordField fx:id="confirmNewPasswordField" maxWidth="300.0" promptText="确认新密码" />
<Button fx:id="confirmButton" mnemonicParsing="false" onAction="#handleConfirmAction" prefWidth="120.0" text="确认修改" />
<Button fx:id="backButton" mnemonicParsing="false" onAction="#handleBackAction" style="-fx-background-color: #6c757d;" text="返回主菜单" textFill="WHITE" />
<Label fx:id="statusLabel" textFill="RED" wrapText="true">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Label>
<VBox alignment="CENTER" spacing="12.0" styleClass="password-glass-panel"
prefWidth="360.0" prefHeight="500.0">
<children>
<Label text="修改密码" styleClass="password-title">
<font>
<Font name="System Bold" size="26.0" />
</font>
<VBox.margin>
<Insets bottom="5.0" top="10.0" />
</VBox.margin>
</Label>
<Label text="请确保密码安全,包含大小写字母和数字" styleClass="password-security-tip"
maxWidth="300.0" alignment="CENTER" wrapText="true" />
<VBox maxWidth="300.0" spacing="4.0">
<children>
<Label text="当前密码" styleClass="form-label"/>
<PasswordField fx:id="oldPasswordField" styleClass="password-textfield"/>
</children>
</VBox>
<VBox maxWidth="300.0" spacing="4.0">
<children>
<Label text="新密码 (6-10位)" styleClass="form-label"/>
<PasswordField fx:id="newPasswordField" styleClass="password-textfield"/>
</children>
</VBox>
<VBox maxWidth="300.0" spacing="4.0">
<children>
<Label text="确认新密码" styleClass="form-label"/>
<PasswordField fx:id="confirmNewPasswordField" styleClass="password-textfield"/>
</children>
</VBox>
<VBox alignment="CENTER" spacing="10.0">
<children>
<Button fx:id="confirmButton" mnemonicParsing="false"
onAction="#handleConfirmAction"
text="确认修改" styleClass="password-primary-button" />
<Button fx:id="backButton" mnemonicParsing="false"
onAction="#handleBackAction"
text="返回主菜单" styleClass="password-secondary-button" />
</children>
<VBox.margin>
<Insets top="12.0" />
</VBox.margin>
</VBox>
<Label fx:id="statusLabel" styleClass="password-status-label"
maxWidth="300.0" alignment="CENTER" text=""
wrapText="true"
minHeight="60.0" prefHeight="60.0">
<VBox.margin>
<Insets top="8.0" />
</VBox.margin>
</Label>
</children>
<padding>
<Insets bottom="30.0" left="30.0" right="30.0" top="25.0" />
</padding>
</VBox>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
<Insets bottom="25.0" left="25.0" right="25.0" top="25.0" />
</padding>
</VBox>

@ -8,24 +8,70 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="350.0" prefWidth="400.0" spacing="15.0" style="-fx-background-color: #f4f4f4;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mathgenerator.controller.LoginController">
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0"
prefWidth="400.0" spacing="20.0" styleClass="login-background, root-container"
xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.mathgenerator.controller.LoginController"
stylesheets="@/com/mathgenerator/styles/styles.css">
<children>
<Label text="用户登录">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<TextField fx:id="usernameField" maxWidth="250.0" promptText="用户名" />
<PasswordField fx:id="passwordField" maxWidth="250.0" promptText="密码" />
<Button fx:id="loginButton" mnemonicParsing="false" onAction="#handleLoginButtonAction" prefWidth="100.0" text="登录" />
<Button fx:id="registerButton" mnemonicParsing="false" onAction="#handleRegisterButtonAction" style="-fx-background-color: #6c757d;" text="注册新用户" textFill="WHITE" />
<Label fx:id="statusLabel" textFill="RED">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Label>
<VBox alignment="CENTER" spacing="15.0" styleClass="login-glass-panel">
<children>
<Label text="用户登录" styleClass="login-title">
<font>
<Font name="System Bold" size="26.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<VBox maxWidth="280.0" spacing="4.0">
<children>
<Label text="用户名" styleClass="form-label" />
<TextField fx:id="usernameField" styleClass="login-textfield" />
</children>
</VBox>
<VBox maxWidth="280.0" spacing="4.0">
<children>
<Label text="密码" styleClass="form-label" />
<PasswordField fx:id="passwordField" styleClass="login-textfield" />
</children>
</VBox>
<VBox alignment="CENTER" spacing="12.0">
<children>
<Button fx:id="loginButton" mnemonicParsing="false"
onAction="#handleLoginButtonAction" prefWidth="140.0"
text="登录" styleClass="login-primary-button" />
<Button fx:id="registerButton" mnemonicParsing="false"
onAction="#handleRegisterButtonAction" prefWidth="140.0"
text="注册新用户" styleClass="login-secondary-button" />
</children>
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</VBox>
<Label fx:id="statusLabel" styleClass="login-status-label" text="欢迎使用数学学习软件">
<VBox.margin>
<Insets top="15.0" />
</VBox.margin>
</Label>
</children>
<padding>
<Insets bottom="35.0" left="35.0" right="35.0" top="35.0" />
</padding>
</VBox>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</padding>
</VBox>

@ -8,43 +8,98 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="500.0" spacing="20.0" style="-fx-background-color: #f4f4f4;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mathgenerator.controller.MainMenuController">
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0"
prefWidth="400.0" spacing="20.0" styleClass="mainmenu-background-new, root-container"
xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.mathgenerator.controller.MainMenuController"
stylesheets="@/com/mathgenerator/styles/styles.css">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
<Insets bottom="25.0" left="25.0" right="25.0" top="25.0" />
</padding>
<children>
<Label fx:id="welcomeLabel" text="欢迎, [用户名]!">
<!-- 欢迎标题 - 修复长用户名显示 -->
<Label fx:id="welcomeLabel" text="欢迎, [用户名]!" styleClass="mainmenu-welcome-title"
maxWidth="360.0" alignment="CENTER" wrapText="true">
<font>
<Font name="System Bold" size="28.0" />
<Font name="System Bold" size="24.0" />
</font>
<VBox.margin>
<Insets bottom="15.0" />
</VBox.margin>
</Label>
<VBox alignment="CENTER" spacing="15.0" VBox.vgrow="ALWAYS">
<!-- 主要内容区域 -->
<VBox alignment="CENTER" spacing="20.0" VBox.vgrow="ALWAYS"
styleClass="mainmenu-glass-panel" prefWidth="340.0">
<children>
<Label text="请选择题目难度">
<!-- 难度选择标签 -->
<Label text="请选择题目难度" styleClass="mainmenu-difficulty-label">
<font>
<Font size="18.0" />
<Font size="20.0" />
</font>
<VBox.margin>
<Insets bottom="12.0" />
</VBox.margin>
</Label>
<Button fx:id="primaryButton" mnemonicParsing="false" onAction="#handlePrimaryAction" prefHeight="50.0" prefWidth="200.0" text="小学" />
<Button fx:id="juniorHighButton" mnemonicParsing="false" onAction="#handleJuniorHighAction" prefHeight="50.0" prefWidth="200.0" text="初中" />
<Button fx:id="seniorHighButton" mnemonicParsing="false" onAction="#handleSeniorHighAction" prefHeight="50.0" prefWidth="200.0" text="高中" />
<HBox alignment="CENTER" spacing="10.0">
<!-- 题目数量设置 -->
<VBox alignment="CENTER" spacing="8.0">
<children>
<Label text="题目数量:" />
<TextField fx:id="questionCountField" prefWidth="80.0" text="10" />
<HBox alignment="CENTER" spacing="10.0">
<children>
<Label text="题目数量:" styleClass="mainmenu-count-label" />
<TextField fx:id="questionCountField" prefWidth="80.0"
text="10" styleClass="mainmenu-count-textfield" />
</children>
</HBox>
</children>
<VBox.margin>
<Insets top="20.0" />
<Insets bottom="12.0" />
</VBox.margin>
</HBox>
<Label fx:id="statusLabel" textFill="RED" />
</VBox>
<!-- 难度选择按钮 -->
<VBox alignment="CENTER" spacing="15.0">
<children>
<Button fx:id="primaryButton" mnemonicParsing="false"
onAction="#handlePrimaryAction" prefHeight="55.0"
prefWidth="200.0" text="小学" styleClass="mainmenu-difficulty-button" />
<Button fx:id="juniorHighButton" mnemonicParsing="false"
onAction="#handleJuniorHighAction" prefHeight="55.0"
prefWidth="200.0" text="初中" styleClass="mainmenu-difficulty-button" />
<Button fx:id="seniorHighButton" mnemonicParsing="false"
onAction="#handleSeniorHighAction" prefHeight="55.0"
prefWidth="200.0" text="高中" styleClass="mainmenu-difficulty-button" />
</children>
</VBox>
<!-- 状态标签 -->
<Label fx:id="statusLabel" styleClass="mainmenu-status-label"
maxWidth="280.0" alignment="CENTER" text="" />
</children>
<padding>
<Insets bottom="25.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
<HBox alignment="CENTER_RIGHT" spacing="10.0">
<!-- 底部功能按钮 -->
<HBox alignment="CENTER" spacing="12.0" prefHeight="60.0">
<children>
<Button fx:id="changePasswordButton" mnemonicParsing="false" onAction="#handleChangePasswordAction" text="修改密码" />
<Button fx:id="logoutButton" mnemonicParsing="false" onAction="#handleLogoutAction" style="-fx-background-color: #dc3545;" text="退出登录" textFill="WHITE" />
<Button fx:id="changePasswordButton" mnemonicParsing="false"
onAction="#handleChangePasswordAction"
text="修改密码" styleClass="mainmenu-function-button" />
<Button fx:id="logoutButton" mnemonicParsing="false"
onAction="#handleLogoutAction"
text="退出登录" styleClass="mainmenu-logout-button" />
</children>
<VBox.margin>
<Insets top="20.0" bottom="5.0" />
</VBox.margin>
</HBox>
</children>
</VBox>

@ -9,60 +9,96 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="600.0" spacing="20.0" style="-fx-background-color: #f4f4f4;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mathgenerator.controller.QuizController">
<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0"
prefWidth="400.0" spacing="12.0" styleClass="quiz-background-new, root-container"
xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.mathgenerator.controller.QuizController"
stylesheets="@/com/mathgenerator/styles/styles.css">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<children>
<Label fx:id="questionNumberLabel" text="第 1 / 10 题">
<!-- 题目进度 -->
<Label fx:id="questionNumberLabel" text="第 1 / 10 题" styleClass="quiz-title-new"
maxWidth="360.0" alignment="CENTER">
<font>
<Font size="18.0" />
<Font size="16.0" />
</font>
</Label>
<ProgressBar fx:id="progressBar" prefWidth="560.0" progress="0.1" />
<Label fx:id="questionTextLabel" style="-fx-font-weight: bold;" text="题目内容: 1 + 1 = ?" wrapText="true">
<!-- 进度条 -->
<ProgressBar fx:id="progressBar" prefWidth="360.0" progress="0.1"
styleClass="quiz-progress-bar-new" />
<!-- 题目内容 -->
<Label fx:id="questionTextLabel" text="题目内容: 1 + 1 = ?"
styleClass="quiz-question-text" wrapText="true"
maxWidth="360.0" alignment="CENTER">
<font>
<Font size="24.0" />
<Font size="18.0" />
</font>
<VBox.margin>
<Insets top="20.0" />
<Insets top="15.0" />
</VBox.margin>
</Label>
<VBox fx:id="optionsVBox" alignment="CENTER_LEFT" spacing="15.0">
<!-- 选项容器 -->
<VBox fx:id="optionsVBox" alignment="CENTER_LEFT" spacing="10.0"
styleClass="quiz-options-container" prefWidth="360.0">
<children>
<RadioButton fx:id="option1" mnemonicParsing="false" text="选项1">
<RadioButton fx:id="option1" mnemonicParsing="false" text="A. 选项1"
styleClass="quiz-radio-button-new">
<toggleGroup>
<ToggleGroup fx:id="optionsGroup" />
</toggleGroup>
<font>
<Font size="16.0" />
<Font size="14.0" />
</font>
</RadioButton>
<RadioButton fx:id="option2" mnemonicParsing="false" text="选项2" toggleGroup="$optionsGroup">
<RadioButton fx:id="option2" mnemonicParsing="false" text="B. 选项2"
toggleGroup="$optionsGroup" styleClass="quiz-radio-button-new">
<font>
<Font size="16.0" />
<Font size="14.0" />
</font>
</RadioButton>
<RadioButton fx:id="option3" mnemonicParsing="false" text="选项3" toggleGroup="$optionsGroup">
<RadioButton fx:id="option3" mnemonicParsing="false" text="C. 选项3"
toggleGroup="$optionsGroup" styleClass="quiz-radio-button-new">
<font>
<Font size="16.0" />
<Font size="14.0" />
</font>
</RadioButton>
<RadioButton fx:id="option4" mnemonicParsing="false" text="选项4" toggleGroup="$optionsGroup">
<RadioButton fx:id="option4" mnemonicParsing="false" text="D. 选项4"
toggleGroup="$optionsGroup" styleClass="quiz-radio-button-new">
<font>
<Font size="16.0" />
<Font size="14.0" />
</font>
</RadioButton>
</children>
<VBox.margin>
<Insets left="50.0" top="20.0" />
<Insets top="15.0" />
</VBox.margin>
</VBox>
<Button fx:id="submitButton" mnemonicParsing="false" onAction="#handleSubmitButtonAction" prefHeight="40.0" prefWidth="150.0" text="提交答案">
<!-- 提交按钮 -->
<Button fx:id="submitButton" mnemonicParsing="false"
onAction="#handleSubmitButtonAction"
text="提交答案" styleClass="quiz-submit-button">
<VBox.margin>
<Insets top="30.0" />
<Insets top="20.0" />
</VBox.margin>
</Button>
<Label fx:id="statusLabel" textFill="RED" />
<!-- 状态标签 - 预留固定空间 -->
<Label fx:id="statusLabel" styleClass="quiz-status-label-empty"
maxWidth="360.0" alignment="CENTER" text=""
minHeight="35.0" prefHeight="35.0" maxHeight="35.0">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Label>
</children>
</VBox>

@ -3,36 +3,97 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="450.0" spacing="15.0" style="-fx-background-color: #f4f4f4;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mathgenerator.controller.RegisterController">
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0"
prefWidth="400.0" spacing="20.0" styleClass="register-background-new, root-container"
xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.mathgenerator.controller.RegisterController"
stylesheets="@/com/mathgenerator/styles/styles.css">
<children>
<Label text="新用户注册">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<TextField fx:id="usernameField" maxWidth="300.0" promptText="用户名" />
<TextField fx:id="emailField" maxWidth="300.0" promptText="邮箱地址" />
<HBox alignment="CENTER" maxWidth="300.0" spacing="10.0">
<VBox alignment="CENTER" spacing="15.0" styleClass="register-glass-panel"
prefWidth="360.0" prefHeight="500.0">
<children>
<TextField fx:id="verificationCodeField" promptText="邮箱验证码" HBox.hgrow="ALWAYS" />
<Button fx:id="sendCodeButton" mnemonicParsing="false" onAction="#handleSendCodeAction" text="发送验证码" />
<Label text="新用户注册" styleClass="register-title">
<font>
<Font name="System Bold" size="26.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<VBox maxWidth="320.0" spacing="4.0">
<children>
<Label text="用户名" styleClass="form-label"/>
<TextField fx:id="usernameField" styleClass="register-textfield" />
</children>
</VBox>
<VBox maxWidth="320.0" spacing="4.0">
<children>
<Label text="邮箱地址" styleClass="form-label"/>
<TextField fx:id="emailField" styleClass="register-textfield" />
</children>
</VBox>
<VBox spacing="8.0" maxWidth="320.0">
<children>
<Label text="请输入邮箱验证码:" styleClass="register-security-tip" />
<HBox alignment="CENTER" spacing="10.0" styleClass="register-code-container">
<children>
<TextField fx:id="verificationCodeField"
promptText="验证码"
styleClass="register-textfield"
HBox.hgrow="ALWAYS" />
<Button fx:id="sendCodeButton" mnemonicParsing="false"
onAction="#handleSendCodeAction"
text="发送验证码"
styleClass="register-code-button" />
</children>
</HBox>
</children>
</VBox>
<VBox alignment="CENTER" spacing="12.0">
<children>
<Button fx:id="registerButton" mnemonicParsing="false"
onAction="#handleRegisterAction" prefWidth="150.0"
text="确认注册" styleClass="register-primary-button" />
<Button fx:id="backToLoginButton" mnemonicParsing="false"
onAction="#handleBackToLoginAction" prefWidth="150.0"
text="返回登录" styleClass="register-secondary-button" />
</children>
<VBox.margin>
<Insets top="15.0" />
</VBox.margin>
</VBox>
<Label fx:id="statusLabel" styleClass="register-status-label"
maxWidth="280.0" alignment="CENTER" text=""
wrapText="true"
minHeight="70.0" prefHeight="70.0">
<VBox.margin>
<Insets top="8.0" />
</VBox.margin>
</Label>
</children>
</HBox>
<Button fx:id="registerButton" mnemonicParsing="false" onAction="#handleRegisterAction" prefWidth="120.0" text="确认注册" />
<Button fx:id="backToLoginButton" mnemonicParsing="false" onAction="#handleBackToLoginAction" style="-fx-background-color: #6c757d;" text="返回登录" textFill="WHITE" />
<Label fx:id="statusLabel" textFill="RED" wrapText="true">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Label>
<padding>
<Insets bottom="30.0" left="40.0" right="40.0" top="30.0" />
</padding>
</VBox>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
<Insets bottom="40.0" left="40.0" right="40.0" top="40.0" />
</padding>
</VBox>

@ -6,38 +6,80 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="500.0" spacing="25.0" style="-fx-background-color: #f4f4f4;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mathgenerator.controller.ScoreController">
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0"
prefWidth="400.0" spacing="20.0" styleClass="score-background-new, root-container"
xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.mathgenerator.controller.ScoreController"
stylesheets="@/com/mathgenerator/styles/styles.css">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
<Insets bottom="25.0" left="25.0" right="25.0" top="25.0" />
</padding>
<children>
<Label text="答题完成!">
<font>
<Font name="System Bold" size="36.0" />
</font>
</Label>
<VBox alignment="CENTER" spacing="10.0">
<!-- 主要内容区域 -->
<VBox alignment="CENTER" spacing="20.0" styleClass="score-glass-panel-new"
prefWidth="350.0" prefHeight="500.0">
<children>
<Label text="您的最终得分是:">
<!-- 完成标题 -->
<Label text="答题完成!" styleClass="score-complete-title-new"
wrapText="true" maxWidth="300.0">
<font>
<Font size="18.0" />
<Font name="System Bold" size="32.0" />
</font>
<VBox.margin>
<Insets bottom="5.0" />
</VBox.margin>
</Label>
<Label fx:id="scoreLabel" text="100.00" textFill="#007bff">
<font>
<Font name="System Bold" size="64.0" />
</font>
<!-- 得分显示区域 -->
<VBox alignment="CENTER" spacing="10.0">
<children>
<Label text="您的最终得分是:" styleClass="score-description-new"
maxWidth="300.0" alignment="CENTER">
<font>
<Font size="18.0" />
</font>
</Label>
<Label fx:id="scoreLabel" text="100.00"
styleClass="score-label-new" maxWidth="300.0"
alignment="CENTER">
<font>
<Font name="System Bold" size="60.0" />
</font>
</Label>
</children>
</VBox>
<!-- 结果消息 - 完全修复文字换行 -->
<Label fx:id="resultMessageLabel"
styleClass="result-message-new"
wrapText="true"
maxWidth="300.0"
alignment="CENTER"
text="太棒了!你答对了所有题目!">
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
</Label>
</children>
</VBox>
<Label fx:id="resultMessageLabel" text="太棒了!你答对了所有题目!" />
<VBox alignment="CENTER" spacing="15.0">
<VBox.margin>
<Insets top="20.0" />
</VBox.margin>
<children>
<Button fx:id="tryAgainButton" mnemonicParsing="false" onAction="#handleTryAgainAction" prefWidth="150.0" text="再做一组" />
<Button fx:id="logoutButton" mnemonicParsing="false" onAction="#handleLogoutAction" style="-fx-background-color: #6c757d;" text="退出登录" textFill="WHITE" />
<!-- 按钮区域 -->
<VBox alignment="CENTER" spacing="12.0">
<VBox.margin>
<Insets top="15.0" />
</VBox.margin>
<children>
<Button fx:id="tryAgainButton" mnemonicParsing="false"
onAction="#handleTryAgainAction"
text="再做一组" styleClass="celebrate-button-new" />
<Button fx:id="logoutButton" mnemonicParsing="false"
onAction="#handleLogoutAction"
text="退出登录" styleClass="score-logout-button-new" />
</children>
</VBox>
</children>
</VBox>
</children>

@ -7,23 +7,66 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="350.0" prefWidth="450.0" spacing="15.0" style="-fx-background-color: #f4f4f4;" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.mathgenerator.controller.SetPasswordController">
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0"
prefWidth="400.0" spacing="15.0" styleClass="setpassword-background, root-container"
xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.mathgenerator.controller.SetPasswordController"
stylesheets="@/com/mathgenerator/styles/styles.css">
<children>
<Label text="设置您的初始密码">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<Label fx:id="promptLabel" text="为您的账户 [用户名] 设置密码" />
<PasswordField fx:id="newPasswordField" maxWidth="300.0" promptText="新密码 (6-10位, 含大小写字母和数字)" />
<PasswordField fx:id="confirmPasswordField" maxWidth="300.0" promptText="确认新密码" />
<Button fx:id="confirmButton" mnemonicParsing="false" onAction="#handleConfirmAction" prefWidth="120.0" text="确认并进入" />
<Label fx:id="statusLabel" textFill="RED" wrapText="true">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Label>
<VBox alignment="CENTER" spacing="15.0" styleClass="setpassword-glass-panel"
prefWidth="360.0" prefHeight="480.0">
<children>
<Label text="设置初始密码" styleClass="setpassword-title">
<font>
<Font name="System Bold" size="26.0" />
</font>
<VBox.margin>
<Insets bottom="5.0" />
</VBox.margin>
</Label>
<Label fx:id="promptLabel" text="为您的账户 [用户名] 设置密码"
styleClass="setpassword-prompt" maxWidth="280.0" alignment="CENTER" wrapText="true" />
<VBox maxWidth="280.0" spacing="4.0">
<children>
<Label text="新密码 (6-10位, 含大小写字母和数字)" styleClass="form-label" wrapText="true"/>
<PasswordField fx:id="newPasswordField" styleClass="setpassword-textfield" />
</children>
</VBox>
<VBox maxWidth="280.0" spacing="4.0">
<children>
<Label text="确认新密码" styleClass="form-label" />
<PasswordField fx:id="confirmPasswordField" styleClass="setpassword-textfield" />
</children>
</VBox>
<Button fx:id="confirmButton" mnemonicParsing="false"
onAction="#handleConfirmAction"
text="确认并进入" styleClass="setpassword-primary-button" />
<Label fx:id="statusLabel" styleClass="setpassword-status-label"
maxWidth="280.0" alignment="CENTER" text=""
wrapText="true"
minHeight="70.0" prefHeight="70.0" maxHeight="70.0">
<VBox.margin>
<Insets top="8.0" />
</VBox.margin>
</Label>
</children>
<padding>
<Insets bottom="25.0" left="30.0" right="30.0" top="25.0" />
</padding>
</VBox>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>

Loading…
Cancel
Save