@ -0,0 +1,8 @@
|
||||
import ui.MainApplication;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
// R1: 启动图形化界面应用 (JavaFX)
|
||||
MainApplication.launch(MainApplication.class, args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package auth;
|
||||
|
||||
public class User {
|
||||
private String username;
|
||||
private String password;
|
||||
private String type;
|
||||
private String email;
|
||||
|
||||
public User(String username, String password, String type, String email) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.type = type;
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public String getPassword() { return password; }
|
||||
public String getType() { return type; }
|
||||
public String getEmail() { return email; }
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package auth;
|
||||
|
||||
public class UserManager {
|
||||
private final AuthService authService;
|
||||
|
||||
public UserManager() {
|
||||
this.authService = new AuthService();
|
||||
}
|
||||
|
||||
public User login(String username, String password) {
|
||||
return authService.login(username, password);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
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,28 @@
|
||||
package generator;
|
||||
|
||||
import util.ExpressionUtils;
|
||||
import java.util.*;
|
||||
|
||||
public class HighGenerator implements ProblemGenerator {
|
||||
@Override
|
||||
public List<Problem> generateProblems(int count) {
|
||||
List<Problem> problems = new ArrayList<>();
|
||||
int attempts = 0;
|
||||
final int maxAttempts = count * 3; // 最多尝试3倍数量
|
||||
|
||||
while (problems.size() < count && attempts < maxAttempts) {
|
||||
Problem problem = ExpressionUtils.generateHighExpr();
|
||||
if (problem != null) {
|
||||
problems.add(problem);
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// 如果无法生成足够题目,用简单题目填充
|
||||
while (problems.size() < count) {
|
||||
problems.add(ExpressionUtils.createProblem("sin(30) + " + (problems.size() + 1)));
|
||||
}
|
||||
|
||||
return problems;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package generator;
|
||||
|
||||
import util.ExpressionUtils;
|
||||
import java.util.*;
|
||||
|
||||
public class MiddleGenerator implements ProblemGenerator {
|
||||
@Override
|
||||
public List<Problem> generateProblems(int count) {
|
||||
List<Problem> problems = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
problems.add(ExpressionUtils.generateMiddleExpr());
|
||||
}
|
||||
return problems;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package generator;
|
||||
|
||||
import util.ExpressionUtils;
|
||||
import java.util.*;
|
||||
|
||||
public class PrimaryGenerator implements ProblemGenerator {
|
||||
@Override
|
||||
public List<Problem> generateProblems(int count) {
|
||||
List<Problem> problems = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
problems.add( ExpressionUtils.generatePrimaryExpr());
|
||||
}
|
||||
return problems;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package generator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
|
||||
public class Problem {
|
||||
private final String expression;
|
||||
private final double result;
|
||||
private final List<String> options;
|
||||
private final String correctAnswerOption;
|
||||
|
||||
public Problem(String expression, double result, List<String> options,
|
||||
String correctAnswerOption) {
|
||||
this.expression = expression;
|
||||
this.result = result;
|
||||
this.options = options;
|
||||
this.correctAnswerOption = correctAnswerOption;
|
||||
}
|
||||
|
||||
public String getQuestionText() {
|
||||
return "计算: " + expression + " = ?";
|
||||
}
|
||||
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
public List<String> getOptions() {
|
||||
return Collections.unmodifiableList(options);
|
||||
}
|
||||
|
||||
public String getCorrectAnswerOption() {
|
||||
return formatResult(result);
|
||||
}
|
||||
|
||||
private String formatResult(double result) {
|
||||
if (Double.isNaN(result)) {
|
||||
return "Error";
|
||||
}
|
||||
if (Math.abs(result - Math.round(result)) < 0.0001) {
|
||||
return String.valueOf((int) Math.round(result));
|
||||
}
|
||||
return String.format("%.2f", result);
|
||||
}
|
||||
|
||||
public double getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getQuestionText()).append("\n");
|
||||
char optionChar = 'A';
|
||||
for (String option : options) {
|
||||
sb.append(optionChar++).append(". ").append(option).append(" ");
|
||||
}
|
||||
sb.append("\n正确答案: ").append(correctAnswerOption);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package generator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ProblemGenerator {
|
||||
List<Problem> generateProblems(int count);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
javafx.version=17.0.16
|
||||
javafx.runtime.version=17.0.16+2
|
||||
javafx.runtime.build=2
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,78 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: 设置路径
|
||||
set JAVA_FX_PATH=javafx-sdk-17.0.16\lib
|
||||
set MAIL_JAR=referenced-lib\activation-1.1.1.jar
|
||||
set ACTIVATION_JAR=referenced-lib\javax.mail-1.6.2.jar
|
||||
|
||||
:: 检查必要的 JAR 文件
|
||||
if not exist "%MAIL_JAR%" (
|
||||
echo 错误: 找不到 %MAIL_JAR%
|
||||
echo 请确保 JavaMail JAR 文件存在于 lib 文件夹中
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%ACTIVATION_JAR%" (
|
||||
echo 警告: 找不到 %ACTIVATION_JAR%,尝试继续编译...
|
||||
)
|
||||
|
||||
:: 清理并创建输出目录
|
||||
if exist out rmdir /s /q out
|
||||
mkdir out
|
||||
|
||||
:: 构建类路径
|
||||
set CLASSPATH=%MAIL_JAR%
|
||||
if exist "%ACTIVATION_JAR%" set CLASSPATH=%CLASSPATH%;%ACTIVATION_JAR%
|
||||
|
||||
echo 类路径: %CLASSPATH%
|
||||
echo.
|
||||
|
||||
:: 编译项目
|
||||
echo 正在编译项目...
|
||||
javac -d out --module-path "%JAVA_FX_PATH%" --add-modules javafx.controls,javafx.fxml ^
|
||||
-cp "referenced-lib\activation-1.1.1.jar;referenced-lib\javax.mail-1.6.2.jar" ^
|
||||
auth\AuthService.java ^
|
||||
auth\User.java ^
|
||||
auth\UserManager.java ^
|
||||
generator\HighGenerator.java ^
|
||||
generator\MiddleGenerator.java ^
|
||||
generator\PrimaryGenerator.java ^
|
||||
generator\Problem.java ^
|
||||
generator\ProblemGenerator.java ^
|
||||
service\ExamService.java ^
|
||||
ui\ExamController.java ^
|
||||
ui\LoginController.java ^
|
||||
ui\MainApplication.java ^
|
||||
ui\ModifyPasswordController.java ^
|
||||
ui\RegisterController.java ^
|
||||
util\EmailService.java ^
|
||||
util\ExpressionUtils.java ^
|
||||
util\FileUtils.java ^
|
||||
util\ModifyUtils.java ^
|
||||
Main.java
|
||||
|
||||
if !errorlevel! neq 0 (
|
||||
echo.
|
||||
echo 编译失败!
|
||||
echo 请检查:
|
||||
echo 1. JavaFX 路径是否正确: %JAVA_FX_PATH%
|
||||
echo 2. JAR 文件是否存在: %CLASSPATH%
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 复制资源文件
|
||||
echo 正在复制资源文件...
|
||||
xcopy fxml out\fxml /E /I /Y >nul 2>&1
|
||||
copy user.txt out\user.txt >nul 2>&1
|
||||
|
||||
:: 运行项目
|
||||
echo 正在启动应用程序...
|
||||
java --module-path "%JAVA_FX_PATH%" ^
|
||||
--add-modules javafx.controls,javafx.fxml ^
|
||||
-cp "out;%CLASSPATH%" ^
|
||||
-Dfile.encoding=UTF-8 ^
|
||||
Main
|
||||
@ -0,0 +1,109 @@
|
||||
package service;
|
||||
|
||||
import auth.User;
|
||||
import generator.Problem;
|
||||
import generator.ProblemGenerator;
|
||||
import generator.PrimaryGenerator;
|
||||
import generator.MiddleGenerator;
|
||||
import generator.HighGenerator;
|
||||
import util.FileUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ExamService {
|
||||
|
||||
public ProblemGenerator getGenerator(String type) {
|
||||
switch (type) {
|
||||
case "小学": return new PrimaryGenerator();
|
||||
case "初中": return new MiddleGenerator();
|
||||
case "高中": return new HighGenerator();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Problem generateUniqueProblem(ProblemGenerator generator,
|
||||
Set<String> historyExpressions) {
|
||||
int attempts = 0;
|
||||
final int maxAttemptsPerProblem = 1000;
|
||||
|
||||
while (attempts < maxAttemptsPerProblem) {
|
||||
Problem problem = generator.generateProblems(1).get(0);
|
||||
// 查重基于题干 (表达式字符串)
|
||||
if (!historyExpressions.contains(problem.getExpression())) {
|
||||
historyExpressions.add(problem.getExpression());
|
||||
return problem;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// R5: 生成一张唯一试卷,并将其保存到用户的历史记录中。
|
||||
public List<Problem> generateExam(User currentUser, int count) {
|
||||
if (count < 10 || count > 30) {
|
||||
throw new IllegalArgumentException("题目数量必须在 10 到 30 之间");
|
||||
}
|
||||
|
||||
ProblemGenerator generator = getGenerator(currentUser.getType());
|
||||
if (generator == null) {
|
||||
System.err.println("错误: 无法获取题目生成器,用户类型: " + currentUser.getType());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Set<String> historyExpressions = FileUtils.loadHistory(currentUser.getUsername());
|
||||
List<Problem> problems = new ArrayList<>();
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
Problem problem = generateUniqueProblem(generator, historyExpressions);
|
||||
if (problem != null) {
|
||||
problems.add(problem);
|
||||
} else {
|
||||
System.err.println("警告: 无法生成足够数量的唯一题目。已生成: " + problems.size() + " 题");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!problems.isEmpty()) {
|
||||
FileUtils.saveProblems(currentUser.getUsername(), problems);
|
||||
} else {
|
||||
System.err.println("错误: 未能生成任何题目");
|
||||
}
|
||||
|
||||
return problems;
|
||||
}
|
||||
|
||||
// 将这个私有方法改为公共方法,以便在其他类中调用
|
||||
public boolean isAnswerCorrect(Problem problem, String userAnswer) {
|
||||
if (userAnswer == null || problem.getCorrectAnswerOption() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isCorrect = userAnswer.trim().equalsIgnoreCase(problem.getCorrectAnswerOption().trim());
|
||||
return isCorrect;
|
||||
}
|
||||
|
||||
// R6: 根据答对的百分比计算分数。
|
||||
public int calculateScore(List<Problem> problems, List<String> answers) {
|
||||
if (problems == null || answers == null ||
|
||||
problems.size() != answers.size() ||
|
||||
problems.isEmpty()) {
|
||||
System.err.println("评分错误: 题目和答案数量不匹配");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int correctCount = 0;
|
||||
for (int i = 0; i < problems.size(); i++) {
|
||||
Problem problem = problems.get(i);
|
||||
String userAnswer = answers.get(i);
|
||||
|
||||
if (isAnswerCorrect(problem, userAnswer)) {
|
||||
correctCount++;
|
||||
}
|
||||
}
|
||||
|
||||
double score = ((double) correctCount / problems.size()) * 100;
|
||||
int finalScore = (int) Math.round(score);
|
||||
return finalScore;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package util;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
public class EmailService {
|
||||
// 邮件配置:请修改此处或设置环境变量
|
||||
private static final String SENDER_EMAIL = "2810672597@qq.com";
|
||||
private static final String SENDER_PASSWORD =("acyhhpwgcqkqdgff");
|
||||
private static final String SMTP_HOST = "smtp.qq.com"; // 例如: smtp.gmail.com
|
||||
private static final String SMTP_PORT = "587"; // 或 465 (SSL)
|
||||
private static final String SUBJECT = "数学学习软件注册验证码";
|
||||
|
||||
public String generateVerificationCode() {
|
||||
Random rand = new Random();
|
||||
return String.format("%06d", rand.nextInt(1000000));
|
||||
}
|
||||
|
||||
private Properties getMailProperties() {
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.starttls.enable", "true");
|
||||
props.put("mail.smtp.host", SMTP_HOST);
|
||||
props.put("mail.smtp.port", SMTP_PORT);
|
||||
return props;
|
||||
}
|
||||
|
||||
private Message createMessage(Session session, String recipientEmail,
|
||||
String code) throws MessagingException {
|
||||
Message message = new MimeMessage(session);
|
||||
message.setFrom(new InternetAddress(SENDER_EMAIL));
|
||||
message.setRecipients(
|
||||
Message.RecipientType.TO,
|
||||
InternetAddress.parse(recipientEmail)
|
||||
);
|
||||
message.setSubject(SUBJECT);
|
||||
message.setText("您的注册验证码是: " + code +
|
||||
"\n\n请在应用中输入此代码完成注册。");
|
||||
return message;
|
||||
}
|
||||
|
||||
// R2: 实际发送验证邮件
|
||||
public boolean sendVerificationEmail(String recipientEmail, String code) {
|
||||
if (SENDER_EMAIL == null || SENDER_PASSWORD == null) {
|
||||
System.err.println("错误: 邮件发送凭证未设置。");
|
||||
return false;
|
||||
}
|
||||
|
||||
Session session = Session.getInstance(getMailProperties(), new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(SENDER_EMAIL, SENDER_PASSWORD);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
Message message = createMessage(session, recipientEmail, code);
|
||||
Transport.send(message);
|
||||
return true;
|
||||
|
||||
} catch (MessagingException e) {
|
||||
System.err.println("邮件发送失败: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package util;
|
||||
|
||||
import generator.Problem;
|
||||
import java.io.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
public class FileUtils {
|
||||
private static final String FOLDER_PATH = "exams/";
|
||||
|
||||
public static String getUserFolder(String username) {
|
||||
String folder = FOLDER_PATH + username;
|
||||
File dir = new File(folder);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
private static void processHistoryFile(File file, Set<String> history) {
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (!line.trim().isEmpty() && line.matches("^\\d+\\..*")) {
|
||||
// 仅提取题干 (表达式字符串) 用于查重
|
||||
history.add(line.substring(line.indexOf('.') + 1).trim());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> loadHistory(String username) {
|
||||
Set<String> history = new HashSet<>();
|
||||
File dir = new File(getUserFolder(username));
|
||||
File[] files = dir.listFiles((d, name) -> name.endsWith(".txt"));
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
processHistoryFile(file, history);
|
||||
}
|
||||
}
|
||||
return history;
|
||||
}
|
||||
|
||||
private static void writeProblem(BufferedWriter bw, int index, Problem problem) throws IOException {
|
||||
bw.write(index + "." + problem.getExpression());
|
||||
bw.newLine();
|
||||
|
||||
char optionChar = 'A';
|
||||
StringBuilder optionsLine = new StringBuilder();
|
||||
for (String option : problem.getOptions()) {
|
||||
optionsLine.append(optionChar++).append(". ").append(option).append(" ");
|
||||
}
|
||||
|
||||
bw.write(optionsLine.toString().trim());
|
||||
bw.newLine();
|
||||
bw.write("答案: " + problem.getCorrectAnswerOption());
|
||||
bw.newLine();
|
||||
bw.newLine();
|
||||
}
|
||||
|
||||
public static void saveProblems(String username, List<Problem> problems) {
|
||||
String folder = getUserFolder(username);
|
||||
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
|
||||
String filename = folder + "/" + timestamp + ".txt";
|
||||
|
||||
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filename))) {
|
||||
int index = 1;
|
||||
for (Problem problem : problems) {
|
||||
writeProblem(bw, index++, problem);
|
||||
}
|
||||
System.out.println("卷子已保存到: " + filename);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package util;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class to modify specific fields in user.txt.
|
||||
* 替换了 ModifyClass.java 和 ModifyPassword.java。
|
||||
*/
|
||||
public class ModifyUtils {
|
||||
|
||||
private static List<String> readAllLines(String filePath, List<String> lines) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static void writeAllLines(String filePath, List<String> lines) throws IOException {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
|
||||
for (String l : lines) {
|
||||
writer.write(l + System.lineSeparator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int findTargetLine(List<String> lines, String targetName) {
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
String line = lines.get(i).trim();
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
String[] parts = line.split("\\s+");
|
||||
if (parts.length > 0 && parts[0].equals(targetName)) {
|
||||
return i; // 0-based index
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 核心逻辑:修改文件中特定用户的某个字段 (0-based)
|
||||
public static int modifyFileField(String filePath, String targetName,
|
||||
int fieldIndex, String newValue) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
try {
|
||||
readAllLines(filePath, lines);
|
||||
} catch (IOException e) {
|
||||
System.err.println("读取文件错误: " + e.getMessage());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int idx = findTargetLine(lines, targetName);
|
||||
if (idx == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
String[] parts = lines.get(idx).trim().split("\\s+");
|
||||
if (parts.length > fieldIndex) {
|
||||
parts[fieldIndex] = newValue;
|
||||
lines.set(idx, String.join(" ", parts));
|
||||
try {
|
||||
writeAllLines(filePath, lines);
|
||||
return idx + 1; // 返回 1-based 行号
|
||||
} catch (IOException e) {
|
||||
System.err.println("写入文件错误: " + e.getMessage());
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
System.out.println("目标行元素不足。");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue