Compare commits

...

9 Commits

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,70 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>math-learning-app</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- JavaMail API -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<!-- JAF (Java Activation Framework) -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>ui.MathLearningApp</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,22 @@
package model;
/**
*
*
*/
public enum GradeType {
/**
*
*/
PRIMARY,
/**
*
*/
MIDDLE,
/**
*
*/
HIGH
}

@ -0,0 +1,64 @@
package model;
/**
*
*
*/
public class Question {
private final String questionText;
private final String[] options;
private final int correctAnswer;
private final String expression;
/**
*
*
* @param expression
* @param questionText
* @param options (4)
* @param correctAnswer (0-3)
*/
public Question(String expression, String questionText, String[] options,
int correctAnswer) {
this.expression = expression;
this.questionText = questionText;
this.options = options;
this.correctAnswer = correctAnswer;
}
/**
*
*
* @return
*/
public String getQuestionText() {
return questionText;
}
/**
*
*
* @return
*/
public String[] getOptions() {
return options;
}
/**
*
*
* @return
*/
public int getCorrectAnswer() {
return correctAnswer;
}
/**
*
*
* @return
*/
public String getExpression() {
return expression;
}
}

@ -0,0 +1,509 @@
package model;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
*
* ,
*/
public class QuestionGenerator {
private static final int MAX_ATTEMPTS = 100;
private final Random random;
/**
*
*/
public QuestionGenerator() {
this.random = new Random();
}
/**
*
*
* @param gradeType
* @param count
* @param email
* @return
*/
public List<Question> generateQuestions(GradeType gradeType,
int count, String email) {
List<Question> questions = new ArrayList<>();
Set<String> existingQuestions = loadUserQuestions(email);
Set<String> currentQuestions = new HashSet<>();
int attempts = 0;
while (questions.size() < count && attempts < MAX_ATTEMPTS) {
String expression = generateExpression(gradeType);
if (!existingQuestions.contains(expression) &&
!currentQuestions.contains(expression)) {
try {
double result = evaluateExpression(expression);
Question question = createQuestion(expression, result);
questions.add(question);
currentQuestions.add(expression);
existingQuestions.add(expression);
} catch (Exception e) {
// 表达式无效,继续尝试
}
}
attempts++;
}
saveUserQuestions(email, new ArrayList<>(currentQuestions));
return questions;
}
/**
*
*
* @param gradeType
* @return
*/
private String generateExpression(GradeType gradeType) {
switch (gradeType) {
case PRIMARY:
return generatePrimaryExpression();
case MIDDLE:
return generateMiddleExpression();
case HIGH:
return generateHighExpression();
default:
return generatePrimaryExpression();
}
}
/**
* ()
*
* @return
*/
private String generatePrimaryExpression() {
int termCount = random.nextInt(2) + 2; // 2或3个操作数
char[] operators = {'+', '-', '*', '/'};
int num1 = random.nextInt(100) + 1;
char op1 = operators[random.nextInt(operators.length)];
int num2;
// 为第一个操作符生成第二个操作数,并确保合法性
if (op1 == '-') {
num2 = random.nextInt(num1 + 1); // 确保 num1 >= num2
} else if (op1 == '/') {
List<Integer> divisors = getDivisors(num1);
num2 = divisors.get(random.nextInt(divisors.size()));
} else { // + or *
num2 = random.nextInt(20) + 1; // 乘法和加法的数小一点,避免结果过大
}
// 如果只有两个操作数,直接返回
if (termCount == 2) {
return String.format("%d %c %d", num1, op1, num2);
}
// --- 如果有三个操作数 ---
String firstPart = String.format("%d %c %d", num1, op1, num2);
double interimResult;
try {
interimResult = evaluateExpression(firstPart);
} catch (Exception e) {
// 如果第一部分计算出错(理论上不应该),则简化为两项操作
return firstPart;
}
// 确保中间结果为非负整数
if (interimResult < 0 || interimResult != (int)interimResult) {
return firstPart; // 不满足条件,提前返回两项操作
}
char op2 = operators[random.nextInt(operators.length)];
int num3;
// 为第二个操作符生成第三个操作数,并确保合法性
if (op2 == '-') {
num3 = random.nextInt((int) interimResult + 1); // 确保 interimResult >= num3
} else if (op2 == '/') {
if (interimResult == 0) { // 除数不能为0
op2 = '+'; // 将操作符改为加法
num3 = random.nextInt(20) + 1;
} else {
List<Integer> divisors = getDivisors((int)interimResult);
num3 = divisors.get(random.nextInt(divisors.size()));
}
} else { // + or *
num3 = random.nextInt(20) + 1;
}
// 随机决定是否以及如何添加括号
if (random.nextBoolean()) {
return String.format("(%d %c %d) %c %d", num1, op1, num2, op2, num3);
} else {
// 检查第二个操作符优先级是否高于第一个
if ((op2 == '*' || op2 == '/') && (op1 == '+' || op1 == '-')) {
return String.format("%d %c (%d %c %d)", num1, op1, num2, op2, num3);
}
// 默认无括号或左侧括号
return String.format("%d %c %d %c %d", num1, op1, num2, op2, num3);
}
}
/**
*
* @param n
* @return
*/
private List<Integer> getDivisors(int n) {
List<Integer> divisors = new ArrayList<>();
if (n == 0) {
divisors.add(1); // 除数不能为0返回1作为安全值
return divisors;
}
n = Math.abs(n); // 处理可能的负数输入
for (int i = 1; i <= n; i++) {
if (n % i == 0) {
divisors.add(i);
}
}
return divisors;
}
/**
*
*
* @param expression
* @return
*/
private String addBrackets(String expression) {
String[] parts = expression.split(" ");
if (parts.length >= 5 && random.nextBoolean()) {
int start = (random.nextInt((parts.length - 3) / 2) + 1) * 2;
int end = Math.min(start + 2, parts.length - 1);
StringBuilder result = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
if (i == start) {
result.append("( ");
}
result.append(parts[i]);
if (i == end) {
result.append(" )");
}
if (i < parts.length - 1) {
result.append(" ");
}
}
return result.toString();
}
return expression;
}
/**
* ()
*
* @return
*/
private String generateMiddleExpression() {
int termCount = random.nextInt(2) + 2; // 2或3个项
StringBuilder expr = new StringBuilder();
char[] operators = {'+', '-', '*', '/'};
int specialOpIndex = random.nextInt(termCount); // 随机决定哪一项是特殊运算
for (int i = 0; i < termCount; i++) {
if (i > 0) {
char op = operators[random.nextInt(operators.length)];
expr.append(" ").append(op).append(" ");
}
if (i == specialOpIndex) {
// 强制生成特殊运算
if (random.nextBoolean()) {
// 生成平方运算
int num = random.nextInt(15) + 1; // 平方的底数小一些
expr.append("pow(").append(num).append(",2)");
} else {
// 生成开方运算, 确保是完全平方数
int sqrtNum = random.nextInt(10) + 1; // 1-10
int perfectSquare = sqrtNum * sqrtNum;
expr.append("(√").append(perfectSquare).append(")");
}
} else {
expr.append(random.nextInt(100) + 1);
}
}
return expr.toString();
}
/**
* ()
*
* @return
*/
private String generateHighExpression() {
int termCount = random.nextInt(2) + 2; // 2或3个项
StringBuilder expr = new StringBuilder();
char[] operators = {'+', '-', '*', '/'};
String[] functions = {"sin", "cos", "tan"};
int trigFuncIndex = random.nextInt(termCount); // 随机决定哪一项是三角函数
for (int i = 0; i < termCount; i++) {
if (i > 0) {
char op = operators[random.nextInt(operators.length)];
expr.append(" ").append(op).append(" ");
}
if (i == trigFuncIndex) {
// 强制生成三角函数
String func = functions[random.nextInt(functions.length)];
int angle;
if ("tan".equals(func)) {
angle = random.nextInt(90); // tan的角度范围 0-89
} else {
angle = random.nextInt(361); // sin/cos的角度范围 0-360
}
expr.append(func).append("(").append(angle).append(")");
} else {
expr.append(random.nextInt(100) + 1);
}
}
return expr.toString();
}
/**
*
*
* @param expression
* @return
*/
private double evaluateExpression(String expression) {
// 预处理表达式,将自定义格式转换为标准格式
String processedExpression = expression.replaceAll("√(\\d+)", "sqrt($1)");
try {
return eval(processedExpression);
} catch (Exception e) {
System.err.println("表达式求值失败: " + expression);
System.err.println("处理后: " + processedExpression);
e.printStackTrace();
throw new IllegalArgumentException("无效的表达式: " + expression, e);
}
}
/**
*
*
* @param expr
* @return
*/
private double eval(String expr) {
return new Object() {
int pos = -1;
int ch;
void nextChar() {
ch = (++pos < expr.length()) ? expr.charAt(pos) : -1;
}
boolean eat(int charToEat) {
while (ch == ' ') {
nextChar();
}
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
double parse() {
nextChar();
double x = parseExpression();
if (pos < expr.length()) {
throw new RuntimeException("Unexpected: " + (char) ch);
}
return x;
}
double parseExpression() {
double x = parseTerm();
for (;;) {
if (eat('+')) {
x += parseTerm();
} else if (eat('-')) {
x -= parseTerm();
} else {
return x;
}
}
}
double parseTerm() {
double x = parseFactor();
for (;;) {
if (eat('*')) {
x *= parseFactor();
} else if (eat('/')) {
x /= parseFactor();
} else {
return x;
}
}
}
double parseFactor() {
if (eat('+')) {
return parseFactor();
}
if (eat('-')) {
return -parseFactor();
}
double x;
int startPos = this.pos;
if (eat('(')) {
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') {
while ((ch >= '0' && ch <= '9') || ch == '.') {
nextChar();
}
x = Double.parseDouble(expr.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') {
while (ch >= 'a' && ch <= 'z') {
nextChar();
}
String func = expr.substring(startPos, this.pos);
x = parseFactor();
if (func.equals("sqrt")) {
x = Math.sqrt(x);
} else if (func.equals("sin")) {
x = Math.sin(x);
} else if (func.equals("cos")) {
x = Math.cos(x);
} else if (func.equals("tan")) {
x = Math.tan(x);
} else {
throw new RuntimeException("Unknown function: " + func);
}
} else {
throw new RuntimeException("Unexpected: " + (char) ch);
}
return x;
}
}.parse();
}
/**
*
*
* @param expression
* @param correctAnswer
* @return
*/
private Question createQuestion(String expression, double correctAnswer) {
String questionText = "计算: " + expression + " = ?";
double[] options = new double[4];
int correctIndex = random.nextInt(4);
options[correctIndex] = correctAnswer;
for (int i = 0; i < 4; i++) {
if (i != correctIndex) {
double offset = (random.nextDouble() * 20 - 10);
if (Math.abs(offset) < 1) {
offset = random.nextBoolean() ? 5 : -5;
}
options[i] = correctAnswer + offset;
}
}
String[] optionStrings = new String[4];
for (int i = 0; i < 4; i++) {
optionStrings[i] = String.format("%c. %.2f",
(char) ('A' + i), options[i]);
}
return new Question(expression, questionText, optionStrings, correctIndex);
}
/**
*
*
* @param email
* @return
*/
private Set<String> loadUserQuestions(String email) {
Set<String> questions = new HashSet<>();
File userDir = new File("questions" + File.separator + email);
if (!userDir.exists() || !userDir.isDirectory()) {
return questions;
}
File[] files = userDir.listFiles((dir, name) -> name.endsWith(".txt"));
if (files == null) {
return questions;
}
for (File file : files) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.matches("\\d+\\.\\s+.*")) {
String question = line.substring(line.indexOf('.') + 1).trim();
questions.add(question);
}
}
} catch (IOException e) {
System.err.println("读取文件失败: " + e.getMessage());
}
}
return questions;
}
/**
*
*
* @param email
* @param questions
*/
private void saveUserQuestions(String email, List<String> questions) {
File userDir = new File("questions" + File.separator + email);
if (!userDir.exists()) {
if (!userDir.mkdirs()) {
System.err.println("创建目录失败");
return;
}
}
LocalDateTime now = LocalDateTime.now();
String fileName = now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
File file = new File(userDir, fileName);
try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
for (int i = 0; i < questions.size(); i++) {
writer.println((i + 1) + ". " + questions.get(i));
if (i < questions.size() - 1) {
writer.println();
}
}
} catch (IOException e) {
System.err.println("保存文件失败: " + e.getMessage());
}
}
}

@ -0,0 +1,154 @@
package model;
import java.util.HashMap;
import java.util.Map;
/**
*
*
*/
public class UserManager {
private static final Map<String, UserInfo> users = new HashMap<>();
private static final Map<String, String> usernameToEmail = new HashMap<>(); // 用于检查用户名唯一性
/**
*
*
* @param email
* @param password
* @param gradeType
* @param username
* @return
*/
public boolean register(String email, String password, GradeType gradeType, String username) {
if (users.containsKey(email) || usernameToEmail.containsKey(username)) {
return false;
}
users.put(email, new UserInfo(email, password, gradeType, username));
usernameToEmail.put(username, email);
return true;
}
/**
*
*
* @param email
* @param password
* @return ,null
*/
public UserInfo login(String email, String password) {
UserInfo user = users.get(email);
if (user == null || !user.getPassword().equals(password)) {
return null;
}
return user;
}
/**
*
*
* @param email
* @return
*/
public boolean userExists(String email) {
return users.containsKey(email);
}
/**
*
*
* @param username
* @return
*/
public boolean usernameExists(String username) {
return usernameToEmail.containsKey(username);
}
/**
*
*
* @param email
* @param oldPassword
* @param newPassword
* @return
*/
public boolean changePassword(String email, String oldPassword,
String newPassword) {
UserInfo user = users.get(email);
if (user == null || !user.getPassword().equals(oldPassword)) {
return false;
}
user.setPassword(newPassword);
return true;
}
/**
*
*/
public static class UserInfo {
private final String email;
private String password;
private final GradeType gradeType;
private final String username;
/**
*
*
* @param email
* @param password
* @param gradeType
* @param username
*/
public UserInfo(String email, String password, GradeType gradeType, String username) {
this.email = email;
this.password = password;
this.gradeType = gradeType;
this.username = username;
}
/**
*
*
* @return
*/
public String getEmail() {
return email;
}
/**
*
*
* @return
*/
public String getPassword() {
return password;
}
/**
*
*
* @param password
*/
public void setPassword(String password) {
this.password = password;
}
/**
*
*
* @return
*/
public GradeType getGradeType() {
return gradeType;
}
/**
*
*
* @return
*/
public String getUsername() {
return username;
}
}
}

@ -0,0 +1,159 @@
package ui;
import model.UserManager;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.regex.Pattern;
/**
*
*
*/
public class ChangePasswordDialog extends JDialog {
private static final long serialVersionUID = 1L;
private static final Pattern PASSWORD_PATTERN =
Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z0-9]{6,10}$");
private final JPasswordField oldPasswordField;
private final JPasswordField newPassword1Field;
private final JPasswordField newPassword2Field;
private final JButton confirmButton;
private final UserManager userManager;
private final String email;
/**
*
*
* @param parent
* @param email
*/
public ChangePasswordDialog(Frame parent, String email) {
super(parent, "修改密码", true);
this.email = email;
this.userManager = new UserManager();
setSize(600, 360);
setLocationRelativeTo(parent);
setResizable(true);
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
JLabel infoLabel = new JLabel("<html>新密码要求:<br>" +
"1. 长度 6-10位<br>" +
"2. 必须包含大写字母<br>" +
"3. 必须包含小写字母<br>" +
"4. 必须包含数字<br>" +
"5. 不能包含特殊字符</html>");
infoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 11));
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
panel.add(infoLabel, gbc);
gbc.gridwidth = 1;
gbc.gridy = 1;
panel.add(new JLabel("原密码:"), gbc);
oldPasswordField = new JPasswordField(20);
gbc.gridx = 1;
gbc.weightx = 1.0;
panel.add(oldPasswordField, gbc);
gbc.weightx = 0.0;
gbc.gridx = 0;
gbc.gridy = 2;
panel.add(new JLabel("新密码:"), gbc);
newPassword1Field = new JPasswordField(20);
gbc.gridx = 1;
gbc.weightx = 1.0;
panel.add(newPassword1Field, gbc);
gbc.weightx = 0.0;
gbc.gridx = 0;
gbc.gridy = 3;
panel.add(new JLabel("确认新密码:"), gbc);
newPassword2Field = new JPasswordField(20);
gbc.gridx = 1;
gbc.weightx = 1.0;
panel.add(newPassword2Field, gbc);
gbc.weightx = 0.0;
confirmButton = new JButton("确认修改");
confirmButton.setPreferredSize(new Dimension(120, 30));
gbc.gridx = 0;
gbc.gridy = 4;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
panel.add(confirmButton, gbc);
add(panel);
confirmButton.addActionListener(e -> handleConfirm());
getRootPane().setDefaultButton(confirmButton);
}
/**
*
*/
private void handleConfirm() {
String oldPassword = new String(oldPasswordField.getPassword());
String newPassword1 = new String(newPassword1Field.getPassword());
String newPassword2 = new String(newPassword2Field.getPassword());
if (oldPassword.isEmpty() || newPassword1.isEmpty() ||
newPassword2.isEmpty()) {
showError("所有字段都不能为空");
return;
}
if (!newPassword1.equals(newPassword2)) {
showError("两次输入的新密码不一致");
return;
}
if (!PASSWORD_PATTERN.matcher(newPassword1).matches()) {
showError("新密码不符合要求:\n" +
"长度6-10位, 必须包含大小写字母和数字, 且不能包含特殊字符");
return;
}
if (userManager.changePassword(email, oldPassword, newPassword1)) {
JOptionPane.showMessageDialog(this,
"密码修改成功!",
"成功",
JOptionPane.INFORMATION_MESSAGE);
dispose();
} else {
showError("原密码错误");
}
}
/**
*
*
* @param message
*/
private void showError(String message) {
JOptionPane.showMessageDialog(this,
message,
"错误",
JOptionPane.ERROR_MESSAGE);
}
}

@ -0,0 +1,234 @@
package ui;
import model.Grade;
import model.UserManager;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
/**
*
* 退
*/
public class GradeSelectionFrame extends JFrame {
private static final long serialVersionUID = 1L;
private final String email;
private final String username;
private Grade currentGrade;
private final JLabel gradeLabel;
private final JComboBox<String> gradeComboBox;
private final JSpinner questionSpinner;
private final JButton startButton;
private final JButton userSettingsButton;
private final JButton logoutButton;
/**
* 使
*
* @param email
* @param initialGrade
*/
public GradeSelectionFrame(String email, Grade initialGrade) {
this(email, initialGrade, email); // 默认显示邮箱作为用户名
}
/**
* 使 UserInfo
*
* @param userInfo
*/
public GradeSelectionFrame(UserManager.UserInfo userInfo) {
this(userInfo.getEmail(), userInfo.getGrade(), userInfo.getUsername());
}
/**
*
*
* @param email
* @param initialGrade
* @param username
*/
private GradeSelectionFrame(String email, Grade initialGrade, String username) {
this.email = email;
this.username = username;
this.currentGrade = initialGrade;
setTitle("数学学习软件 - 选择年级");
setSize(700, 450);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(true);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// --- Top Panel ---
JPanel topPanel = new JPanel(new BorderLayout());
// 欢迎信息(显示用户名)
JLabel welcomeLabel = new JLabel("欢迎, " + username, JLabel.LEFT);
welcomeLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
topPanel.add(welcomeLabel, BorderLayout.WEST);
// 用户设置按钮(右上角)
userSettingsButton = new JButton("用户设置");
JPanel topRightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
topRightPanel.add(userSettingsButton);
topPanel.add(topRightPanel, BorderLayout.EAST);
// 当前年级显示
gradeLabel = new JLabel("当前年级: " + getGradeName(currentGrade),
JLabel.CENTER);
gradeLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
gradeLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
topPanel.add(gradeLabel, BorderLayout.SOUTH);
mainPanel.add(topPanel, BorderLayout.NORTH);
// --- Center Panel ---
JPanel centerPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 5, 10, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 0;
centerPanel.add(new JLabel("选择年级:"), gbc);
String[] grades = {"小学", "初中", "高中"};
gradeComboBox = new JComboBox<>(grades);
gradeComboBox.setSelectedItem(getGradeName(currentGrade));
gbc.gridx = 1;
gbc.weightx = 1.0;
centerPanel.add(gradeComboBox, gbc);
gbc.weightx = 0.0;
gbc.gridx = 0;
gbc.gridy = 1;
centerPanel.add(new JLabel("题目数量:"), gbc);
SpinnerNumberModel spinnerModel = new SpinnerNumberModel(10, 10, 30, 1);
questionSpinner = new JSpinner(spinnerModel);
// 禁用 JSpinner 的文本输入功能,只允许通过按钮调节
JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) questionSpinner.getEditor();
editor.getTextField().setEditable(false);
gbc.gridx = 1;
gbc.weightx = 1.0;
centerPanel.add(questionSpinner, gbc);
gbc.weightx = 0.0;
startButton = new JButton("开始答题");
startButton.setPreferredSize(new Dimension(200, 40));
startButton.setFont(new Font("微软雅黑", Font.BOLD, 14));
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
centerPanel.add(startButton, gbc);
mainPanel.add(centerPanel, BorderLayout.CENTER);
// --- Bottom Panel ---
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
logoutButton = new JButton("退出登录");
bottomPanel.add(logoutButton);
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
add(mainPanel);
// --- Action Listeners ---
startButton.addActionListener(e -> startQuiz());
userSettingsButton.addActionListener(e -> openUserSettings());
logoutButton.addActionListener(e -> logout());
gradeComboBox.addActionListener(e -> updateGrade());
}
/**
*
*/
private void updateGrade() {
String selected = (String) gradeComboBox.getSelectedItem();
switch (selected) {
case "小学":
currentGrade = Grade.primary;
break;
case "初中":
currentGrade = Grade.middle;
break;
case "高中":
currentGrade = Grade.high;
break;
}
gradeLabel.setText("当前年级: " + getGradeName(currentGrade));
}
/**
*
*/
private void startQuiz() {
int questionCount = (Integer) questionSpinner.getValue();
QuizFrame quizFrame = new QuizFrame(email, currentGrade, questionCount);
quizFrame.setVisible(true);
this.dispose();
}
/**
*
*/
private void openUserSettings() {
UserSettingsDialog dialog = new UserSettingsDialog(this, email, username);
dialog.setVisible(true);
}
/**
* 退
*/
private void logout() {
int confirm = JOptionPane.showConfirmDialog(this,
"确定要退出登录吗?",
"确认",
JOptionPane.YES_NO_OPTION);
if (confirm == JOptionPane.YES_OPTION) {
LoginFrame loginFrame = new LoginFrame();
loginFrame.setVisible(true);
this.dispose();
}
}
/**
*
*
* @param grade
* @return
*/
private String getGradeName(Grade grade) {
switch (grade) {
case primary:
return "小学";
case middle:
return "初中";
case high:
return "高中";
default:
return "未知";
}
}
}

