添加注册功能并完善邮箱验证

pull/5/head
杨默涵 2 months ago
parent 580270ef9f
commit eb5c8a061d

@ -31,6 +31,8 @@ mvn exec:java -Dexec.mainClass="com.example.mathsystemtogether.HelloApplication"
## 👤 用户账号
### 预设测试账号
系统预设了9个测试账号
| 用户名 | 密码 | 难度级别 | 题目类型 |
@ -45,12 +47,54 @@ mvn exec:java -Dexec.mainClass="com.example.mathsystemtogether.HelloApplication"
| 王五2 | 123 | 高中 | 三角函数运算 |
| 王五3 | 123 | 高中 | 三角函数运算 |
### 新用户注册
系统支持新用户注册功能:
1. **点击注册按钮**:在登录界面点击"📝 注册"按钮
2. **填写注册信息**
- 用户名至少3个字符
- 密码至少6个字符
- 确认密码
- 邮箱地址
- 难度级别选择
3. **邮箱验证**
- 点击"📤 发送验证码"获取验证码
- 输入收到的6位数字验证码
- 验证码有效期为5分钟
4. **完成注册**:点击"🚀 注册"完成账户创建
5. **自动跳转**:注册成功后自动返回登录界面
**注意**
- 用户名不能重复
- 邮箱地址格式必须正确
- 验证码通过真实邮件发送
- 用户数据保存在本地 `user_data.txt` 文件中
### 邮件服务配置
系统支持真实的邮箱验证码功能已配置QQ邮箱
- **邮箱地址**1961004835@qq.com
- **SMTP服务器**smtp.qq.com:587
- **配置文件**`src/main/resources/mail.properties`
如需修改邮箱配置,请编辑 `mail.properties` 文件。
## 🎮 使用流程
### 1. 用户登录
### 1. 用户登录或注册
**登录现有账户:**
- 输入用户名张三1
- 输入密码123
- 点击"登录"按钮
- 点击"🚀 登录"按钮
**注册新账户:**
- 点击"📝 注册"按钮
- 填写注册信息(用户名、密码、邮箱、难度级别)
- 获取并输入邮箱验证码
- 点击"🚀 注册"完成账户创建
### 2. 设置考试
- 选择难度级别(小学/初中/高中)
@ -125,10 +169,16 @@ MathSystemTogether/
├── src/main/java/com/example/mathsystemtogether/
│ ├── HelloApplication.java # 主应用程序入口
│ ├── ExamController.java # 考试系统控制器
│ ├── RegisterController.java # 注册功能控制器
│ ├── EmailService.java # 邮件服务类
│ ├── Question.java # 选择题数据模型
│ └── ChoiceQuestionGenerator.java # 选择题生成器
├── src/main/resources/com/example/mathsystemtogether/
│ └── exam-view.fxml # 考试界面布局文件
│ ├── exam-view.fxml # 考试界面布局文件
│ └── register-view.fxml # 注册界面布局文件
├── src/main/resources/
│ └── mail.properties # 邮件服务配置文件
├── user_data.txt # 用户数据存储文件
├── papers/ # 题目文件存储目录
│ ├── 张三1/ # 用户文件夹
│ ├── 李四1/
@ -140,6 +190,12 @@ MathSystemTogether/
## 🔧 核心功能
### 用户管理功能
- **用户注册**:支持新用户注册,包含邮箱验证
- **用户登录**:支持预设账号和注册账号登录
- **数据存储**:用户信息保存在本地文件中
- **数据验证**:用户名唯一性、邮箱格式验证
### 题目生成算法
- **小学**:基础四则运算(+、-、×、÷)
- **初中**:平方运算(²)、开方运算(√)
@ -156,6 +212,14 @@ MathSystemTogether/
- 进度实时更新
- 结果自动统计
### 邮箱验证功能
- **真实邮件发送**通过SMTP服务器发送验证码邮件
- **验证码生成**6位数字随机验证码
- **时效控制**验证码5分钟有效期
- **防重复发送**60秒倒计时限制
- **格式验证**:邮箱地址格式检查
- **HTML邮件**美观的HTML格式验证码邮件
## 📊 考试结果
系统会显示:

@ -61,6 +61,14 @@
<target>21</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>com.example.mathsystemtogether.HelloApplication</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>

@ -7,11 +7,22 @@ echo 系统功能:
echo - 支持小学、初中、高中三个难度级别
echo - 选择题形式答题
echo - 自动评分和结果统计
echo - 用户注册功能(邮箱验证码)
echo - 本地用户数据存储
echo.
echo 测试账号:
echo - 小学张三1/123, 张三2/123, 张三3/123
echo - 初中李四1/123, 李四2/123, 李四3/123
echo - 高中王五1/123, 王五2/123, 王五3/123
echo.
echo 新用户注册:
echo - 点击"注册"按钮创建新账户
echo - 使用邮箱获取验证码
echo - 选择适合的难度级别
echo.
echo 邮件服务配置:
echo - 已配置QQ邮箱1961004835@qq.com
echo - 配置文件src/main/resources/mail.properties
echo.
mvn exec:java -Dexec.mainClass="com.example.mathsystemtogether.HelloApplication"
pause

@ -1,125 +0,0 @@
package com.example.mathsystemtogether;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import jakarta.mail.Authenticator;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.PasswordAuthentication;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
/**
*
* sendEmail SMTP
*/
public class EmailCodeService {
private static class CodeRecord {
final String code;
final Instant expireAt;
CodeRecord(String code, Instant expireAt) {
this.code = code;
this.expireAt = expireAt;
}
}
private final Map<String, CodeRecord> emailToCode = new ConcurrentHashMap<>();
private final Map<String, Instant> emailToLastSend = new ConcurrentHashMap<>();
private final Random random = new Random();
private final Duration codeTtl = Duration.ofMinutes(5);
private final Duration rateLimit = Duration.ofSeconds(60);
public void sendCode(String email) {
Instant now = Instant.now();
Instant last = emailToLastSend.get(email);
if (last != null && Duration.between(last, now).compareTo(rateLimit) < 0) {
long waitSeconds = rateLimit.minus(Duration.between(last, now)).getSeconds();
throw new IllegalStateException("请求过于频繁,请于" + waitSeconds + "秒后重试");
}
String code = generateCode();
emailToCode.put(email, new CodeRecord(code, now.plus(codeTtl)));
emailToLastSend.put(email, now);
// 使用 SMTP 发送邮件QQ邮箱
sendEmail(email, code);
}
public boolean verifyCode(String email, String code) {
CodeRecord record = emailToCode.get(email);
if (record == null) return false;
if (Instant.now().isAfter(record.expireAt)) {
emailToCode.remove(email);
return false;
}
boolean ok = record.code.equals(code);
if (ok) {
emailToCode.remove(email); // 一次性
}
return ok;
}
private String generateCode() {
return String.valueOf(100000 + random.nextInt(900000));
}
private void sendEmail(String email, String code) {
// 从资源文件读取发件配置
Properties conf = new Properties();
try {
conf.load(EmailCodeService.class.getResourceAsStream("/com/example/mathsystemtogether/mail.properties"));
} catch (Exception e) {
System.out.println("[EmailCodeService] 读取 mail.properties 失败,改为控制台输出验证码:" + code);
return;
}
String from = conf.getProperty("mail.from", "");
String authCode = conf.getProperty("mail.authCode", "");
String host = conf.getProperty("mail.host", "smtp.qq.com");
String port = conf.getProperty("mail.port", "465");
boolean ssl = Boolean.parseBoolean(conf.getProperty("mail.ssl", "true"));
String subject = conf.getProperty("mail.subject", "您的验证码");
String bodyPrefix = conf.getProperty("mail.bodyPrefix", "验证码:");
String bodySuffix = conf.getProperty("mail.bodySuffix", "5分钟内有效。");
if (from.isEmpty() || authCode.isEmpty()) {
System.out.println("[EmailCodeService] 未配置 mail.from 或 mail.authCode改为控制台输出验证码" + code);
return;
}
Properties props = new Properties();
props.put("mail.smtp.host", host);
props.put("mail.smtp.port", port);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", String.valueOf(ssl));
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(from, authCode);
}
});
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email));
message.setSubject(subject);
message.setText(bodyPrefix + code + bodySuffix);
Transport.send(message);
} catch (MessagingException e) {
throw new IllegalStateException("邮件发送失败:" + e.getMessage(), e);
}
}
}

@ -8,35 +8,22 @@ import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.time.Instant;
import java.util.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
*
*/
public class ExamController {
@FXML private VBox registerPanel;
@FXML private TextField registerUsernameField;
@FXML private PasswordField registerPasswordField;
@FXML private TextField registerEmailField;
@FXML private TextField registerCodeField;
@FXML private Button sendRegisterCodeButton;
@FXML private Button verifyRegisterCodeButton;
@FXML private Label registerStatusLabel;
// 登录界面控件
@FXML private TextField usernameField;
@FXML private PasswordField passwordField;
@FXML private Button loginButton;
@FXML private TextField emailField;
@FXML private TextField emailCodeField;
@FXML private Button sendCodeButton;
@FXML private Button verifyCodeButton;
@FXML private Label emailStatusLabel;
@FXML private Label loginStatusLabel;
@FXML private Button registerButton;
@FXML private Label loginStatusLabel;
// 考试设置界面控件
@FXML private VBox examSetupPanel;
@ -47,36 +34,12 @@ public class ExamController {
@FXML private Button logoutButton;
@FXML private Label statusLabel;
// 添加设置密码面板相关控件引用需要在FXML中添加对应控件
@FXML private VBox setPasswordPanel;
@FXML private PasswordField setPasswordField;
@FXML private PasswordField confirmPasswordField;
@FXML private Button confirmSetPasswordButton;
@FXML private Label setPasswordStatusLabel;
// 添加修改密码面板相关控件引用
@FXML private VBox changePasswordPanel;
@FXML private PasswordField oldPasswordField;
@FXML private PasswordField newPasswordField;
@FXML private PasswordField confirmNewPasswordField;
@FXML private Button confirmChangePasswordButton;
@FXML private Button cancelChangePasswordButton;
@FXML private Label changePasswordStatusLabel;
// 添加修改密码按钮引用
@FXML private Button changePasswordButton;
// 数据成员
private Account currentAccount;
private List<Question> examQuestions;
private int currentQuestionIndex = 0;
private Map<Integer, String> userAnswers = new HashMap<>();
private ChoiceQuestionGenerator questionGenerator;
private final Map<String, Account> userMap = new HashMap<>();
private final EmailCodeService emailCodeService = new EmailCodeService();
private boolean emailVerified = false;
private final Map<String, RegisterInfo> pendingRegistrations = new HashMap<>();
private static final String USER_DATA_FILE = "user_data.txt";
// 常量定义
private static final int MIN_QUESTIONS = 5;
@ -85,344 +48,12 @@ public class ExamController {
@FXML
public void initialize() {
initAccounts();
loadUserDataFromFile();
setupLevelComboBox();
examSetupPanel.setVisible(false);
questionCountField.setText("10");
if (registerPanel != null) {
registerPanel.setVisible(false);
}
if (setPasswordPanel != null) {
setPasswordPanel.setVisible(false);
}
if (changePasswordPanel != null) {
changePasswordPanel.setVisible(false);
}
}
// 添加内部类用于存储注册信息
static class RegisterInfo {
final String username;
String password;
final String email;
final String registerCode;
final Instant expireTime;
boolean codeVerified;
RegisterInfo(String username, String password, String email, String registerCode) {
this.username = username;
this.password = password;
this.email = email;
this.registerCode = registerCode;
this.expireTime = Instant.now().plusSeconds(300); // 5分钟有效期
this.codeVerified = false;
}
public boolean isExpired() {
return Instant.now().isAfter(expireTime);
}
}
@FXML
private void handleVerifyRegisterCode() {
String email = registerEmailField.getText().trim();
String code = registerCodeField.getText().trim();
if (email.isEmpty() || code.isEmpty()) {
registerStatusLabel.setText("请填写邮箱和验证码");
registerStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
// 使用邮箱验证码服务验证
boolean isValid = emailCodeService.verifyCode(email, code);
if (isValid) {
// 检查是否有待处理的注册信息
RegisterInfo registerInfo = pendingRegistrations.get(email);
if (registerInfo != null) {
registerInfo.codeVerified = true;
registerStatusLabel.setText("验证码验证成功,请设置密码");
registerStatusLabel.setStyle("-fx-text-fill: green;");
// 显示设置密码面板
if (setPasswordPanel != null) {
setPasswordPanel.setVisible(true);
}
} else {
registerStatusLabel.setText("注册信息丢失,请重新注册");
registerStatusLabel.setStyle("-fx-text-fill: red;");
}
} else {
registerStatusLabel.setText("验证码错误或已过期");
registerStatusLabel.setStyle("-fx-text-fill: red;");
}
}
@FXML
private void showRegisterPanel() {
if (registerPanel != null) {
registerPanel.setVisible(true);
}
}
@FXML
private void hideRegisterPanel() {
// 隐藏注册面板
if (registerPanel != null) {
registerPanel.setVisible(false);
}
// 显示登录面板相关组件
if (usernameField != null) usernameField.setVisible(true);
if (passwordField != null) passwordField.setVisible(true);
if (emailField != null) emailField.setVisible(true);
if (emailCodeField != null) emailCodeField.setVisible(true);
if (sendCodeButton != null) sendCodeButton.setVisible(true);
if (verifyCodeButton != null) verifyCodeButton.setVisible(true);
if (loginButton != null) loginButton.setVisible(true);
if (emailStatusLabel != null) emailStatusLabel.setVisible(true);
if (loginStatusLabel != null) loginStatusLabel.setVisible(true);
// 清空注册相关字段
if (registerUsernameField != null) registerUsernameField.clear();
if (registerEmailField != null) registerEmailField.clear();
if (registerCodeField != null) registerCodeField.clear();
if (registerStatusLabel != null) registerStatusLabel.setText("");
}
@FXML
private void showSetPasswordPanel() {
if (setPasswordPanel != null) {
setPasswordPanel.setVisible(true);
}
}
/**
*
*/
@FXML
private void hideSetPasswordPanel() {
if (setPasswordPanel != null) {
setPasswordPanel.setVisible(false);
}
}
/**
*
*/
@FXML
private void handleSetPassword() {
String password = setPasswordField.getText();
String confirmPassword = confirmPasswordField.getText();
if (password.isEmpty() || confirmPassword.isEmpty()) {
setPasswordStatusLabel.setText("请输入密码");
setPasswordStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
if (!password.equals(confirmPassword)) {
setPasswordStatusLabel.setText("两次输入的密码不一致");
setPasswordStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
if (!isValidPassword(password)) {
setPasswordStatusLabel.setText("密码必须为6-10位包含大小写字母和数字");
setPasswordStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
// 获取邮箱(从注册邮箱字段)
String email = registerEmailField.getText().trim();
RegisterInfo registerInfo = pendingRegistrations.get(email);
if (registerInfo != null) {
// 更新注册信息中的密码
registerInfo.password = password;
// 完成注册
userMap.put(registerInfo.username, new Account(registerInfo.username, password, Level.));
pendingRegistrations.remove(email);
setPasswordStatusLabel.setText("注册成功!可以使用新账号登录");
setPasswordStatusLabel.setStyle("-fx-text-fill: green;");
// 清空所有注册相关输入框
registerUsernameField.clear();
registerEmailField.clear();
registerCodeField.clear();
setPasswordField.clear();
confirmPasswordField.clear();
// 隐藏面板
if (setPasswordPanel != null) {
setPasswordPanel.setVisible(false);
}
if (registerPanel != null) {
registerPanel.setVisible(false);
}
// 显示登录面板
hideRegisterPanel();
} else {
setPasswordStatusLabel.setText("注册信息丢失,请重新注册");
setPasswordStatusLabel.setStyle("-fx-text-fill: red;");
}
}
// 添加注册按钮事件处理方法
@FXML
private void handleRegisterUser() {
String username = registerUsernameField.getText().trim();
String email = registerEmailField.getText().trim();
if (username.isEmpty() || email.isEmpty()) {
registerStatusLabel.setText("请填写用户名和邮箱");
registerStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
if (userMap.containsKey(username)) {
registerStatusLabel.setText("用户名已存在");
registerStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
try {
// 发送注册验证码
emailCodeService.sendCode(email);
// 生成验证码并创建注册信息(密码将在后续设置)
String tempPassword = ""; // 临时空密码
RegisterInfo registerInfo = new RegisterInfo(username, tempPassword, email, "");
pendingRegistrations.put(email, registerInfo);
registerStatusLabel.setText("注册验证码已发送到邮箱,请查收");
registerStatusLabel.setStyle("-fx-text-fill: green;");
} catch (Exception e) {
registerStatusLabel.setText("发送验证码失败:" + e.getMessage());
registerStatusLabel.setStyle("-fx-text-fill: red;");
}
}
/**
*
*/
@FXML
private void showChangePasswordPanel() {
if (changePasswordPanel != null && currentAccount != null) {
changePasswordPanel.setVisible(true);
changePasswordPanel.toFront();
}
}
/**
*
*/
@FXML
private void hideChangePasswordPanel() {
if (changePasswordPanel != null) {
changePasswordPanel.setVisible(false);
// 清空输入框
oldPasswordField.clear();
newPasswordField.clear();
confirmNewPasswordField.clear();
changePasswordStatusLabel.setText("");
}
}
/**
*
*/
@FXML
private void handleChangePassword() {
if (currentAccount == null) {
changePasswordStatusLabel.setText("请先登录");
changePasswordStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
String oldPassword = oldPasswordField.getText();
String newPassword = newPasswordField.getText();
String confirmNewPassword = confirmNewPasswordField.getText();
if (oldPassword.isEmpty() || newPassword.isEmpty() || confirmNewPassword.isEmpty()) {
changePasswordStatusLabel.setText("请填写所有字段");
changePasswordStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
// 验证原密码
if (!currentAccount.password.equals(oldPassword)) {
changePasswordStatusLabel.setText("原密码错误");
changePasswordStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
// 验证新密码一致性
if (!newPassword.equals(confirmNewPassword)) {
changePasswordStatusLabel.setText("新密码两次输入不一致");
changePasswordStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
// 验证新密码强度
if (!isValidPassword(newPassword)) {
changePasswordStatusLabel.setText("新密码必须为6-10位包含大小写字母和数字");
changePasswordStatusLabel.setStyle("-fx-text-fill: red;");
return;
}
// 更新密码
Account account = userMap.get(currentAccount.username);
if (account != null) {
account.password = newPassword; // 注意需要修改Account类使password可修改
currentAccount.password = newPassword;
changePasswordStatusLabel.setText("密码修改成功");
changePasswordStatusLabel.setStyle("-fx-text-fill: green;");
// 清空输入框
oldPasswordField.clear();
newPasswordField.clear();
confirmNewPasswordField.clear();
// 隐藏面板
hideChangePasswordPanel();
}
}
/**
*
* @param password
* @return
*/
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;
} else if (Character.isLowerCase(c)) {
hasLower = true;
} else if (Character.isDigit(c)) {
hasDigit = true;
}
}
return hasUpper && hasLower && hasDigit;
}
private void initAccounts() {
// 小学三个账号
userMap.put("张三1", new Account("张三1", "123", Level.));
@ -444,6 +75,34 @@ private void handleRegisterUser() {
levelComboBox.setValue("小学");
}
private void loadUserDataFromFile() {
try {
if (!Files.exists(Paths.get(USER_DATA_FILE))) {
return;
}
List<String> lines = Files.readAllLines(Paths.get(USER_DATA_FILE));
for (String line : lines) {
String[] parts = line.split("\\|");
if (parts.length >= 4) {
String username = parts[0];
String password = parts[1];
String levelStr = parts[3];
Level level;
try {
level = Level.valueOf(levelStr);
} catch (IllegalArgumentException e) {
level = Level.; // 默认级别
}
userMap.put(username, new Account(username, password, level));
}
}
} catch (IOException e) {
// 加载用户数据失败,静默处理
}
}
@FXML
private void handleLogin() {
@ -464,28 +123,39 @@ private void handleRegisterUser() {
examSetupPanel.setVisible(true);
loginStatusLabel.setText("登录成功!");
loginStatusLabel.setStyle("-fx-text-fill: green;");
} else {
loginStatusLabel.setText("用户名或密码错误");
loginStatusLabel.setStyle("-fx-text-fill: red;");
}
}
@FXML
private void handleRegister() {
try {
// 打开注册界面
FXMLLoader loader = new FXMLLoader(getClass().getResource("register-view.fxml"));
Scene scene = new Scene(loader.load(), 800, 800);
Stage registerStage = new Stage();
registerStage.setTitle("数学考试系统 - 用户注册");
registerStage.setScene(scene);
registerStage.setResizable(false);
registerStage.show();
} catch (Exception e) {
loginStatusLabel.setText("打开注册界面失败:" + e.getMessage());
loginStatusLabel.setStyle("-fx-text-fill: red;");
}
}
@FXML
private void handleLogout() {
currentAccount = null;
examQuestions = null;
currentQuestionIndex = 0;
userAnswers.clear();
examSetupPanel.setVisible(false);
usernameField.clear();
passwordField.clear();
loginStatusLabel.setText("");
statusLabel.setText("");
emailVerified = false;
if (emailField != null) emailField.clear();
if (emailCodeField != null) emailCodeField.clear();
if (emailStatusLabel != null) emailStatusLabel.setText("");
}
@FXML
@ -508,8 +178,6 @@ private void handleRegisterUser() {
String selectedLevel = levelComboBox.getValue();
ChoiceQuestionGenerator.Level level = ChoiceQuestionGenerator.Level.valueOf(selectedLevel);
// 生成考试题目
questionGenerator = new ChoiceQuestionGenerator();
examQuestions = questionGenerator.generateQuestions(level, count);
@ -529,10 +197,6 @@ private void handleRegisterUser() {
}
}
private void startExam() {
try {
// 打开专门的考试界面
@ -560,7 +224,6 @@ private void handleRegisterUser() {
}
}
// 内部类
static class Account {
final String username;
@ -577,4 +240,4 @@ private void handleRegisterUser() {
enum Level {
, ,
}
}
}

@ -57,7 +57,7 @@
</HBox>
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Button fx:id="loginButton" text="🚀 登录" onAction="#handleLogin" style="-fx-background-color: linear-gradient(to right, #8A2BE2, #9932CC); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 10 20; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
<Button fx:id="registerButton" text="📝 注册" onAction="#showRegisterPanel" style="-fx-background-color: linear-gradient(to right, #32CD32, #228B22); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 10 20; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
<Button fx:id="registerButton" text="📝 注册" onAction="#handleRegister" style="-fx-background-color: linear-gradient(to right, #32CD32, #228B22); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 10 20; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
<Label fx:id="loginStatusLabel" textFill="#8B0000" style="-fx-font-weight: bold;"/>
</HBox>
@ -93,78 +93,8 @@
<Label fx:id="statusLabel" textFill="#4B0082" style="-fx-font-weight: bold; -fx-font-size: 14;"/>
</VBox>
<!-- 在考试设置区域下方添加修改密码按钮 -->
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Button fx:id="changePasswordButton" text="🔑 修改密码" onAction="#showChangePasswordPanel" style="-fx-background-color: linear-gradient(to right, #FF8C00, #FFA500); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 10 20; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
</HBox>
<!-- 添加设置密码面板 -->
<VBox fx:id="setPasswordPanel" spacing="15.0" style="-fx-background-color: linear-gradient(to bottom, #F8F0FF, #E6E6FA, #D8BFD8); -fx-padding: 20; -fx-background-radius: 15; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.3), 15, 0, 0, 5);" visible="false">
<Label text="🔐 设置密码" textFill="#4B0082" textAlignment="CENTER">
<font>
<Font name="System Bold" size="18.0"/>
</font>
</Label>
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="🔑 密码:" minWidth="80.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<PasswordField fx:id="setPasswordField" promptText="6-10位包含大小写字母和数字" prefWidth="180.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="✅ 确认密码:" minWidth="80.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<PasswordField fx:id="confirmPasswordField" promptText="请再次输入密码" prefWidth="180.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Button fx:id="confirmSetPasswordButton" text="✅ 确认设置" onAction="#handleSetPassword" style="-fx-background-color: linear-gradient(to right, #32CD32, #228B22); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 10 20; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
<Button text="❌ 取消" onAction="#hideSetPasswordPanel" style="-fx-background-color: linear-gradient(to right, #DC143C, #B22222); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 10 20; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
<Label fx:id="setPasswordStatusLabel" textFill="#8B0000" style="-fx-font-weight: bold;"/>
</HBox>
</VBox>
<!-- 添加修改密码面板 -->
<VBox fx:id="changePasswordPanel" spacing="15.0" style="-fx-background-color: linear-gradient(to bottom, #F8F0FF, #E6E6FA, #D8BFD8); -fx-padding: 20; -fx-background-radius: 15; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.3), 15, 0, 0, 5);" visible="false">
<Label text="🔑 修改密码" textFill="#4B0082" textAlignment="CENTER">
<font>
<Font name="System Bold" size="18.0"/>
</font>
</Label>
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="🔒 原密码:" minWidth="80.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<PasswordField fx:id="oldPasswordField" promptText="请输入原密码" prefWidth="180.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="🆕 新密码:" minWidth="80.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<PasswordField fx:id="newPasswordField" promptText="6-10位包含大小写字母和数字" prefWidth="180.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="✅ 确认新密码:" minWidth="80.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<PasswordField fx:id="confirmNewPasswordField" promptText="请再次输入新密码" prefWidth="180.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Button fx:id="confirmChangePasswordButton" text="✅ 确认修改" onAction="#handleChangePassword" style="-fx-background-color: linear-gradient(to right, #32CD32, #228B22); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 10 20; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
<Button fx:id="cancelChangePasswordButton" text="❌ 取消" onAction="#hideChangePasswordPanel" style="-fx-background-color: linear-gradient(to right, #DC143C, #B22222); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 10 20; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
<Label fx:id="changePasswordStatusLabel" textFill="#8B0000" style="-fx-font-weight: bold;"/>
</HBox>
</VBox>
<!-- 使用说明 -->
<VBox spacing="15.0" style="-fx-background-color: linear-gradient(to bottom, #F8F0FF, #E6E6FA, #D8BFD8); -fx-padding: 20; -fx-background-radius: 15; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.2), 10, 0, 0, 3);">

@ -1,9 +0,0 @@
mail.from=1961004835@qq.com
mail.authCode=nixyzbpbcrekhgfe
mail.host=smtp.qq.com
mail.port=465
mail.ssl=true
mail.subject=您的验证码
mail.bodyPrefix=验证码:
mail.bodySuffix=5分钟内有效。
Loading…
Cancel
Save