wty to develop #1

Merged
psoyzuart merged 20 commits from wangtengyun_branch into develop 4 months ago

@ -1,2 +0,0 @@
# partner_project

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

@ -0,0 +1,170 @@
/* 根容器样式 */
.root {
-fx-font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
-fx-background-image: url("1.jpg");
-fx-background-repeat: no-repeat;
-fx-background-size: cover;
}
/* 通用按钮样式 */
.button {
-fx-background-color: #4CAF50;
-fx-text-fill: white;
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-padding: 10px 20px;
-fx-background-radius: 25px;
-fx-border-radius: 25px;
-fx-cursor: hand;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 5, 0, 0, 2);
-fx-transition: all 0.3s;
}
.button:hover {
-fx-background-color: #45a049;
-fx-scale-x: 1.05;
-fx-scale-y: 1.05;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 8, 0, 0, 3);
}
.button:pressed {
-fx-background-color: #3d8b40;
-fx-scale-x: 0.95;
-fx-scale-y: 0.95;
}
/* 次要按钮样式 */
.button-secondary {
-fx-background-color: #2196F3;
}
.button-secondary:hover {
-fx-background-color: #1976D2;
}
.button-secondary:pressed {
-fx-background-color: #0D47A1;
}
/* 警告按钮样式 */
.button-warning {
-fx-background-color: #ff9800;
}
.button-warning:hover {
-fx-background-color: #f57c00;
}
/* 危险按钮样式 */
.button-danger {
-fx-background-color: #f44336;
}
.button-danger:hover {
-fx-background-color: #d32f2f;
}
/* 标签样式 */
.label {
-fx-text-fill: #333333;
-fx-font-size: 14px;
}
.label-title {
-fx-font-size: 24px;
-fx-font-weight: bold;
-fx-text-fill: #2c3e50;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 3, 0, 1, 1);
}
.label-subtitle {
-fx-font-size: 18px;
-fx-font-weight: bold;
-fx-text-fill: #34495e;
}
.label-info {
-fx-text-fill: #7f8c8d;
-fx-font-size: 12px;
}
/* 输入框样式 */
.text-field, .password-field {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 10px 15px;
-fx-font-size: 14px;
-fx-effect: innershadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2);
}
.text-field:focused, .password-field:focused {
-fx-border-color: #3498db;
-fx-effect: dropshadow(three-pass-box, rgba(52, 152, 219, 0.3), 10, 0, 0, 3);
}
/* 下拉框样式 */
.combo-box {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 5px 15px;
}
.combo-box .arrow-button {
-fx-background-color: transparent;
}
.combo-box .list-cell {
-fx-background-color: white;
-fx-text-fill: #333333;
}
.combo-box .list-view {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 10px;
-fx-background-radius: 10px;
}
/* 单选按钮样式 */
.radio-button {
-fx-text-fill: #333333;
-fx-font-size: 14px;
-fx-padding: 5px;
}
.radio-button .radio {
-fx-background-color: white;
-fx-border-color: #bdc3c7;
-fx-border-radius: 50%;
-fx-background-radius: 50%;
}
.radio-button:selected .radio {
-fx-background-color: #3498db;
-fx-border-color: #2980b9;
}
.radio-button .dot {
-fx-background-color: white;
-fx-background-radius: 50%;
}
/* 容器样式 */
.vbox {
-fx-background-color: rgba(255, 255, 255, 0.5);
-fx-background-radius: 16px;
}
.container {
-fx-background-color: white;
-fx-background-radius: 20px;
-fx-border-radius: 20px;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 15, 0, 0, 5);
-fx-padding: 30px;
}

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.ExamController">
<children>
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="20.0" styleClass="vbox" stylesheets="@css/main.css">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<Label fx:id="titleLabel" style="-fx-font-size: 25; -fx-font-weight: bold;" text="难度" />
<HBox alignment="CENTER" prefHeight="65.0" prefWidth="241.0">
<children>
<ComboBox fx:id="difficultyCombo" prefHeight="37.0" prefWidth="99.0" promptText="难度" />
<Region prefHeight="65.0" prefWidth="31.0" />
<Button fx:id="switchDiffButton" onAction="#handleSwitchDifficulty" prefHeight="39.0" prefWidth="97.0" styleClass="button-warning" text="切换难度" />
</children>
</HBox>
<TextField fx:id="countField" alignment="CENTER" maxWidth="220.0" promptText="输入生成题目数量 (10-30)" />
<Button fx:id="startButton" onAction="#handleStartExam" styleClass="button-secondary" text="生成试卷" />
<Button fx:id="logoutButton" onAction="#handleLogout" styleClass="button-danger" text="退出登录" />
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</children>
</VBox>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" spacing="15" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.ExamController">
<VBox alignment="CENTER_LEFT" maxHeight="300.0" maxWidth="270.0" prefHeight="300.0" styleClass="vbox">
<children>
<Label fx:id="problemText" style="-fx-font-size: 12;">
<VBox.margin>
<Insets left="8.0" />
</VBox.margin>
</Label>
<Region prefHeight="17.0" prefWidth="300.0" />
<VBox fx:id="optionsBox" alignment="CENTER_LEFT" maxHeight="150.0" maxWidth="300.0" spacing="5" />
<HBox alignment="CENTER">
<children>
<Button fx:id="nextButton" onAction="#handleNextProblem" text="下一题" />
</children>
</HBox>
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<?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.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.LoginController">
<children>
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="20.0" styleClass="vbox">
<Label style="-fx-font-size: 25; -fx-font-weight: bold;" text="登 录" />
<TextField fx:id="usernameField" maxWidth="220.0" promptText="用户名/邮箱" />
<PasswordField fx:id="passwordField" maxWidth="220.0" promptText="密码" />
<HBox alignment="CENTER" prefHeight="23.0" prefWidth="143.0">
<children>
<Button fx:id="loginButton" onAction="#handleLogin" styleClass="button-secondary" text="登录" />
<Region prefHeight="23.0" prefWidth="30.0" />
<Button fx:id="registerButton" onAction="#handleRegister" text="注册" />
</children>
</HBox>
<Button fx:id="modifyPasswordButton" onAction="#handleModifyPassword" styleClass="button-warning" text="修改密码" />
<Label fx:id="messageLabel" style="-fx-text-fill: red;" textAlignment="CENTER">
<VBox.margin>
<Insets />
</VBox.margin>
</Label>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</children>
</VBox>

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<?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.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" spacing="10" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.ModifyPasswordController">
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="12.0" styleClass="vbox">
<children>
<Label style="-fx-font-size: 25; -fx-font-weight: bold;" text="修改密码">
<VBox.margin>
<Insets top="8.0" />
</VBox.margin>
</Label>
<TextField fx:id="usernameField" alignment="CENTER" maxWidth="220.0" promptText="用户名" />
<PasswordField fx:id="oldPasswordField" alignment="CENTER" maxWidth="220.0" promptText="旧密码" />
<PasswordField fx:id="newPasswordField" alignment="CENTER" maxWidth="220.0" promptText="新密码" />
<PasswordField fx:id="confirmPasswordField" alignment="CENTER" maxWidth="220.0" promptText="确认新密码" />
<HBox alignment="CENTER" prefHeight="66.0" prefWidth="560.0">
<children>
<Button fx:id="backButton" onAction="#handleBack" styleClass="button-danger" text="返回登录" />
<Region prefHeight="45.0" prefWidth="33.0" />
<Button fx:id="modifyButton" onAction="#handleModifyPassword" styleClass="button-secondary" text="确认修改" />
</children>
</HBox>
<Label fx:id="messageLabel" style="-fx-text-fill: red;" />
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.RegisterController">
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="30.0" styleClass="vbox">
<children>
<Label style="-fx-font-size: 24; -fx-font-weight: bold;" text="注册 (2/3) - 验证注册码">
<VBox.margin>
<Insets top="12.0" />
</VBox.margin>
</Label>
<Label fx:id="emailLabel" text="已发送到: " />
<TextField fx:id="codeField" alignment="CENTER" maxWidth="220.0" prefWidth="201.0" promptText="请输入收到的 6 位注册码" />
<Button fx:id="resendButton" onAction="#handleResendCode" prefHeight="39.0" prefWidth="96.0" text="重新发送" />
<HBox alignment="CENTER" prefHeight="58.0" prefWidth="560.0">
<children>
<Button fx:id="backButton" onAction="#handleBack" styleClass="button-danger" text="返回" />
<Region prefHeight="58.0" prefWidth="49.0" />
<Button fx:id="verifyButton" onAction="#handleVerifyCode" styleClass="button-secondary" text="验证" />
</children>
</HBox>
<Label fx:id="messageLabel" style="-fx-text-fill: red;" />
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.RegisterController">
<children>
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="30.0" styleClass="vbox" stylesheets="@css/main.css">
<Label style="-fx-font-size: 25; -fx-font-weight: bold;" text="注册 (1/3) - 邮箱验证" />
<TextField fx:id="emailField" alignment="CENTER" maxWidth="220.0" promptText="邮箱" />
<Button fx:id="sendCodeButton" onAction="#handleSendCode" prefHeight="39.0" prefWidth="110.0" styleClass="button-secondary" text="发送注册码" />
<Button fx:id="backButton" onAction="#handleBack" styleClass="button-danger" text="返回" />
<Label fx:id="messageLabel" style="-fx-text-fill: red;" />
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</children>
</VBox>

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<?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.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.RegisterController">
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="15.0" styleClass="vbox">
<children>
<Label fx:id="titleLabel" style="-fx-font-size: 25; -fx-font-weight: bold;" text="注册 (3/3) - 设置密码" />
<TextField fx:id="usernameField" alignment="CENTER" maxWidth="220.0" promptText="设置用户名" />
<PasswordField fx:id="passwordField" alignment="CENTER" maxWidth="220.0" promptText="设置密码" />
<PasswordField fx:id="confirmPasswordField" alignment="CENTER" maxWidth="220.0" promptText="确认密码" />
<Button fx:id="completeButton" onAction="#handleCompleteRegistration" prefWidth="96.0" styleClass="button-secondary" text="完成注册" />
<Button fx:id="backButton" onAction="#handleBack" styleClass="button-danger" text="返回" />
<Label fx:id="messageLabel" style="-fx-text-fill: red;" />
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER_LEFT" prefHeight="400.0" prefWidth="600.0" stylesheets="@css/main.css" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.ExamController">
<VBox alignment="CENTER" maxHeight="360.0" maxWidth="270.0" minHeight="360.0" minWidth="270.0" prefHeight="360.0" prefWidth="270.0" spacing="20.0" styleClass="vbox">
<children>
<Label fx:id="scoreLabel" style="-fx-font-size: 25; -fx-font-weight: bold;" text="恭喜您完成答题!" />
<Label fx:id="finalScoreLabel" style="-fx-font-size: 15;" />
<ImageView fitHeight="70.0" fitWidth="130.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@css/2.gif" />
</image>
</ImageView>
<Button fx:id="continueButton" onAction="#handleContinue" prefHeight="39.0" prefWidth="218.0" styleClass="button-secondary" text="继续做题 (返回选择难度)" />
<HBox alignment="CENTER" prefHeight="0.0" prefWidth="270.0">
<children>
<Button fx:id="logoutButton" onAction="#handleLogout" styleClass="button-warning" text="退出登录" />
<Region prefHeight="39.0" prefWidth="28.0" />
<Button fx:id="exitButton" onAction="#handleExit" styleClass="button-danger" text="退出程序" />
</children>
</HBox>
</children>
<VBox.margin>
<Insets left="30.0" />
</VBox.margin>
</VBox>
</VBox>

@ -0,0 +1,442 @@
package ui;
import auth.User;
import generator.Problem;
import service.ExamService;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import auth.AuthService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class ExamController {
@FXML private Label titleLabel;
@FXML private ComboBox<String> difficultyCombo;
@FXML private TextField countField;
@FXML private Label problemText;
@FXML private VBox optionsBox;
@FXML private Button nextButton;
@FXML private Label scoreLabel;
@FXML private Label finalScoreLabel;
private Stage primaryStage;
private User currentUser;
private final ExamService examService = new ExamService();
private List<Problem> currentExam;
private List<String> userAnswers;
private int currentProblemIndex = 0;
private Map<Integer, Map<String, String>> optionMappings = new HashMap<>();
// 添加setter方法用于在界面间传递数据
public void setCurrentExam(List<Problem> currentExam) {
this.currentExam = currentExam;
}
public void setUserAnswers(List<String> userAnswers) {
this.userAnswers = userAnswers;
}
@FXML
public void initialize() {
if (currentUser != null) {
System.out.println("当前用户: " + currentUser.getUsername() + ", 类型: " + currentUser.getType());
}
}
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
public void setCurrentUser(User user) {
this.currentUser = user;
initializeDifficultySelection();
}
private void initializeDifficultySelection() {
if (titleLabel != null) {
titleLabel.setText("难度(当前: " + currentUser.getType() + ")");
}
if (difficultyCombo != null) {
// 清空并重新添加选项
difficultyCombo.getItems().clear();
difficultyCombo.getItems().addAll("小学", "初中", "高中");
// 设置当前用户类型为默认值
difficultyCombo.setValue(currentUser.getType());
}
}
@FXML
private void handleSwitchDifficulty() {
if (difficultyCombo == null) {
showAlert("错误", "界面初始化失败,请重新登录");
return;
}
String newDifficulty = difficultyCombo.getValue();
if (newDifficulty == null) {
showAlert("错误", "请先选择难度");
return;
}
if (currentUser == null) {
System.err.println("错误: currentUser 为 null");
showAlert("错误", "用户信息丢失,请重新登录");
return;
}
if (newDifficulty.equals(currentUser.getType())) {
showAlert("提示", "已经是" + newDifficulty + "难度");
return;
}
try {
// 创建 AuthService 实例
AuthService authService = new AuthService();
// 更新数据库中的难度
boolean success = authService.updateDifficulty(currentUser.getUsername(), newDifficulty);
if (success) {
// 验证更新是否成功 - 重新加载用户数据
User updatedUser = authService.login(currentUser.getUsername(), currentUser.getPassword());
if (updatedUser != null) {
currentUser = updatedUser;
showAlert("成功", "难度已成功切换为: " + newDifficulty);
// 更新界面显示
if (titleLabel != null) {
titleLabel.setText("难度选择 (当前: " + currentUser.getType() + ")");
}
// 重新初始化下拉框
initializeDifficultySelection();
} else {
System.err.println("错误: 重新加载用户信息失败");
showAlert("错误", "用户信息验证失败");
}
} else {
System.err.println("错误: 文件更新失败");
showAlert("错误", "难度切换失败,请检查文件权限或联系管理员");
}
} catch (Exception e) {
System.err.println("难度切换异常: " + e.getMessage());
e.printStackTrace();
showAlert("错误", "难度切换异常: " + e.getMessage());
}
}
@FXML
private void handleStartExam() {
try {
int count = Integer.parseInt(countField.getText());
if (count < 10 || count > 30) {
throw new NumberFormatException();
}
generateAndShowExam(count);
} catch (NumberFormatException ex) {
showAlert("错误", "请输入有效的题目数量 (10-30)。");
} catch (Exception ex) {
showAlert("错误", "生成试卷失败: " + ex.getMessage());
}
}
@FXML
private void handleNextProblem() {
saveCurrentAnswer();
currentProblemIndex++;
if (currentProblemIndex < currentExam.size()) {
showProblem(currentProblemIndex);
} else {
showScoreScreen();
}
}
@FXML
private void handleContinue() {
showDifficultySelection();
}
@FXML
private void handleExit() {
primaryStage.close();
}
@FXML
private void handleLogout() {
showLoginScreen();
}
private void generateAndShowExam(int count) throws Exception {
currentExam = examService.generateExam(currentUser, count);
// 检查试卷是否成功生成
if (currentExam == null || currentExam.isEmpty()) {
throw new Exception("无法生成试卷,请稍后重试");
}
// 初始化所有数据结构
userAnswers = new ArrayList<>(currentExam.size());
optionMappings = new HashMap<>();
for (int i = 0; i < currentExam.size(); i++) {
userAnswers.add(null);
}
currentProblemIndex = 0;
showProblem(currentProblemIndex);
}
private void showProblem(int index) {
try {
// 检查索引是否有效
if (currentExam == null || index < 0 || index >= currentExam.size()) {
showAlert("错误", "题目索引无效,无法显示题目");
return;
}
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/exam-problem.fxml"));
Parent root = loader.load();
ExamController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentUser(currentUser);
controller.setCurrentExam(currentExam);
controller.setUserAnswers(userAnswers);
controller.setOptionMappings(optionMappings); // 传递选项映射
controller.setCurrentProblemIndex(index); // 设置当前题目索引
controller.displayProblem(currentExam.get(index), index);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载题目界面失败: " + e.getMessage());
}
}
public void setOptionMappings(Map<Integer, Map<String, String>> optionMappings) {
this.optionMappings = optionMappings;
}
public void setCurrentProblemIndex(int currentProblemIndex) {
this.currentProblemIndex = currentProblemIndex;
}
// 修改 displayProblem 方法,确保每次都正确初始化
public void displayProblem(Problem problem, int index) {
// 确保当前索引正确设置
this.currentProblemIndex = index;
// 确保 optionMappings 不为 null
if (optionMappings == null) {
optionMappings = new HashMap<>();
}
if (problemText != null) {
problemText.setText((index + 1) + ". " + problem.getQuestionText());
}
if (optionsBox != null) {
optionsBox.getChildren().clear();
ToggleGroup group = new ToggleGroup();
List<String> options = problem.getOptions();
char optionChar = 'A';
// 创建或更新当前题目的选项映射
Map<String, String> currentOptionMap = new HashMap<>();
optionMappings.put(index, currentOptionMap);
for (int i = 0; i < options.size(); i++) {
String optionLabel = String.valueOf(optionChar);
String optionValue = options.get(i);
currentOptionMap.put(optionLabel, optionValue);
RadioButton rb = new RadioButton(optionLabel + ". " + optionValue);
rb.setUserData(optionLabel); // 存储选项字母 A, B, C, D
rb.setToggleGroup(group);
rb.setStyle("-fx-font-size: 12px; -fx-font-weight: bold;");
optionsBox.getChildren().add(rb);
// 恢复选择 - 使用选项字母进行比较
if (userAnswers != null && index < userAnswers.size() &&
userAnswers.get(index) != null &&
userAnswers.get(index).equals(optionLabel)) {
rb.setSelected(true);
}
optionChar++;
}
}
if (nextButton != null) {
nextButton.setText(index < currentExam.size() - 1 ? "下一题" : "提交并评分");
}
}
private void saveCurrentAnswer() {
if (optionsBox == null || userAnswers == null || currentProblemIndex >= userAnswers.size()) {
return;
}
// 查找选中的单选按钮
for (javafx.scene.Node node : optionsBox.getChildren()) {
if (node instanceof RadioButton) {
RadioButton rb = (RadioButton) node;
if (rb.isSelected()) {
String selectedOption = (String) rb.getUserData();
userAnswers.set(currentProblemIndex, selectedOption);
return;
}
}
}
// 如果没有选择任何选项设置为null
userAnswers.set(currentProblemIndex, null);
}
private void showScoreScreen() {
try {
// 保存最后一题的答案
saveCurrentAnswer();
// 计算分数 - 使用新的评分方法
int score = calculateScore();
int correctCount = getCorrectCount();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/score-screen.fxml"));
Parent root = loader.load();
ExamController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentUser(currentUser);
controller.displayScore(score, currentExam.size(), correctCount);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载评分界面失败: " + e.getMessage());
}
}
// 添加计算正确数量的方法
private int getCorrectCount() {
int correctCount = 0;
for (int i = 0; i < currentExam.size(); i++) {
Problem problem = currentExam.get(i);
String userAnswerLetter = userAnswers.get(i);
if (userAnswerLetter != null && optionMappings.containsKey(i)) {
String userSelectedValue = optionMappings.get(i).get(userAnswerLetter);
String correctAnswerValue = problem.getCorrectAnswerOption();
if (userSelectedValue != null && userSelectedValue.equals(correctAnswerValue)) {
correctCount++;
}
}
}
return correctCount;
}
// 修改 displayScore 方法显示更详细的信息
public void displayScore(int score, int totalQuestions, int correctCount) {
if (finalScoreLabel != null) {
finalScoreLabel.setText(String.format("最终得分: %d / 100\n正确题数: %d / %d",
score, correctCount, totalQuestions));
}
}
private void showDifficultySelection() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/difficulty-selection.fxml"));
Parent root = loader.load();
ExamController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentUser(currentUser);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载难度选择界面失败: " + e.getMessage());
}
}
private void showLoginScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载登录界面失败: " + e.getMessage());
}
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setContentText(message);
alert.showAndWait();
}
// 在 ExamController 中添加评分方法
private int calculateScore() {
if (currentExam == null || userAnswers == null ||
currentExam.size() != userAnswers.size()) {
System.err.println("评分错误: 数据不完整");
return 0;
}
int correctCount = 0;
for (int i = 0; i < currentExam.size(); i++) {
Problem problem = currentExam.get(i);
String userAnswerLetter = userAnswers.get(i);
if (userAnswerLetter != null && optionMappings != null && optionMappings.containsKey(i)) {
// 获取用户选择的选项对应的数值
String userSelectedValue = optionMappings.get(i).get(userAnswerLetter);
// 获取正确答案
String correctAnswerValue = problem.getCorrectAnswerOption();
boolean isCorrect = userSelectedValue != null &&
userSelectedValue.equals(correctAnswerValue);
if (isCorrect) {
correctCount++;
}
}
}
double score = ((double) correctCount / currentExam.size()) * 100;
int finalScore = (int) Math.round(score);
return finalScore;
}
}

@ -0,0 +1,125 @@
package ui;
import auth.AuthService;
import auth.User;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class LoginController {
@FXML private TextField usernameField;
@FXML private PasswordField passwordField;
@FXML private Label messageLabel;
private final AuthService authService = new AuthService();
private Stage primaryStage;
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
@FXML
private void handleLogin() {
String username = usernameField.getText();
String password = passwordField.getText();
User user = authService.login(username, password);
if (user != null) {
messageLabel.setText("登录成功!");
showExamSelectScreen(user);
} else {
messageLabel.setText("登录失败,用户名或密码错误。");
}
}
@FXML
private void handleRegister() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-email.fxml"));
Parent root = loader.load();
RegisterController controller = loader.getController();
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
Stage currentStage = (Stage) usernameField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载注册界面失败: " + e.getMessage());
}
}
@FXML
private void handleModifyPassword() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/modify-password.fxml"));
Parent root = loader.load();
ModifyPasswordController controller = loader.getController();
// 确保primaryStage不为null
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
// 备选方案从当前场景获取stage
Stage currentStage = (Stage) usernameField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载修改密码界面失败: " + e.getMessage());
}
}
private void showExamSelectScreen(User user) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/difficulty-selection.fxml"));
Parent root = loader.load();
ExamController controller = loader.getController();
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
controller.setCurrentUser(user);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
Stage currentStage = (Stage) usernameField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
controller.setCurrentUser(user);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载考试选择界面失败: " + e.getMessage());
}
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(title);
alert.setContentText(message);
alert.showAndWait();
}
}

@ -0,0 +1,35 @@
package ui;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.image.Image;
public class MainApplication extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
// 获取控制器并设置primaryStage
LoginController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
// 设置窗口图标
try {
Image icon = new Image(getClass().getResourceAsStream("/fxml/css/3.png"));
primaryStage.getIcons().add(icon);
} catch (Exception e) {
System.err.println("无法加载图标文件: " + e.getMessage());
}
primaryStage.setTitle("小初高数学学习软件");
primaryStage.setScene(scene);
primaryStage.show();
}
}

@ -0,0 +1,109 @@
package ui;
import auth.AuthService;
import auth.User;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
public class ModifyPasswordController {
@FXML private TextField usernameField;
@FXML private PasswordField oldPasswordField;
@FXML private PasswordField newPasswordField;
@FXML private PasswordField confirmPasswordField;
@FXML private Label messageLabel;
private final AuthService authService = new AuthService();
private Stage primaryStage;
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
@FXML
private void handleModifyPassword() {
String username = usernameField.getText().trim();
String oldPassword = oldPasswordField.getText();
String newPassword = newPasswordField.getText();
String confirmPassword = confirmPasswordField.getText();
User user = authService.login(username, oldPassword);
if (user == null) {
messageLabel.setText("错误:用户名或旧密码不正确。");
return;
}
if (!newPassword.equals(confirmPassword)) {
messageLabel.setText("错误:两次输入的新密码不一致。");
return;
}
if (!AuthService.isPasswordValid(newPassword)) {
messageLabel.setText("错误:新密码不符合要求 (6-10位, 需含大小写字母和数字)。");
return;
}
if (authService.updatePassword(username, newPassword)) {
showAlert("成功", "密码修改成功,请重新登录。");
showLoginScreen();
} else {
messageLabel.setText("错误:密码修改失败,请稍后重试。");
}
}
@FXML
private void handleBack() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
Stage currentStage = (Stage) usernameField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载登录界面失败: " + e.getMessage());
}
}
private void showLoginScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载登录界面失败: " + e.getMessage());
}
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setContentText(message);
alert.showAndWait();
}
}

@ -0,0 +1,206 @@
package ui;
import auth.AuthService;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class RegisterController {
@FXML private TextField emailField;
@FXML private TextField codeField;
@FXML private TextField usernameField;
@FXML private PasswordField passwordField;
@FXML private PasswordField confirmPasswordField;
@FXML private Label messageLabel;
@FXML private Label emailLabel;
@FXML private Label titleLabel;
private final AuthService authService = new AuthService();
private final Map<String, String> pendingRegistrations = new HashMap<>();
private Stage primaryStage;
private String currentEmail;
public void setPrimaryStage(Stage primaryStage) {
this.primaryStage = primaryStage;
}
@FXML
private void handleSendCode() {
String email = emailField.getText().trim();
if (!authService.validateEmail(email)) {
messageLabel.setText("错误:邮箱格式不正确。");
return;
}
if (authService.isUsernameOrEmailTaken(null, email)) {
messageLabel.setText("错误:该邮箱已被注册。");
return;
}
String code = authService.sendVerificationCode(email);
if (code != null) {
pendingRegistrations.put(email, code);
currentEmail = email;
showAlert("成功", "验证码已发送到您的邮箱,请查收!");
showRegisterCodeScreen();
} else {
messageLabel.setText("错误:验证码发送失败,请检查邮箱或服务器设置。");
}
}
@FXML
private void handleVerifyCode() {
String inputCode = codeField.getText().trim();
String storedCode = pendingRegistrations.get(currentEmail);
if (inputCode.equals(storedCode)) {
messageLabel.setText("验证成功!");
showRegisterPasswordScreen();
} else {
messageLabel.setText("错误:验证码不正确。");
}
}
@FXML
private void handleResendCode() {
String newCode = authService.sendVerificationCode(currentEmail);
if (newCode != null) {
pendingRegistrations.put(currentEmail, newCode);
messageLabel.setText("新的验证码已发送。");
} else {
messageLabel.setText("错误:重发失败。");
}
}
@FXML
private void handleCompleteRegistration() {
String username = usernameField.getText().trim();
String password = passwordField.getText();
String confirmPassword = confirmPasswordField.getText();
if (!validatePasswordSetup(username, password, confirmPassword)) {
return;
}
if (authService.registerUser(username, password, currentEmail)) {
pendingRegistrations.remove(currentEmail);
showAlert("成功", "注册成功,请登录。");
showLoginScreen();
} else {
messageLabel.setText("致命错误:注册失败,请联系管理员。");
}
}
@FXML
private void handleBack() {
showLoginScreen();
}
private boolean validatePasswordSetup(String username, String password, String confirmPassword) {
if (authService.isUsernameOrEmailTaken(username, null)) {
messageLabel.setText("错误:该用户名已被占用。");
return false;
}
if (!password.equals(confirmPassword)) {
messageLabel.setText("错误:两次密码输入不一致。");
return false;
}
if (!AuthService.isPasswordValid(password)) {
messageLabel.setText("错误:密码不符合要求 (6-10位, 需含大小写字母和数字)。");
return false;
}
return true;
}
private void showRegisterCodeScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-code.fxml"));
Parent root = loader.load();
RegisterController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentEmail(currentEmail);
controller.setPendingRegistrations(pendingRegistrations);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载验证码界面失败: " + e.getMessage());
}
}
private void showRegisterPasswordScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/register-password.fxml"));
Parent root = loader.load();
RegisterController controller = loader.getController();
controller.setPrimaryStage(primaryStage);
controller.setCurrentEmail(currentEmail);
controller.setPendingRegistrations(pendingRegistrations);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载密码设置界面失败: " + e.getMessage());
}
}
private void showLoginScreen() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Login.fxml"));
Parent root = loader.load();
LoginController controller = loader.getController();
if (primaryStage != null) {
controller.setPrimaryStage(primaryStage);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
} else {
Stage currentStage = (Stage) emailField.getScene().getWindow();
controller.setPrimaryStage(currentStage);
Scene scene = new Scene(root);
currentStage.setScene(scene);
}
} catch (IOException e) {
e.printStackTrace();
showAlert("错误", "加载登录界面失败: " + e.getMessage());
}
}
// Setters for sharing data between different registration screens
public void setCurrentEmail(String email) {
this.currentEmail = email;
if (emailLabel != null) {
emailLabel.setText("已发送到: " + email);
}
if (titleLabel != null) {
titleLabel.setText("注册 (3/3) - 设置密码 (邮箱: " + email + ")");
}
}
public void setPendingRegistrations(Map<String, String> pendingRegistrations) {
this.pendingRegistrations.clear();
this.pendingRegistrations.putAll(pendingRegistrations);
}
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setContentText(message);
alert.showAndWait();
}
}
Loading…
Cancel
Save