@ -0,0 +1,178 @@
package ui;
import model.UserManager;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
/**
*
*
* <p>
* 使{@link UserManager}
*
* @author
*/
public class LoginFrame extends JFrame {
private static final long serialVersionUID = 1L;
/** 邮箱/用户名输入框 */
private final JTextField accountField;
/** 密码输入框 */
private final JPasswordField passwordField;
/** 登录按钮 */
private final JButton loginButton;
/** 注册按钮 */
private final JButton registerButton;
/** 用户管理器,处理登录验证 */
private final UserManager userManager;
/**
*
*
* <p>
* {@link UserManager}
*/
public LoginFrame() {
this.userManager = new UserManager();
setTitle("数学学习软件 - 登录");
setSize(600, 450);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(true);
JPanel mainPanel = new JPanel(new GridBagLayout());
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
// 标题
JLabel titleLabel = new JLabel("小初高数学学习系统", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20));
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
mainPanel.add(titleLabel, gbc);
// 提示信息
JLabel hintLabel = new JLabel("支持邮箱或用户名登录", JLabel.CENTER);
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 11));
hintLabel.setForeground(java.awt.Color.GRAY);
gbc.gridy = 1;
mainPanel.add(hintLabel, gbc);
// 账号输入
gbc.gridwidth = 1;
gbc.gridy = 2;
mainPanel.add(new JLabel("账号:"), gbc);
accountField = new JTextField(25);
gbc.gridx = 1;
gbc.weightx = 1.0;
mainPanel.add(accountField, gbc);
gbc.weightx = 0.0;
// 密码输入
gbc.gridx = 0;
gbc.gridy = 3;
mainPanel.add(new JLabel("密码:"), gbc);
passwordField = new JPasswordField(25);
gbc.gridx = 1;
gbc.weightx = 1.0;
mainPanel.add(passwordField, gbc);
gbc.weightx = 0.0;
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
loginButton = new JButton("登录");
registerButton = new JButton("注册");
loginButton.setPreferredSize(new Dimension(100, 30));
registerButton.setPreferredSize(new Dimension(100, 30));
buttonPanel.add(loginButton);
buttonPanel.add(registerButton);
gbc.gridx = 0;
gbc.gridy = 4;
gbc.gridwidth = 2;
mainPanel.add(buttonPanel, gbc);
add(mainPanel);
// 事件监听
loginButton.addActionListener(e -> handleLogin());
registerButton.addActionListener(e -> handleRegister());
getRootPane().setDefaultButton(loginButton);
}
/**
*
*
* <p>
*
*/
private void handleLogin() {
String account = accountField.getText().trim();
String password = new String(passwordField.getPassword());
if (account.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(this,
"账号和密码不能为空",
"错误",
JOptionPane.ERROR_MESSAGE);
return;
}
// 使用新的登录方法,支持邮箱或用户名
UserManager.UserInfo userInfo = userManager.loginWithEmailOrUsername(account, password);
if (userInfo != null) {
openGradeSelection(userInfo);
} else {
JOptionPane.showMessageDialog(this,
"账号或密码错误",
"登录失败",
JOptionPane.ERROR_MESSAGE);
}
}
/**
*
*
* <p>{@link RegisterDialog}
*/
private void handleRegister() {
RegisterDialog dialog = new RegisterDialog(this, userManager);
dialog.setVisible(true);
}
/**
*
*
* <p>{@link GradeSelectionFrame}
*
*
* @param userInfo
*/
private void openGradeSelection(UserManager.UserInfo userInfo) {
GradeSelectionFrame gradeFrame = new GradeSelectionFrame(userInfo);
gradeFrame.setVisible(true);
this.dispose();
}
}

@ -0,0 +1,24 @@
package ui;
import javax.swing.SwingUtilities;
/**
*
*
* @author
* @version 1.0
*/
public class MathLearningApp {
/**
*
*
* @param args
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
LoginFrame loginFrame = new LoginFrame();
loginFrame.setVisible(true);
});
}
}

@ -0,0 +1,215 @@
package ui;
import model.Grade;
import model.UserManager;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.Serial;
import java.util.regex.Pattern;
/**
*
*
*/
public class PasswordSetupDialog extends JDialog {
@Serial
private static final long serialVersionUID = 1L;
private static final Pattern PASSWORD_PATTERN =
Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z0-9]{6,10}$");
private final JTextField usernameField;
private final JPasswordField password1Field;
private final JPasswordField password2Field;
private final JComboBox<String> gradeComboBox;
private final JButton confirmButton;
private final UserManager userManager;
private final String email;
private boolean registrationComplete;
/**
*
*
* @param parent
* @param userManager
* @param email
*/
public PasswordSetupDialog(Frame parent, UserManager userManager, String email) {
super(parent, "设置密码和用户名", true);
this.userManager = userManager;
this.email = email;
this.registrationComplete = false;
setSize(600, 420);
setLocationRelativeTo(parent);
setResizable(true);
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
JLabel infoLabel = new JLabel("<html>密码要求:<br>" +
"1. 长度 6-10位<br>" +
"2. 必须包含大写字母<br>" +
"3. 必须包含小写字母<br>" +
"4. 必须包含数字<br>" +
"5. 不能包含其他特殊字符</html>");
infoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 11));
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
panel.add(infoLabel, gbc);
gbc.gridwidth = 1;
gbc.gridy = 1;
panel.add(new JLabel("输入密码:"), gbc);
password1Field = new JPasswordField(20);
gbc.gridx = 1;
gbc.weightx = 1.0;
panel.add(password1Field, gbc);
gbc.weightx = 0.0;
gbc.gridx = 0;
gbc.gridy = 2;
panel.add(new JLabel("确认密码:"), gbc);
password2Field = new JPasswordField(20);
gbc.gridx = 1;
gbc.weightx = 1.0;
panel.add(password2Field, gbc);
gbc.weightx = 0.0;
gbc.gridx = 0;
gbc.gridy = 3;
panel.add(new JLabel("设置用户名:"), gbc);
usernameField = new JTextField(20);
gbc.gridx = 1;
gbc.weightx = 1.0;
panel.add(usernameField, gbc);
gbc.weightx = 0.0;
gbc.gridx = 0;
gbc.gridy = 4;
panel.add(new JLabel("选择年级:"), gbc);
String[] grades = {"小学", "初中", "高中"};
gradeComboBox = new JComboBox<>(grades);
gbc.gridx = 1;
gbc.weightx = 1.0;
panel.add(gradeComboBox, gbc);
gbc.weightx = 0.0;
confirmButton = new JButton("确认注册");
confirmButton.setPreferredSize(new Dimension(120, 30));
gbc.gridx = 0;
gbc.gridy = 5;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
panel.add(confirmButton, gbc);
add(panel);
confirmButton.addActionListener(e -> handleConfirm());
getRootPane().setDefaultButton(confirmButton);
}
/**
*
*/
private void handleConfirm() {
String password1 = new String(password1Field.getPassword());
String password2 = new String(password2Field.getPassword());
String username = usernameField.getText().trim();
if (username.isEmpty() || password1.isEmpty() || password2.isEmpty()) {
showError("用户名和密码不能为空");
return;
}
if (userManager.usernameExists(username)) {
showError("该用户名已被使用,请更换一个");
return;
}
if (!password1.equals(password2)) {
showError("两次输入的密码不一致");
return;
}
if (!PASSWORD_PATTERN.matcher(password1).matches()) {
showError("密码不符合要求:\n" +
"长度6-10位, 必须包含大小写字母和数字, 且不能包含特殊字符");
return;
}
Grade gradeType = getSelectedGradeType();
boolean success = userManager.register(email, password1, gradeType, username);
if (success) {
JOptionPane.showMessageDialog(this,
"注册成功!请返回登录。",
"成功",
JOptionPane.INFORMATION_MESSAGE);
registrationComplete = true;
dispose();
} else {
showError("注册失败,该邮箱可能已被注册");
}
}
/**
*
*
* @return
*/
private Grade getSelectedGradeType() {
String selected = (String) gradeComboBox.getSelectedItem();
switch (selected) {
case "小学":
return Grade.primary;
case "初中":
return Grade.middle;
case "高中":
return Grade.high;
default:
return Grade.primary;
}
}
/**
*
*
* @param message
*/
private void showError(String message) {
JOptionPane.showMessageDialog(this,
message,
"错误",
JOptionPane.ERROR_MESSAGE);
}
/**
*
*
* @return
*/
public boolean isRegistrationComplete() {
return registrationComplete;
}
}

@ -0,0 +1,210 @@
package ui;
import model.Grade;
import model.Question;
import model.QuestionMaker;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.Serial;
import java.util.List;
/**
*
* ,
*/
public class QuizFrame extends JFrame {
@Serial
private static final long serialVersionUID = 1L;
private final String email;
private final Grade gradeType;
private final List<Question> questions;
private int currentIndex;
private int correctCount;
private JLabel questionLabel;
private JLabel progressLabel;
private JRadioButton[] optionButtons;
private JButton submitButton;
private ButtonGroup optionGroup;
/**
*
*
* @param email
* @param gradeType
* @param questionCount
*/
public QuizFrame(String email, Grade gradeType, int questionCount) {
this.email = email;
this.gradeType = gradeType;
this.currentIndex = 0;
this.correctCount = 0;
QuestionMaker generator = new QuestionMaker();
this.questions = generator.makeQuestions(gradeType, questionCount, email);
if (questions.isEmpty()) {
JOptionPane.showMessageDialog(null,
"生成题目失败,返回主界面",
"错误",
JOptionPane.ERROR_MESSAGE);
returnToGradeSelection();
return;
}
setTitle("数学学习软件 - 答题");
setSize(900, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(true);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
progressLabel = new JLabel("", JLabel.CENTER);
progressLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
mainPanel.add(progressLabel, BorderLayout.NORTH);
JPanel centerPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
// 新增:答案格式提示
JLabel answerFormatLabel = new JLabel("注意:所有计算结果的答案均保留两位小数。", JLabel.CENTER);
answerFormatLabel.setFont(new Font("微软雅黑", Font.ITALIC, 12));
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridwidth = 2;
centerPanel.add(answerFormatLabel, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2;
questionLabel = new JLabel();
questionLabel.setFont(new Font("Cambria Math", Font.PLAIN, 18));
questionLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 20, 10));
centerPanel.add(questionLabel, gbc);
optionGroup = new ButtonGroup();
optionButtons = new JRadioButton[4];
for (int i = 0; i < 4; i++) {
optionButtons[i] = new JRadioButton();
optionButtons[i].setFont(new Font("Cambria Math", Font.PLAIN, 14));
optionGroup.add(optionButtons[i]);
gbc.gridy = i + 2;
gbc.gridwidth = 2;
centerPanel.add(optionButtons[i], gbc);
gbc.gridwidth = 1;
}
mainPanel.add(centerPanel, BorderLayout.CENTER);
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
submitButton = new JButton("提交答案");
submitButton.setPreferredSize(new Dimension(150, 35));
submitButton.setFont(new Font("微软雅黑", Font.BOLD, 14));
bottomPanel.add(submitButton);
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
add(mainPanel);
submitButton.addActionListener(e -> handleSubmit());
displayQuestion();
}
/**
*
*/
private void displayQuestion() {
if (currentIndex >= questions.size()) {
showResult();
return;
}
Question question = questions.get(currentIndex);
progressLabel.setText(String.format("第 %d/%d 题",
currentIndex + 1, questions.size()));
questionLabel.setText("<html><body style='width: 500px'>" +
question.getQuestionText() + "</body></html>");
optionGroup.clearSelection();
String[] options = question.getOptions();
for (int i = 0; i < 4; i++) {
optionButtons[i].setText(options[i]);
}
submitButton.setEnabled(true);
}
/**
*
*/
private void handleSubmit() {
int selectedIndex = -1;
for (int i = 0; i < 4; i++) {
if (optionButtons[i].isSelected()) {
selectedIndex = i;
break;
}
}
if (selectedIndex == -1) {
JOptionPane.showMessageDialog(this,
"请选择一个答案",
"提示",
JOptionPane.WARNING_MESSAGE);
return;
}
Question question = questions.get(currentIndex);
if (selectedIndex == question.getCorrectAnswer()) {
correctCount++;
}
currentIndex++;
displayQuestion();
}
/**
*
*/
private void showResult() {
double percentage = (double) correctCount / questions.size() * 100;
int score = (int) percentage;
ResultFrame resultFrame = new ResultFrame(
email, gradeType, score, correctCount, questions.size());
resultFrame.setVisible(true);
this.dispose();
}
/**
*
*/
private void returnToGradeSelection() {
GradeSelectionFrame frame = new GradeSelectionFrame(email, gradeType);
frame.setVisible(true);
this.dispose();
}
}

@ -0,0 +1,278 @@
package ui;
import model.UserManager;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.Properties;
import java.util.regex.Pattern;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
/**
*
*
*/
public class RegisterDialog extends JDialog {
private static final long serialVersionUID = 1L;
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@qq\\.com$");
// 邮件配置 - 请根据实际情况修改
private static final String SMTP_HOST = "smtp.qq.com"; // QQ邮箱SMTP服务器
private static final String SMTP_PORT = "587"; // 或使用465(SSL)
private static final String FROM_EMAIL = "3551664030@qq.com"; // 发件人邮箱
private static final String EMAIL_PASSWORD = "wvazqphcxhuqdbfa"; // 授权码(非登录密码)
private final JTextField emailField;
private final JTextField codeField;
private final JButton sendCodeButton;
private final JButton verifyButton;
private final UserManager userManager;
private String generatedCode;
/**
*
*
* @param parent
* @param userManager
*/
public RegisterDialog(Frame parent, UserManager userManager) {
super(parent, "用户注册", true);
this.userManager = userManager;
setSize(600, 300);
setLocationRelativeTo(parent);
setResizable(false);
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 0;
panel.add(new JLabel("邮箱:"), gbc);
emailField = new JTextField(25);
gbc.gridx = 1;
gbc.gridwidth = 2;
panel.add(emailField, gbc);
sendCodeButton = new JButton("发送验证码");
gbc.gridx = 3;
gbc.gridwidth = 1;
panel.add(sendCodeButton, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
panel.add(new JLabel("验证码:"), gbc);
codeField = new JTextField(25);
codeField.setEnabled(false);
gbc.gridx = 1;
gbc.gridwidth = 2;
panel.add(codeField, gbc);
verifyButton = new JButton("验证");
verifyButton.setEnabled(false);
gbc.gridx = 3;
gbc.gridwidth = 1;
panel.add(verifyButton, gbc);
add(panel);
sendCodeButton.addActionListener(e -> sendVerificationCode());
verifyButton.addActionListener(e -> verifyCode());
}
/**
*
*/
private void sendVerificationCode() {
String email = emailField.getText().trim();
if (email.isEmpty()) {
showError("邮箱不能为空");
return;
}
if (!EMAIL_PATTERN.matcher(email).matches()) {
showError("请输入有效的QQ邮箱(以@qq.com结尾)");
return;
}
if (userManager.userExists(email)) {
showError("该邮箱已注册");
return;
}
generatedCode = generateCode();
// 禁用按钮,防止重复点击
sendCodeButton.setEnabled(false);
sendCodeButton.setText("发送中...");
// 使用SwingWorker在后台线程发送邮件避免阻塞UI
new SwingWorker<Boolean, Void>() {
@Override
protected Boolean doInBackground() {
return sendEmail(email, generatedCode);
}
@Override
protected void done() {
try {
boolean success = get();
if (success) {
JOptionPane.showMessageDialog(
RegisterDialog.this,
"验证码已发送到您的邮箱,请查收!",
"发送成功",
JOptionPane.INFORMATION_MESSAGE);
emailField.setEnabled(false);
codeField.setEnabled(true);
verifyButton.setEnabled(true);
sendCodeButton.setText("已发送");
} else {
showError("验证码发送失败,请检查邮箱地址或稍后重试");
sendCodeButton.setEnabled(true);
sendCodeButton.setText("发送验证码");
}
} catch (Exception e) {
showError("验证码发送失败: " + e.getMessage());
sendCodeButton.setEnabled(true);
sendCodeButton.setText("发送验证码");
}
}
}.execute();
}
/**
*
*
* @param toEmail
* @param code
* @return
*/
private boolean sendEmail(String toEmail, String code) {
try {
// 配置邮件服务器属性
Properties props = new Properties();
props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.port", SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true"); // 启用TLS
// 如果使用SSL端口465取消下面两行注释
// props.put("mail.smtp.ssl.enable", "true");
// props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
// 创建会话
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(FROM_EMAIL, EMAIL_PASSWORD);
}
});
// 创建邮件消息
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(FROM_EMAIL));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
message.setSubject("注册验证码");
// 邮件内容
String content = String.format(
"<html><body>" +
"<h2>欢迎注册</h2>" +
"<p>您的验证码是:<strong style='font-size: 24px; color: #007bff;'>%s</strong></p>" +
"<p>验证码有效期为10分钟请及时使用。</p>" +
"<p>如果这不是您的操作,请忽略此邮件。</p>" +
"</body></html>",
code
);
message.setContent(content, "text/html; charset=utf-8");
// 发送邮件
Transport.send(message);
return true;
} catch (MessagingException e) {
e.printStackTrace();
return false;
}
}
/**
*
*/
private void verifyCode() {
String inputCode = codeField.getText().trim();
if (inputCode.isEmpty()) {
showError("请输入验证码");
return;
}
if (!inputCode.equals(generatedCode)) {
showError("验证码错误");
return;
}
openPasswordDialog();
}
/**
*
*/
private void openPasswordDialog() {
String email = emailField.getText().trim();
PasswordSetupDialog passwordDialog = new PasswordSetupDialog(
(Frame) getOwner(),
userManager,
email);
passwordDialog.setVisible(true);
if (passwordDialog.isRegistrationComplete()) {
dispose();
}
}
/**
* 6
*
* @return
*/
private String generateCode() {
return String.format("%06d", (int) (Math.random() * 1000000));
}
/**
*
*
* @param message
*/
private void showError(String message) {
JOptionPane.showMessageDialog(this,
message,
"错误",
JOptionPane.ERROR_MESSAGE);
}
}

@ -0,0 +1,127 @@
package ui;
import model.Grade;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
/**
*
* ,退
*/
public class ResultFrame extends JFrame {
private static final long serialVersionUID = 1L;
private final String email;
private final Grade gradeType;
/**
*
*
* @param email
* @param gradeType
* @param score
* @param correctCount
* @param totalCount
*/
public ResultFrame(String email, Grade gradeType, int score,
int correctCount, int totalCount) {
this.email = email;
this.gradeType = gradeType;
setTitle("数学学习软件 - 答题结果");
setSize(600, 420);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(true);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
JPanel centerPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 10, 10, 10);
gbc.gridx = 0;
gbc.gridy = 0;
JLabel titleLabel = new JLabel("答题完成!", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
centerPanel.add(titleLabel, gbc);
gbc.gridy = 1;
JLabel scoreLabel = new JLabel("得分: " + score, JLabel.CENTER);
scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 36));
scoreLabel.setForeground(getScoreColor(score));
centerPanel.add(scoreLabel, gbc);
gbc.gridy = 2;
JLabel detailLabel = new JLabel(
String.format("答对 %d 题 / 共 %d 题", correctCount, totalCount),
JLabel.CENTER);
detailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
centerPanel.add(detailLabel, gbc);
mainPanel.add(centerPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 0));
JButton continueButton = new JButton("继续做题");
JButton exitButton = new JButton("退出");
continueButton.setPreferredSize(new Dimension(120, 35));
exitButton.setPreferredSize(new Dimension(120, 35));
buttonPanel.add(continueButton);
buttonPanel.add(exitButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
add(mainPanel);
continueButton.addActionListener(e -> continueQuiz());
exitButton.addActionListener(e -> exitToSelection());
}
/**
*
*
* @param score
* @return
*/
private Color getScoreColor(int score) {
if (score >= 90) {
return new Color(0, 150, 0);
} else if (score >= 60) {
return new Color(255, 140, 0);
} else {
return new Color(200, 0, 0);
}
}
/**
*
*/
private void continueQuiz() {
GradeSelectionFrame frame = new GradeSelectionFrame(email, gradeType);
frame.setVisible(true);
this.dispose();
}
/**
* 退
*/
private void exitToSelection() {
GradeSelectionFrame frame = new GradeSelectionFrame(email, gradeType);
frame.setVisible(true);
this.dispose();
}
}

@ -0,0 +1,86 @@
package ui;
import javax.swing.*;
import java.awt.*;
/**
*
*
*/
public class UserSettingsDialog extends JDialog {
private static final long serialVersionUID = 1L;
private final String email;
private final String username;
private final Frame parentFrame;
/**
*
*
* @param parent
* @param email
* @param username
*/
public UserSettingsDialog(Frame parent, String email, String username) {
super(parent, "用户设置", true);
this.parentFrame = parent;
this.email = email;
this.username = username;
setSize(400, 250);
setLocationRelativeTo(parent);
setResizable(false);
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(10, 5, 10, 5);
gbc.anchor = GridBagConstraints.WEST;
// 显示用户名
gbc.gridx = 0;
gbc.gridy = 0;
panel.add(new JLabel("用户名:"), gbc);
gbc.gridx = 1;
JLabel usernameLabel = new JLabel(username);
usernameLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
panel.add(usernameLabel, gbc);
// 显示邮箱
gbc.gridx = 0;
gbc.gridy = 1;
panel.add(new JLabel("邮箱:"), gbc);
gbc.gridx = 1;
JLabel emailLabel = new JLabel(email);
emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
panel.add(emailLabel, gbc);
// 修改密码按钮
JButton changePasswordButton = new JButton("修改密码");
changePasswordButton.setPreferredSize(new Dimension(120, 30));
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
gbc.insets = new Insets(20, 5, 10, 5);
panel.add(changePasswordButton, gbc);
add(panel);
// 按钮点击事件
changePasswordButton.addActionListener(e -> openChangePasswordDialog());
}
/**
*
*/
private void openChangePasswordDialog() {
// 关闭当前对话框
this.dispose();
// 打开修改密码对话框
ChangePasswordDialog dialog = new ChangePasswordDialog(parentFrame, email);
dialog.setVisible(true);
}
}
Loading…
Cancel
Save