parent
8f97b4fc83
commit
f3222a487e
@ -0,0 +1,7 @@
|
||||
package model;
|
||||
|
||||
public enum Grade {
|
||||
primary,
|
||||
middle,
|
||||
high
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package model;
|
||||
|
||||
public class HighMaker extends Student {
|
||||
final static String[] binaryOps = {"+", "-", "*", "/"}; // 操作符
|
||||
final static String[] trigOps = {"sin", "cos", "tan"};
|
||||
int operandCount;
|
||||
String[] questionParts; // 操作数
|
||||
boolean[] specialOpFlags; // 特殊操作符索引序列
|
||||
int parenStart;
|
||||
int parenEnd;
|
||||
|
||||
public HighMaker(String name, String password, String path) {
|
||||
super(name, password, path);
|
||||
}
|
||||
|
||||
// 数据预处理
|
||||
public void getRandom() {
|
||||
this.operandCount = random.nextInt(4) + 2;
|
||||
this.questionParts = new String[this.operandCount];
|
||||
|
||||
// 使用基类方法随机决定特殊操作符的数量和位置
|
||||
this.specialOpFlags = randomMaker(this.operandCount);
|
||||
|
||||
// 生成题目各部分
|
||||
for (int i = 0; i < operandCount; i++) {
|
||||
int operand = random.nextInt(100) + 1;
|
||||
if (specialOpFlags[i]) {
|
||||
String op = trigOps[random.nextInt(trigOps.length)];
|
||||
questionParts[i] = op + "(" + operand + ")";
|
||||
} else {
|
||||
questionParts[i] = String.valueOf(operand);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用基类方法生成有效括号
|
||||
int[] parenPos = bracketMaker(this.operandCount);
|
||||
this.parenStart = parenPos[0];
|
||||
this.parenEnd = parenPos[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String makeOneQuestion() {
|
||||
getRandom();
|
||||
StringBuilder question = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < operandCount; i++) {
|
||||
if (i == parenStart) {
|
||||
question.append("(");
|
||||
}
|
||||
question.append(questionParts[i]);
|
||||
if (i == parenEnd) {
|
||||
question.append(")");
|
||||
}
|
||||
|
||||
if (i < operandCount - 1) {
|
||||
String op = binaryOps[random.nextInt(binaryOps.length)];
|
||||
question.append(" ").append(op).append(" ");
|
||||
}
|
||||
}
|
||||
return question.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package model;
|
||||
|
||||
public class MiddleMaker extends Student {
|
||||
final static String[] binaryOps = {"+", "-", "*", "/"}; // 操作符
|
||||
final static String[] unaryOps = {"^2", "√"};
|
||||
int operandCount;
|
||||
String[] questionParts; // 操作数
|
||||
boolean[] specialOpFlags; // 特殊操作符索引序列
|
||||
int parenStart;
|
||||
int parenEnd;
|
||||
|
||||
public MiddleMaker(String name, String password, String path) {
|
||||
super(name, password, path);
|
||||
}
|
||||
|
||||
// 数据预处理
|
||||
public void getRandom() {
|
||||
this.operandCount = random.nextInt(4) + 2;
|
||||
this.questionParts = new String[this.operandCount];
|
||||
|
||||
// 使用基类方法随机决定特殊操作符的数量和位置
|
||||
this.specialOpFlags = randomMaker(this.operandCount);
|
||||
|
||||
// 生成题目各部分
|
||||
for (int i = 0; i < this.operandCount; i++) {
|
||||
int operand = random.nextInt(100) + 1;
|
||||
if (this.specialOpFlags[i]) {
|
||||
String op = unaryOps[random.nextInt(unaryOps.length)];
|
||||
if (op.equals("√")) {
|
||||
this.questionParts[i] = op + operand;
|
||||
} else {
|
||||
this.questionParts[i] = operand + op;
|
||||
}
|
||||
} else {
|
||||
this.questionParts[i] = String.valueOf(operand);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用基类方法生成有效括号
|
||||
int[] parenPos = bracketMaker(this.operandCount);
|
||||
this.parenStart = parenPos[0];
|
||||
this.parenEnd = parenPos[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String makeOneQuestion() {
|
||||
getRandom();
|
||||
StringBuilder question = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < operandCount; i++) {
|
||||
if (i == parenStart) {
|
||||
question.append("(");
|
||||
}
|
||||
question.append(questionParts[i]);
|
||||
if (i == parenEnd) {
|
||||
question.append(")");
|
||||
}
|
||||
if (i < operandCount - 1) {
|
||||
question.append(" ").append(binaryOps[random.nextInt(binaryOps.length)]).append(" ");
|
||||
}
|
||||
}
|
||||
return question.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package model;
|
||||
|
||||
public class PrimaryMaker extends Student {
|
||||
final static char[] operators = {'+', '-', '*', '/'}; // 操作符
|
||||
int operandCount; // 操作数个数
|
||||
int[] operands; // 操作数列表
|
||||
int parenStart; // 左括号索引
|
||||
int parenEnd; // 右括号
|
||||
|
||||
public PrimaryMaker(String name, String password, String path) {
|
||||
super(name, password, path);
|
||||
}
|
||||
|
||||
// 数据预处理
|
||||
public void getRandom() {
|
||||
// 随机决定操作数个数
|
||||
this.operandCount = random.nextInt(4) + 2;
|
||||
this.operands = new int[operandCount];
|
||||
for (int j = 0; j < operandCount; j++) {
|
||||
operands[j] = random.nextInt(100) + 1;
|
||||
}
|
||||
|
||||
// 使用基类方法生成有效括号
|
||||
int[] parenPos = bracketMaker(this.operandCount);
|
||||
this.parenStart = parenPos[0];
|
||||
this.parenEnd = parenPos[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String makeOneQuestion() {
|
||||
getRandom();
|
||||
StringBuilder question = new StringBuilder();
|
||||
|
||||
for (int j = 0; j < this.operandCount; j++) {
|
||||
if (j == this.parenStart) {
|
||||
question.append("(");
|
||||
}
|
||||
question.append(operands[j]);
|
||||
if (j == this.parenEnd) {
|
||||
question.append(")");
|
||||
}
|
||||
|
||||
if (j < this.operandCount - 1) {
|
||||
char op = operators[random.nextInt(operators.length)];
|
||||
if (op == '/') {
|
||||
this.operands[j + 1] = this.operands[j + 1] == 0 ? 1 : this.operands[j + 1];
|
||||
}
|
||||
question.append(" ").append(op).append(" ");
|
||||
}
|
||||
}
|
||||
return question.toString();
|
||||
}
|
||||
}
|
||||
@ -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,61 @@
|
||||
package model;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public abstract class Student {
|
||||
protected String name;
|
||||
protected String password;
|
||||
protected String path;
|
||||
protected Random random;
|
||||
|
||||
public Student(String name, String password, String path) {
|
||||
this.name = name;
|
||||
this.password = password;
|
||||
this.path = path;
|
||||
this.random = new Random();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成有效的括号位置
|
||||
* 确保括号不会包围整个表达式
|
||||
*
|
||||
* @param operands 操作数数量
|
||||
* @return 包含起始和结束位置的数组 [parenStart, parenEnd]
|
||||
*/
|
||||
protected int[] bracketMaker(int operands) {
|
||||
boolean useParen = operands > 2 && random.nextBoolean();
|
||||
int parenStart = 0;
|
||||
int parenEnd = operands - 1;
|
||||
|
||||
while (parenStart == 0 && parenEnd == operands - 1) {
|
||||
parenStart = useParen ? random.nextInt(operands - 1) : -2;
|
||||
parenEnd = useParen ? random.nextInt(operands - 1 - parenStart) + parenStart + 1 : -2;
|
||||
}
|
||||
|
||||
return new int[]{parenStart, parenEnd};
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机选择特殊操作符的位置
|
||||
*
|
||||
* @param operands 操作数数量
|
||||
* @return 标记特殊操作符位置的布尔数组
|
||||
*/
|
||||
protected boolean[] randomMaker(int operands) {
|
||||
int specialNumber = Math.min(operands, random.nextInt(operands) + 1);
|
||||
boolean[] specialOpFlags = new boolean[operands];
|
||||
|
||||
for (int i = 0; i < specialNumber; i++) {
|
||||
int pos;
|
||||
do {
|
||||
pos = random.nextInt(operands);
|
||||
} while (specialOpFlags[pos]);
|
||||
specialOpFlags[pos] = true;
|
||||
}
|
||||
|
||||
return specialOpFlags;
|
||||
}
|
||||
|
||||
protected abstract String makeOneQuestion();
|
||||
|
||||
}
|
||||
@ -0,0 +1,149 @@
|
||||
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).{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(400, 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;
|
||||
|
||||
JLabel infoLabel = new JLabel("<html>新密码要求:<br>" +
|
||||
"长度6-10位,必须包含大小写字母和数字</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(15);
|
||||
gbc.gridx = 1;
|
||||
panel.add(oldPasswordField, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 2;
|
||||
panel.add(new JLabel("新密码:"), gbc);
|
||||
|
||||
newPassword1Field = new JPasswordField(15);
|
||||
gbc.gridx = 1;
|
||||
panel.add(newPassword1Field, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 3;
|
||||
panel.add(new JLabel("确认新密码:"), gbc);
|
||||
|
||||
newPassword2Field = new JPasswordField(15);
|
||||
gbc.gridx = 1;
|
||||
panel.add(newPassword2Field, gbc);
|
||||
|
||||
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,191 @@
|
||||
package ui;
|
||||
|
||||
import model.Grade;
|
||||
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 Grade currentGrade;
|
||||
private final JLabel gradeLabel;
|
||||
private final JComboBox<String> gradeComboBox;
|
||||
private final JSpinner questionSpinner;
|
||||
private final JButton startButton;
|
||||
private final JButton changePasswordButton;
|
||||
private final JButton logoutButton;
|
||||
|
||||
/**
|
||||
* 构造年级选择界面
|
||||
*
|
||||
* @param email 用户邮箱
|
||||
* @param initialGrade 初始年级
|
||||
*/
|
||||
public GradeSelectionFrame(String email, Grade initialGrade) {
|
||||
this.email = email;
|
||||
this.currentGrade = initialGrade;
|
||||
|
||||
setTitle("数学学习软件 - 选择年级");
|
||||
setSize(450, 350);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLocationRelativeTo(null);
|
||||
setResizable(false);
|
||||
|
||||
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
|
||||
JPanel topPanel = new JPanel(new BorderLayout());
|
||||
JLabel welcomeLabel = new JLabel("欢迎, " + email, JLabel.CENTER);
|
||||
welcomeLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
||||
topPanel.add(welcomeLabel, BorderLayout.NORTH);
|
||||
|
||||
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.CENTER);
|
||||
|
||||
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||
|
||||
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;
|
||||
centerPanel.add(gradeComboBox, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 1;
|
||||
centerPanel.add(new JLabel("题目数量:"), gbc);
|
||||
|
||||
SpinnerNumberModel spinnerModel = new SpinnerNumberModel(10, 10, 30, 1);
|
||||
questionSpinner = new JSpinner(spinnerModel);
|
||||
gbc.gridx = 1;
|
||||
centerPanel.add(questionSpinner, gbc);
|
||||
|
||||
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);
|
||||
|
||||
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
|
||||
changePasswordButton = new JButton("修改密码");
|
||||
logoutButton = new JButton("退出登录");
|
||||
bottomPanel.add(changePasswordButton);
|
||||
bottomPanel.add(logoutButton);
|
||||
|
||||
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||
|
||||
add(mainPanel);
|
||||
|
||||
startButton.addActionListener(e -> startQuiz());
|
||||
changePasswordButton.addActionListener(e -> changePassword());
|
||||
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 changePassword() {
|
||||
ChangePasswordDialog dialog = new ChangePasswordDialog(this, email);
|
||||
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,142 @@
|
||||
package ui;
|
||||
|
||||
import model.Grade;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 登录和注册界面
|
||||
* 提供用户登录和注册入口
|
||||
*/
|
||||
public class LoginFrame extends JFrame {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final JTextField emailField;
|
||||
private final JPasswordField passwordField;
|
||||
private final JButton loginButton;
|
||||
private final JButton registerButton;
|
||||
private final UserManager userManager;
|
||||
|
||||
/**
|
||||
* 构造登录界面
|
||||
*/
|
||||
public LoginFrame() {
|
||||
this.userManager = new UserManager();
|
||||
|
||||
setTitle("数学学习软件 - 登录");
|
||||
setSize(400, 300);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLocationRelativeTo(null);
|
||||
setResizable(false);
|
||||
|
||||
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);
|
||||
|
||||
gbc.gridwidth = 1;
|
||||
gbc.gridy = 1;
|
||||
mainPanel.add(new JLabel("邮箱:"), gbc);
|
||||
|
||||
emailField = new JTextField(20);
|
||||
gbc.gridx = 1;
|
||||
mainPanel.add(emailField, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 2;
|
||||
mainPanel.add(new JLabel("密码:"), gbc);
|
||||
|
||||
passwordField = new JPasswordField(20);
|
||||
gbc.gridx = 1;
|
||||
mainPanel.add(passwordField, gbc);
|
||||
|
||||
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 = 3;
|
||||
gbc.gridwidth = 2;
|
||||
mainPanel.add(buttonPanel, gbc);
|
||||
|
||||
add(mainPanel);
|
||||
|
||||
loginButton.addActionListener(e -> handleLogin());
|
||||
registerButton.addActionListener(e -> handleRegister());
|
||||
|
||||
getRootPane().setDefaultButton(loginButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户登录
|
||||
*/
|
||||
private void handleLogin() {
|
||||
String email = emailField.getText().trim();
|
||||
String password = new String(passwordField.getPassword());
|
||||
|
||||
if (email.isEmpty() || password.isEmpty()) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"邮箱和密码不能为空",
|
||||
"错误",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
Grade grade = userManager.login(email, password);
|
||||
if (grade != null) {
|
||||
openGradeSelection(email, grade);
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"邮箱或密码错误",
|
||||
"登录失败",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开注册对话框
|
||||
*/
|
||||
private void handleRegister() {
|
||||
RegisterDialog dialog = new RegisterDialog(this, userManager);
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开年级选择界面
|
||||
*
|
||||
* @param email 用户邮箱
|
||||
* @param initialGrade 初始年级
|
||||
*/
|
||||
private void openGradeSelection(String email, Grade initialGrade) {
|
||||
GradeSelectionFrame gradeFrame = new GradeSelectionFrame(email, initialGrade);
|
||||
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,190 @@
|
||||
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 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).{6,10}$");
|
||||
|
||||
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(400, 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;
|
||||
|
||||
JLabel infoLabel = new JLabel("<html>密码要求:<br>" +
|
||||
"1. 长度6-10位<br>" +
|
||||
"2. 必须包含大写字母<br>" +
|
||||
"3. 必须包含小写字母<br>" +
|
||||
"4. 必须包含数字</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(15);
|
||||
gbc.gridx = 1;
|
||||
panel.add(password1Field, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 2;
|
||||
panel.add(new JLabel("确认密码:"), gbc);
|
||||
|
||||
password2Field = new JPasswordField(15);
|
||||
gbc.gridx = 1;
|
||||
panel.add(password2Field, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 3;
|
||||
panel.add(new JLabel("选择年级:"), gbc);
|
||||
|
||||
String[] grades = {"小学", "初中", "高中"};
|
||||
gradeComboBox = new JComboBox<>(grades);
|
||||
gbc.gridx = 1;
|
||||
panel.add(gradeComboBox, gbc);
|
||||
|
||||
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 password1 = new String(password1Field.getPassword());
|
||||
String password2 = new String(password2Field.getPassword());
|
||||
|
||||
if (password1.isEmpty() || password2.isEmpty()) {
|
||||
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);
|
||||
|
||||
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,197 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 构造答题界面
|
||||
*
|
||||
* @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(600, 450);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLocationRelativeTo(null);
|
||||
setResizable(false);
|
||||
|
||||
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;
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.gridwidth = 2;
|
||||
|
||||
questionLabel = new JLabel();
|
||||
questionLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
||||
questionLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 20, 10));
|
||||
centerPanel.add(questionLabel, gbc);
|
||||
|
||||
ButtonGroup optionGroup = new ButtonGroup();
|
||||
optionButtons = new JRadioButton[4];
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
optionButtons[i] = new JRadioButton();
|
||||
optionButtons[i].setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
||||
optionGroup.add(optionButtons[i]);
|
||||
gbc.gridy = i + 1;
|
||||
centerPanel.add(optionButtons[i], gbc);
|
||||
}
|
||||
|
||||
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>");
|
||||
|
||||
String[] options = question.getOptions();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
optionButtons[i].setText(options[i]);
|
||||
optionButtons[i].setSelected(false);
|
||||
}
|
||||
|
||||
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,177 @@
|
||||
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 java.awt.Frame;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 用户注册对话框
|
||||
* 实现邮箱验证码注册功能
|
||||
*/
|
||||
public class RegisterDialog extends JDialog {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Pattern EMAIL_PATTERN =
|
||||
Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
|
||||
|
||||
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(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(5, 5, 5, 5);
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
panel.add(new JLabel("邮箱:"), gbc);
|
||||
|
||||
emailField = new JTextField(20);
|
||||
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(20);
|
||||
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("邮箱格式不正确");
|
||||
return;
|
||||
}
|
||||
|
||||
if (userManager.userExists(email)) {
|
||||
showError("该邮箱已注册");
|
||||
return;
|
||||
}
|
||||
|
||||
generatedCode = generateCode();
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"验证码已发送: " + generatedCode + "\n(实际应用中会发送到邮箱)",
|
||||
"验证码",
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
|
||||
emailField.setEnabled(false);
|
||||
sendCodeButton.setEnabled(false);
|
||||
codeField.setEnabled(true);
|
||||
verifyButton.setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证验证码
|
||||
*/
|
||||
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(400, 300);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLocationRelativeTo(null);
|
||||
setResizable(false);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue