diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..c0f0a16 --- /dev/null +++ b/src/README.md @@ -0,0 +1,2 @@ +只修改了model后端部分,ui前端部分未更改。 + diff --git a/src/model/Grade.java b/src/model/Grade.java new file mode 100644 index 0000000..d0f8951 --- /dev/null +++ b/src/model/Grade.java @@ -0,0 +1,7 @@ +package model; + +public enum Grade { + primary, + middle, + high +} diff --git a/src/model/HighMaker.java b/src/model/HighMaker.java new file mode 100644 index 0000000..4abfbb6 --- /dev/null +++ b/src/model/HighMaker.java @@ -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(); + } + +} \ No newline at end of file diff --git a/src/model/MiddleMaker.java b/src/model/MiddleMaker.java new file mode 100644 index 0000000..19c158f --- /dev/null +++ b/src/model/MiddleMaker.java @@ -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(); + } + +} \ No newline at end of file diff --git a/src/model/PrimaryMaker.java b/src/model/PrimaryMaker.java new file mode 100644 index 0000000..08851b3 --- /dev/null +++ b/src/model/PrimaryMaker.java @@ -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(); + } +} \ No newline at end of file diff --git a/src/model/Question.java b/src/model/Question.java new file mode 100644 index 0000000..c917616 --- /dev/null +++ b/src/model/Question.java @@ -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; + } +} \ No newline at end of file diff --git a/src/model/QuestionMaker.java b/src/model/QuestionMaker.java new file mode 100644 index 0000000..f78b013 --- /dev/null +++ b/src/model/QuestionMaker.java @@ -0,0 +1,296 @@ +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 QuestionMaker { + private static final int max_attempts=100; + private Student student; + private final Random random; + + public QuestionMaker() { + this.random = new Random(); + } + + public List makeQuestions(Grade grade, + int sum, String email) { + startStudent(grade,email); + + List questions = new ArrayList<>(); + Set exQuestions=loadQuestions(email); + Set currQuestions=new HashSet<>(); + int attempt=0; + + while(questions.size()(currQuestions)); + return questions; + } + + private void startStudent(Grade grade, String email) { + String path="questions"+File.separator+email; + + switch(grade){ + case primary: + student=new PrimaryMaker("primary","",path); + break; + case middle: + student=new MiddleMaker("middle","",path); + break; + case high: + student=new HighMaker("high","",path); + break; + default: + } + } + + private double checkExpression(String expression, Grade grade) { + String expressions=expression.replace(" ",""); + + if (grade == Grade.middle) { + // 初中:处理平方和平方根 + // 先处理括号内的平方:(5)^2 -> (5*5) + expressions = expressions.replaceAll("\\((\\d+)\\)\\^2", "($1*$1)"); + // 处理普通数字的平方:5^2 -> (5*5) + expressions = expressions.replaceAll("(\\d+)\\^2", "($1*$1)"); + // 处理平方根:√16 -> Math.sqrt(16) + expressions = expressions.replaceAll("√(\\d+)", "Math.sqrt($1)"); + } else if (grade == Grade.high) { + // 高中:处理三角函数 + expressions = expressions.replaceAll("sin\\((\\d+)\\)", "Math.sin(Math.toRadians($1))"); + expressions = expressions.replaceAll("cos\\((\\d+)\\)", "Math.cos(Math.toRadians($1))"); + expressions = expressions.replaceAll("tan\\((\\d+)\\)", "Math.tan(Math.toRadians($1))"); + } + return evaluation(expressions); + } + + private double evaluation(String expressions) { + return new Object(){ + int pos=-1; + int ch; + void nextChar() { + ch = (++pos < expressions.length()) ? expressions.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 < expressions.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(expressions.substring(startPos, this.pos)); + } else if (ch >= 'A' && ch <= 'Z') { + // 处理 Math.xxx 函数 + while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '.') { + nextChar(); + } + String func = expressions.substring(startPos, this.pos); + eat('('); + x = parseExpression(); + eat(')'); + + if (func.equals("Math.sqrt")) { + x = Math.sqrt(x); + } else if (func.equals("Math.sin")) { + x = Math.sin(x); + } else if (func.equals("Math.cos")) { + x = Math.cos(x); + } else if (func.equals("Math.tan")) { + x = Math.tan(x); + } else if (func.equals("Math.toRadians")) { + x = Math.toRadians(x); + } else { + throw new RuntimeException("Unknown function: " + func); + } + } else if (ch >= 'a' && ch <= 'z') { + while (ch >= 'a' && ch <= 'z') { + nextChar(); + } + String function = expressions.substring(startPos, this.pos); + x = parseFactor(); + if (function.equals("sqrt")) { + x = Math.sqrt(x); + } else if (function.equals("sin")) { + x = Math.sin(x); + } else if (function.equals("cos")) { + x = Math.cos(x); + } else if (function.equals("tan")) { + x = Math.tan(x); + } else { + throw new RuntimeException("Unknown function: " + function); + } + } else { + throw new RuntimeException("Unexpected: " + (char) ch); + } + + return x; + } + }.parse(); + } + + private Question createQuestion(String expression, double result) { + String text="计算: "+expression+" = ? "; + double[] options=new double[4]; + int currIndex=random.nextInt(4); + + options[currIndex]=result; + + for(int i=0;i<4;i++) { + if(i!=currIndex) { + double offset=(random.nextDouble()*20-10); + if(Math.abs(offset)<1) { + offset=random.nextBoolean() ? 5 : -5 ; + } + options[i]=offset+result; + } + } + + 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, text, optionStrings, currIndex); + + } + + private Set loadQuestions(String email) { + Set questions=new HashSet<>(); + File userDir = new File("questions" + File.separator + email); + + if(!userDir.exists()||!userDir.isDirectory()) { + return questions; + } + + File[] files = userDir.listFiles((_, 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; + } + + private void saveQuestions(String email, List 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()); + } + } +} diff --git a/src/model/Student.java b/src/model/Student.java new file mode 100644 index 0000000..e172e62 --- /dev/null +++ b/src/model/Student.java @@ -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(); + +} diff --git a/src/model/UserManager.java b/src/model/UserManager.java new file mode 100644 index 0000000..2be01b1 --- /dev/null +++ b/src/model/UserManager.java @@ -0,0 +1,118 @@ +package model; + +import java.util.HashMap; +import java.util.Map; + +/** + * 用户管理类 + * 负责用户注册、登录、密码修改等功能 + */ +public class UserManager { + private static final Map users = new HashMap<>(); + + /** + * 用户注册 + * + * @param email 用户邮箱 + * @param password 密码 + * @param grade 年级类型 + * @return 注册是否成功 + */ + public boolean register(String email, String password, Grade grade) { + if (users.containsKey(email)) { + return false; + } + users.put(email, new UserInfo(password, grade)); + return true; + } + + /** + * 用户登录 + * + * @param email 用户邮箱 + * @param password 密码 + * @return 年级类型,登录失败返回null + */ + public Grade login(String email, String password) { + UserInfo user = users.get(email); + if (user == null || !user.getPassword().equals(password)) { + return null; + } + return user.getGrade(); + } + + /** + * 检查用户是否存在 + * + * @param email 用户邮箱 + * @return 用户是否存在 + */ + public boolean userExists(String email) { + return users.containsKey(email); + } + + /** + * 修改密码 + * + * @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; + } + + /** + * 内部类:用户信息 + */ + private static class UserInfo { + private String password; + private final Grade gradeType; + + /** + * 构造用户信息 + * + * @param password 密码 + * @param gradeType 年级类型 + */ + public UserInfo(String password, Grade gradeType) { + //this.email = email; + this.password = password; + this.gradeType = gradeType; + } + + /** + * 获取密码 + * + * @return 密码 + */ + public String getPassword() { + return password; + } + + /** + * 设置密码 + * + * @param password 新密码 + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * 获取年级类型 + * + * @return 年级类型 + */ + public Grade getGrade() { + return gradeType; + } + } +} \ No newline at end of file diff --git a/src/ui/ChangePasswordDialog.java b/src/ui/ChangePasswordDialog.java new file mode 100644 index 0000000..942c762 --- /dev/null +++ b/src/ui/ChangePasswordDialog.java @@ -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("新密码要求:
" + + "长度6-10位,必须包含大小写字母和数字"); + 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); + } +} \ No newline at end of file diff --git a/src/ui/GradeSelectionFrame.java b/src/ui/GradeSelectionFrame.java new file mode 100644 index 0000000..eef62b3 --- /dev/null +++ b/src/ui/GradeSelectionFrame.java @@ -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 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 "未知"; + } + } +} \ No newline at end of file diff --git a/src/ui/LoginFrame.java b/src/ui/LoginFrame.java new file mode 100644 index 0000000..0b5d572 --- /dev/null +++ b/src/ui/LoginFrame.java @@ -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(); + } +} \ No newline at end of file diff --git a/src/ui/MathLearningApp.java b/src/ui/MathLearningApp.java new file mode 100644 index 0000000..12351e6 --- /dev/null +++ b/src/ui/MathLearningApp.java @@ -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); + }); + } +} \ No newline at end of file diff --git a/src/ui/PasswordSetupDialog.java b/src/ui/PasswordSetupDialog.java new file mode 100644 index 0000000..520a6d0 --- /dev/null +++ b/src/ui/PasswordSetupDialog.java @@ -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 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("密码要求:
" + + "1. 长度6-10位
" + + "2. 必须包含大写字母
" + + "3. 必须包含小写字母
" + + "4. 必须包含数字"); + 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; + } +} \ No newline at end of file diff --git a/src/ui/QuizFrame.java b/src/ui/QuizFrame.java new file mode 100644 index 0000000..2ef121e --- /dev/null +++ b/src/ui/QuizFrame.java @@ -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 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("" + + question.getQuestionText() + ""); + + 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(); + } +} \ No newline at end of file diff --git a/src/ui/RegisterDialog.java b/src/ui/RegisterDialog.java new file mode 100644 index 0000000..1bab2a6 --- /dev/null +++ b/src/ui/RegisterDialog.java @@ -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); + } +} \ No newline at end of file diff --git a/src/ui/ResultFrame.java b/src/ui/ResultFrame.java new file mode 100644 index 0000000..be21af4 --- /dev/null +++ b/src/ui/ResultFrame.java @@ -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(); + } +} \ No newline at end of file