|
|
|
|
@ -0,0 +1,419 @@
|
|
|
|
|
import javafx.application.Application;
|
|
|
|
|
import javafx.application.Platform;
|
|
|
|
|
import javafx.scene.Scene;
|
|
|
|
|
import javafx.scene.control.*;
|
|
|
|
|
import javafx.scene.layout.*;
|
|
|
|
|
import javafx.stage.Stage;
|
|
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
public class Main extends Application {
|
|
|
|
|
|
|
|
|
|
private Stage primaryStage;
|
|
|
|
|
private MailSender mailSender = new MailSender("3417398995@qq.com", "zhwytlhmucfxcibe");
|
|
|
|
|
private String generatedCode;
|
|
|
|
|
private Map<String, String> userDatabase = new HashMap<>(); // 存储邮箱 -> 密码
|
|
|
|
|
private Map<String, String> currentUser = new HashMap<>(); // 存储当前登录用户信息
|
|
|
|
|
private List<Question> currentExam = new ArrayList<>();
|
|
|
|
|
private int currentIndex = 0;
|
|
|
|
|
private int score = 0;
|
|
|
|
|
private ExamManager examManager;
|
|
|
|
|
private QuestionGenerator questionGenerator = new QuestionGenerator();
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
launch(args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void start(Stage stage) {
|
|
|
|
|
primaryStage = stage;
|
|
|
|
|
primaryStage.setTitle("小初高数学学习软件");
|
|
|
|
|
primaryStage.setScene(buildLoginScene());
|
|
|
|
|
primaryStage.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 登录界面
|
|
|
|
|
private Scene buildLoginScene() {
|
|
|
|
|
VBox root = new VBox(10);
|
|
|
|
|
root.setStyle("-fx-padding: 20;");
|
|
|
|
|
Label titleLabel = new Label("数学学习软件 - 登录");
|
|
|
|
|
titleLabel.setStyle("-fx-font-size: 16; -fx-font-weight: bold;");
|
|
|
|
|
|
|
|
|
|
TextField emailField = new TextField();
|
|
|
|
|
emailField.setPromptText("请输入邮箱");
|
|
|
|
|
PasswordField passwordField = new PasswordField();
|
|
|
|
|
passwordField.setPromptText("请输入密码");
|
|
|
|
|
|
|
|
|
|
Button loginBtn = new Button("登录");
|
|
|
|
|
Button registerBtn = new Button("前往注册");
|
|
|
|
|
Label infoLabel = new Label();
|
|
|
|
|
|
|
|
|
|
// 登录按钮事件
|
|
|
|
|
loginBtn.setOnAction(e -> {
|
|
|
|
|
String email = emailField.getText().trim();
|
|
|
|
|
String password = passwordField.getText();
|
|
|
|
|
|
|
|
|
|
if (email.isEmpty() || password.isEmpty()) {
|
|
|
|
|
infoLabel.setText("请输入邮箱和密码");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!userDatabase.containsKey(email)) {
|
|
|
|
|
infoLabel.setText("用户不存在");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!userDatabase.get(email).equals(password)) {
|
|
|
|
|
infoLabel.setText("密码错误");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 登录成功
|
|
|
|
|
currentUser.put("email", email);
|
|
|
|
|
infoLabel.setText("登录成功!");
|
|
|
|
|
primaryStage.setScene(buildLevelSelectionScene());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 注册按钮事件
|
|
|
|
|
registerBtn.setOnAction(e -> {
|
|
|
|
|
primaryStage.setScene(buildRegisterScene());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
root.getChildren().addAll(titleLabel, emailField, passwordField, loginBtn, registerBtn, infoLabel);
|
|
|
|
|
return new Scene(root, 400, 300);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 注册界面
|
|
|
|
|
private Scene buildRegisterScene() {
|
|
|
|
|
VBox root = new VBox(10);
|
|
|
|
|
root.setStyle("-fx-padding: 20;");
|
|
|
|
|
Label titleLabel = new Label("用户注册");
|
|
|
|
|
titleLabel.setStyle("-fx-font-size: 16; -fx-font-weight: bold;");
|
|
|
|
|
|
|
|
|
|
TextField emailField = new TextField();
|
|
|
|
|
emailField.setPromptText("请输入邮箱");
|
|
|
|
|
Button sendCodeBtn = new Button("发送验证码");
|
|
|
|
|
TextField codeField = new TextField();
|
|
|
|
|
codeField.setPromptText("请输入验证码");
|
|
|
|
|
PasswordField passwordField = new PasswordField();
|
|
|
|
|
passwordField.setPromptText("设置密码 (6-10位,含大小写字母和数字)");
|
|
|
|
|
PasswordField confirmPasswordField = new PasswordField();
|
|
|
|
|
confirmPasswordField.setPromptText("确认密码");
|
|
|
|
|
Button registerBtn = new Button("注册");
|
|
|
|
|
Button backBtn = new Button("返回登录");
|
|
|
|
|
Label infoLabel = new Label();
|
|
|
|
|
|
|
|
|
|
sendCodeBtn.setOnAction(e -> {
|
|
|
|
|
String email = emailField.getText().trim();
|
|
|
|
|
if (!email.contains("@")) {
|
|
|
|
|
infoLabel.setText("邮箱格式不正确");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (userDatabase.containsKey(email)) {
|
|
|
|
|
infoLabel.setText("该邮箱已注册");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
generatedCode = String.format("%06d", new Random().nextInt(999999));
|
|
|
|
|
infoLabel.setText("验证码发送中...");
|
|
|
|
|
|
|
|
|
|
new Thread(() -> {
|
|
|
|
|
boolean success = mailSender.sendMail(email, "注册验证码", "你的验证码为:" + generatedCode);
|
|
|
|
|
Platform.runLater(() -> {
|
|
|
|
|
if (success) {
|
|
|
|
|
infoLabel.setText("验证码已发送,请查看邮箱");
|
|
|
|
|
} else {
|
|
|
|
|
infoLabel.setText("发送失败,请检查邮箱");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}).start();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
registerBtn.setOnAction(e -> {
|
|
|
|
|
String email = emailField.getText().trim();
|
|
|
|
|
String inputCode = codeField.getText().trim();
|
|
|
|
|
String pw = passwordField.getText();
|
|
|
|
|
String pwConfirm = confirmPasswordField.getText();
|
|
|
|
|
|
|
|
|
|
if (!inputCode.equals(generatedCode)) {
|
|
|
|
|
infoLabel.setText("验证码错误");
|
|
|
|
|
} else if (!pw.equals(pwConfirm)) {
|
|
|
|
|
infoLabel.setText("两次密码不一致");
|
|
|
|
|
} else if (!isValidPassword(pw)) {
|
|
|
|
|
infoLabel.setText("密码必须6-10位,包含大小写字母和数字");
|
|
|
|
|
} else if (userDatabase.containsKey(email)) {
|
|
|
|
|
infoLabel.setText("邮箱已注册");
|
|
|
|
|
} else {
|
|
|
|
|
userDatabase.put(email, pw);
|
|
|
|
|
infoLabel.setText("注册成功!");
|
|
|
|
|
// 注册成功后自动登录
|
|
|
|
|
currentUser.put("email", email);
|
|
|
|
|
primaryStage.setScene(buildLevelSelectionScene());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
backBtn.setOnAction(e -> {
|
|
|
|
|
primaryStage.setScene(buildLoginScene());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
root.getChildren().addAll(titleLabel, emailField, sendCodeBtn, codeField,
|
|
|
|
|
passwordField, confirmPasswordField, registerBtn, backBtn, infoLabel);
|
|
|
|
|
return new Scene(root, 400, 400);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 密码验证方法
|
|
|
|
|
private boolean isValidPassword(String password) {
|
|
|
|
|
if (password.length() < 6 || password.length() > 10) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
boolean hasUpper = false;
|
|
|
|
|
boolean hasLower = false;
|
|
|
|
|
boolean hasDigit = false;
|
|
|
|
|
|
|
|
|
|
for (char c : password.toCharArray()) {
|
|
|
|
|
if (Character.isUpperCase(c)) hasUpper = true;
|
|
|
|
|
if (Character.isLowerCase(c)) hasLower = true;
|
|
|
|
|
if (Character.isDigit(c)) hasDigit = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hasUpper && hasLower && hasDigit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 难度选择界面
|
|
|
|
|
private Scene buildLevelSelectionScene() {
|
|
|
|
|
VBox root = new VBox(15);
|
|
|
|
|
root.setStyle("-fx-padding: 20;");
|
|
|
|
|
Label label = new Label("请选择难度");
|
|
|
|
|
label.setStyle("-fx-font-size: 14; -fx-font-weight: bold;");
|
|
|
|
|
|
|
|
|
|
Button primaryBtn = new Button("小学");
|
|
|
|
|
primaryBtn.setStyle("-fx-font-size: 12; -fx-pref-width: 120;");
|
|
|
|
|
Button juniorBtn = new Button("初中");
|
|
|
|
|
juniorBtn.setStyle("-fx-font-size: 12; -fx-pref-width: 120;");
|
|
|
|
|
Button seniorBtn = new Button("高中");
|
|
|
|
|
seniorBtn.setStyle("-fx-font-size: 12; -fx-pref-width: 120;");
|
|
|
|
|
|
|
|
|
|
Button changePwdBtn = new Button("修改密码");
|
|
|
|
|
changePwdBtn.setStyle("-fx-font-size: 12;");
|
|
|
|
|
Button logoutBtn = new Button("退出登录");
|
|
|
|
|
logoutBtn.setStyle("-fx-font-size: 12;");
|
|
|
|
|
|
|
|
|
|
primaryBtn.setOnAction(e -> buildExamNumberScene("小学"));
|
|
|
|
|
juniorBtn.setOnAction(e -> buildExamNumberScene("初中"));
|
|
|
|
|
seniorBtn.setOnAction(e -> buildExamNumberScene("高中"));
|
|
|
|
|
|
|
|
|
|
changePwdBtn.setOnAction(e -> primaryStage.setScene(buildChangePasswordScene()));
|
|
|
|
|
logoutBtn.setOnAction(e -> {
|
|
|
|
|
currentUser.clear();
|
|
|
|
|
primaryStage.setScene(buildLoginScene());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
VBox buttonBox = new VBox(10, primaryBtn, juniorBtn, seniorBtn);
|
|
|
|
|
buttonBox.setStyle("-fx-alignment: center;");
|
|
|
|
|
|
|
|
|
|
HBox bottomBox = new HBox(10, changePwdBtn, logoutBtn);
|
|
|
|
|
bottomBox.setStyle("-fx-alignment: center;");
|
|
|
|
|
|
|
|
|
|
root.getChildren().addAll(label, buttonBox, bottomBox);
|
|
|
|
|
Scene scene = new Scene(root, 400, 300);
|
|
|
|
|
primaryStage.setScene(scene);
|
|
|
|
|
return scene;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 修改密码界面
|
|
|
|
|
private Scene buildChangePasswordScene() {
|
|
|
|
|
VBox root = new VBox(10);
|
|
|
|
|
root.setStyle("-fx-padding: 20;");
|
|
|
|
|
Label titleLabel = new Label("修改密码");
|
|
|
|
|
titleLabel.setStyle("-fx-font-size: 16; -fx-font-weight: bold;");
|
|
|
|
|
|
|
|
|
|
PasswordField oldPwdField = new PasswordField();
|
|
|
|
|
oldPwdField.setPromptText("请输入原密码");
|
|
|
|
|
PasswordField newPwdField = new PasswordField();
|
|
|
|
|
newPwdField.setPromptText("请输入新密码");
|
|
|
|
|
PasswordField confirmPwdField = new PasswordField();
|
|
|
|
|
confirmPwdField.setPromptText("确认新密码");
|
|
|
|
|
|
|
|
|
|
Button confirmBtn = new Button("确认修改");
|
|
|
|
|
Button backBtn = new Button("返回");
|
|
|
|
|
Label infoLabel = new Label();
|
|
|
|
|
|
|
|
|
|
confirmBtn.setOnAction(e -> {
|
|
|
|
|
String oldPwd = oldPwdField.getText();
|
|
|
|
|
String newPwd = newPwdField.getText();
|
|
|
|
|
String confirmPwd = confirmPwdField.getText();
|
|
|
|
|
String email = currentUser.get("email");
|
|
|
|
|
|
|
|
|
|
if (!userDatabase.get(email).equals(oldPwd)) {
|
|
|
|
|
infoLabel.setText("原密码错误");
|
|
|
|
|
} else if (!newPwd.equals(confirmPwd)) {
|
|
|
|
|
infoLabel.setText("两次输入的新密码不一致");
|
|
|
|
|
} else if (!isValidPassword(newPwd)) {
|
|
|
|
|
infoLabel.setText("新密码必须6-10位,包含大小写字母和数字");
|
|
|
|
|
} else {
|
|
|
|
|
userDatabase.put(email, newPwd);
|
|
|
|
|
infoLabel.setText("密码修改成功!");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
backBtn.setOnAction(e -> {
|
|
|
|
|
primaryStage.setScene(buildLevelSelectionScene());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
root.getChildren().addAll(titleLabel, oldPwdField, newPwdField, confirmPwdField,
|
|
|
|
|
confirmBtn, backBtn, infoLabel);
|
|
|
|
|
return new Scene(root, 400, 300);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 输入题目数量界面
|
|
|
|
|
private void buildExamNumberScene(String level) {
|
|
|
|
|
VBox root = new VBox(10);
|
|
|
|
|
root.setStyle("-fx-padding: 20;");
|
|
|
|
|
Label label = new Label("请输入" + level + "题目数量 (10-30)");
|
|
|
|
|
label.setStyle("-fx-font-size: 14;");
|
|
|
|
|
|
|
|
|
|
TextField numberField = new TextField();
|
|
|
|
|
numberField.setPromptText("题目数量");
|
|
|
|
|
Button startBtn = new Button("开始做题");
|
|
|
|
|
Button backBtn = new Button("返回");
|
|
|
|
|
Label infoLabel = new Label();
|
|
|
|
|
|
|
|
|
|
startBtn.setOnAction(e -> {
|
|
|
|
|
try {
|
|
|
|
|
int count = Integer.parseInt(numberField.getText().trim());
|
|
|
|
|
if (count < 10 || count > 30) {
|
|
|
|
|
infoLabel.setText("题目数量必须在10-30之间");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
generateExam(level, count);
|
|
|
|
|
currentIndex = 0;
|
|
|
|
|
score = 0;
|
|
|
|
|
primaryStage.setScene(buildExamScene());
|
|
|
|
|
} catch (NumberFormatException ex) {
|
|
|
|
|
infoLabel.setText("请输入有效数字");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
backBtn.setOnAction(e -> {
|
|
|
|
|
primaryStage.setScene(buildLevelSelectionScene());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
root.getChildren().addAll(label, numberField, startBtn, backBtn, infoLabel);
|
|
|
|
|
primaryStage.setScene(new Scene(root, 400, 250));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成题目
|
|
|
|
|
private void generateExam(String level, int count) {
|
|
|
|
|
currentExam.clear();
|
|
|
|
|
examManager = new ExamManager(level);
|
|
|
|
|
List<String> questionTexts = examManager.generateExam(count);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < questionTexts.size(); i++) {
|
|
|
|
|
String questionText = (i + 1) + ". " + questionTexts.get(i) + " = ?";
|
|
|
|
|
String correctAnswer = questionGenerator.calculateAnswer(questionTexts.get(i));
|
|
|
|
|
|
|
|
|
|
List<String> options = questionGenerator.generateOptions(correctAnswer);
|
|
|
|
|
currentExam.add(new Question(questionText, options, correctAnswer));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 考试界面
|
|
|
|
|
private Scene buildExamScene() {
|
|
|
|
|
if (currentIndex >= currentExam.size()) {
|
|
|
|
|
return buildScoreScene();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Question q = currentExam.get(currentIndex);
|
|
|
|
|
VBox root = new VBox(15);
|
|
|
|
|
root.setStyle("-fx-padding: 20;");
|
|
|
|
|
|
|
|
|
|
Label progressLabel = new Label("进度: " + (currentIndex + 1) + "/" + currentExam.size());
|
|
|
|
|
progressLabel.setStyle("-fx-font-weight: bold;");
|
|
|
|
|
|
|
|
|
|
Label questionLabel = new Label(q.getQuestion());
|
|
|
|
|
questionLabel.setStyle("-fx-font-size: 14; -fx-wrap-text: true;");
|
|
|
|
|
|
|
|
|
|
ToggleGroup group = new ToggleGroup();
|
|
|
|
|
VBox optionsBox = new VBox(8);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < q.getOptions().size(); i++) {
|
|
|
|
|
RadioButton radioBtn = new RadioButton((char)('A' + i) + ". " + q.getOptions().get(i));
|
|
|
|
|
radioBtn.setToggleGroup(group);
|
|
|
|
|
radioBtn.setUserData(q.getOptions().get(i));
|
|
|
|
|
optionsBox.getChildren().add(radioBtn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Button nextBtn = new Button(currentIndex == currentExam.size() - 1 ? "提交" : "下一题");
|
|
|
|
|
nextBtn.setStyle("-fx-font-size: 12;");
|
|
|
|
|
|
|
|
|
|
nextBtn.setOnAction(e -> {
|
|
|
|
|
RadioButton selected = (RadioButton) group.getSelectedToggle();
|
|
|
|
|
if (selected == null) {
|
|
|
|
|
// 如果没有选择,自动选择A选项
|
|
|
|
|
if (!optionsBox.getChildren().isEmpty()) {
|
|
|
|
|
selected = (RadioButton) optionsBox.getChildren().get(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selected != null) {
|
|
|
|
|
String selectedAnswer = (String) selected.getUserData();
|
|
|
|
|
if (selectedAnswer.equals(q.getAnswer())) {
|
|
|
|
|
score++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
currentIndex++;
|
|
|
|
|
primaryStage.setScene(buildExamScene());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
root.getChildren().addAll(progressLabel, questionLabel, optionsBox, nextBtn);
|
|
|
|
|
return new Scene(root, 500, 350);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示分数
|
|
|
|
|
private Scene buildScoreScene() {
|
|
|
|
|
VBox root = new VBox(15);
|
|
|
|
|
root.setStyle("-fx-padding: 30; -fx-alignment: center;");
|
|
|
|
|
|
|
|
|
|
int percentage = (int) ((score * 100.0) / currentExam.size());
|
|
|
|
|
Label scoreLabel = new Label("答题完成!");
|
|
|
|
|
scoreLabel.setStyle("-fx-font-size: 18; -fx-font-weight: bold;");
|
|
|
|
|
|
|
|
|
|
Label percentageLabel = new Label("正确率: " + percentage + "%");
|
|
|
|
|
percentageLabel.setStyle("-fx-font-size: 16;");
|
|
|
|
|
|
|
|
|
|
Label detailLabel = new Label("答对 " + score + " 题 / 总共 " + currentExam.size() + " 题");
|
|
|
|
|
detailLabel.setStyle("-fx-font-size: 14;");
|
|
|
|
|
|
|
|
|
|
HBox buttonBox = new HBox(20);
|
|
|
|
|
buttonBox.setStyle("-fx-alignment: center;");
|
|
|
|
|
|
|
|
|
|
Button retryBtn = new Button("继续做题");
|
|
|
|
|
retryBtn.setStyle("-fx-font-size: 12;");
|
|
|
|
|
Button exitBtn = new Button("退出");
|
|
|
|
|
exitBtn.setStyle("-fx-font-size: 12;");
|
|
|
|
|
|
|
|
|
|
retryBtn.setOnAction(e -> primaryStage.setScene(buildLevelSelectionScene()));
|
|
|
|
|
exitBtn.setOnAction(e -> Platform.exit());
|
|
|
|
|
|
|
|
|
|
buttonBox.getChildren().addAll(retryBtn, exitBtn);
|
|
|
|
|
root.getChildren().addAll(scoreLabel, percentageLabel, detailLabel, buttonBox);
|
|
|
|
|
return new Scene(root, 400, 250);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Question 类
|
|
|
|
|
static class Question {
|
|
|
|
|
private final String question;
|
|
|
|
|
private final List<String> options;
|
|
|
|
|
private final String answer;
|
|
|
|
|
|
|
|
|
|
public Question(String question, List<String> options, String answer) {
|
|
|
|
|
this.question = question;
|
|
|
|
|
this.options = options;
|
|
|
|
|
this.answer = answer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getQuestion() { return question; }
|
|
|
|
|
public List<String> getOptions() { return options; }
|
|
|
|
|
public String getAnswer() { return answer; }
|
|
|
|
|
}
|
|
|
|
|
}
|