commit a196139e9ada7c3379b712683171ae9555d8af87 Author: 柳意 <1449892469@qq.com> Date: Thu Oct 9 17:34:53 2025 +0800 柳意 1.修改出题逻辑——QuestionGenerator diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f68d109 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..1c2fda5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..307554b --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml new file mode 100644 index 0000000..e535ed4 --- /dev/null +++ b/.idea/google-java-format.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/libraries/sun_mail_javax.xml b/.idea/libraries/sun_mail_javax.xml new file mode 100644 index 0000000..a4cd2ab --- /dev/null +++ b/.idea/libraries/sun_mail_javax.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..12ee456 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d5b0815 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..6d50cd4 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MathLearningSoftware.iml b/MathLearningSoftware.iml new file mode 100644 index 0000000..ee69103 --- /dev/null +++ b/MathLearningSoftware.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/data/users.txt b/data/users.txt new file mode 100644 index 0000000..a53df5e --- /dev/null +++ b/data/users.txt @@ -0,0 +1,4 @@ +2023@qq.com|971458|Hsh2006|true +202326010221@168.com|829554||false +14498@qq.com|862004||false +1449892469@qq.com|137032|Ly2004|true diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..0dc3f4b --- /dev/null +++ b/doc/README.md @@ -0,0 +1,40 @@ +# 数学学习软件 + +## 项目简介 + +这是一个基于Java Swing开发的数学学习软件,旨在为不同学段的学生提供个性化的数学练习体验。软件支持用户注册、登录、密码管理,以及根据小学、初中、高中不同难度生成相应的数学题目。 + +## 功能特性 + +### 用户管理 +- **用户注册**: 通过邮箱注册,系统发送注册码验证 +- **用户登录**: 安全的邮箱密码登录机制 +- **密码管理**: 支持密码修改,密码强度验证 + +### 学习功能 +- **难度选择**: 小学、初中、高中三个难度级别 +- **题目生成**: 根据难度自动生成相应数学题目 +- **答题系统**: 选择题形式,实时反馈 +- **成绩统计**: 答题结果分析和详细报告 + +### 技术特点 +- 基于Java Swing的图形界面 +- JSON数据持久化存储 +- 模块化设计,易于扩展 +- 代码结构清晰,注释完整 + +## 系统要求 + +- Java 8 或更高版本 +- 支持Java Swing的桌面环境 +- 至少 100MB 可用磁盘空间 + +## 安装和运行 + +### 方法一:使用预编译JAR文件 + +1. 下载项目的最新发布版本 +2. 确保系统已安装Java运行环境 +3. 双击JAR文件运行,或使用命令行: + ```bash + java -jar MathLearningSoftware.jar \ No newline at end of file diff --git a/src/com/mathlearning/Main.java b/src/com/mathlearning/Main.java new file mode 100644 index 0000000..a07f25c --- /dev/null +++ b/src/com/mathlearning/Main.java @@ -0,0 +1,13 @@ +package com.mathlearning; + +import com.mathlearning.view.LoginFrame; + +public class Main { + public static void main(String[] args) { + javax.swing.SwingUtilities.invokeLater( + () -> { + LoginFrame loginFrame = new LoginFrame(); + loginFrame.setVisible(true); + }); + } +} diff --git a/src/com/mathlearning/controller/AuthController.java b/src/com/mathlearning/controller/AuthController.java new file mode 100644 index 0000000..c92211f --- /dev/null +++ b/src/com/mathlearning/controller/AuthController.java @@ -0,0 +1,138 @@ +package com.mathlearning.controller; + +import com.mathlearning.model.User; +import com.mathlearning.service.AuthService; +import com.mathlearning.service.EmailService; +import com.mathlearning.service.UserService; + +public class AuthController { + private UserService userService; + private AuthService authService; + private EmailService emailService; + private User currentUser; + + public AuthController() { + this.userService = new UserService(); + this.authService = new AuthService(); + this.emailService = new EmailService(); + } + + public String register(String email) { + try { + String validationMessage = authService.getEmailValidationMessage(email); + if (!validationMessage.equals("邮箱格式正确")) { + return validationMessage; + } + User existingUser = userService.getUserByEmail(email); + if (existingUser != null && existingUser.isRegistered()) { + return "该邮箱已被注册"; + } + String registrationCode = authService.generateRegistrationCode(email); + boolean success; + if (existingUser == null) { + success = userService.registerUser(email, registrationCode); + } else { + existingUser.setRegistrationCode(registrationCode); + success = userService.updateUser(existingUser); + } + if (success) { + boolean emailSent = emailService.sendRegistrationCode(email, registrationCode); + if (emailSent) { + return "注册码已发送到您的邮箱,有效时间2分钟"; + } else { + return "邮件发送失败,请检查网络连接或稍后重试"; + } + } else { + return "注册失败,请重试"; + } + } catch (Exception e) { + System.out.println("注册过程中发生异常: " + e.getMessage()); + e.printStackTrace(); + return "注册过程中发生错误,请检查网络连接后重试"; + } + } + + public boolean completeRegistration(String email, String registrationCode, String password) { + try { + /* + if (!authService.validateRegistrationCode(email, registrationCode)) { + return false; + } + */ + User user = userService.getUserByEmail(email); + if (user != null && authService.validatePassword(password)) { + return userService.completeRegistration(email, password); + } + return false; + } catch (Exception e) { + System.out.println("完成注册过程中发生异常: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + public boolean login(String email, String password) { + try { + boolean success = userService.login(email, password); + if (success) { + currentUser = userService.getUserByEmail(email); + } + return success; + } catch (Exception e) { + System.out.println("登录过程中发生异常: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + public boolean changePassword(String oldPassword, String newPassword) { + try { + if (currentUser != null && authService.validatePassword(newPassword)) { + return userService.changePassword(currentUser.getEmail(), oldPassword, newPassword); + } + return false; + } catch (Exception e) { + System.out.println("修改密码过程中发生异常: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + // 获取验证码剩余有效时间(秒) + public int getRemainingTime(String email) { + try { + return authService.getRemainingTime(email); + } catch (Exception e) { + System.out.println("获取剩余时间过程中发生异常: " + e.getMessage()); + return 0; + } + } + + // 获取详细的邮箱验证信息 + public String getEmailValidationMessage(String email) { + try { + return authService.getEmailValidationMessage(email); + } catch (Exception e) { + System.out.println("获取邮箱验证信息过程中发生异常: " + e.getMessage()); + return "邮箱验证服务暂时不可用,请检查网络连接"; + } + } + + // 获取当前用户 + public User getCurrentUser() { + return currentUser; + } + + // 获取当前用户邮箱 + public String getCurrentUserEmail() { + return currentUser != null ? currentUser.getEmail() : null; + } + + public boolean isCodeRegistered(String email, String registrationCode) { + if (!authService.validateRegistrationCode(email, registrationCode)) { + return false; + } else { + return true; + } + } +} diff --git a/src/com/mathlearning/controller/NavigationController.java b/src/com/mathlearning/controller/NavigationController.java new file mode 100644 index 0000000..fb5df68 --- /dev/null +++ b/src/com/mathlearning/controller/NavigationController.java @@ -0,0 +1,30 @@ +package com.mathlearning.controller; + +public class NavigationController { + private static NavigationController instance; + + private NavigationController() {} + + public static NavigationController getInstance() { + if (instance == null) { + instance = new NavigationController(); + } + return instance; + } + + public void navigateToLogin() { + System.out.println("导航到登录界面"); + } + + public void navigateToLevelSelection() { + System.out.println("导航到难度选择界面"); + } + + public void navigateToQuiz() { + System.out.println("导航到答题界面"); + } + + public void navigateToScore() { + System.out.println("导航到分数界面"); + } +} diff --git a/src/com/mathlearning/controller/QuestionController.java b/src/com/mathlearning/controller/QuestionController.java new file mode 100644 index 0000000..e793a9f --- /dev/null +++ b/src/com/mathlearning/controller/QuestionController.java @@ -0,0 +1,77 @@ +package com.mathlearning.controller; + +import com.mathlearning.model.Question; +import com.mathlearning.model.QuestionGenerator; + +import java.util.List; + +public class QuestionController { + private QuestionGenerator questionGenerator; + private List currentQuestions; + private int currentQuestionIndex; + private int score; + private int[] userAnswers; + + public QuestionController() { + this.questionGenerator = new QuestionGenerator(); + } + + public void startNewQuiz(String level, int questionCount) { + this.currentQuestions = questionGenerator.generateQuestions(level, questionCount); + this.currentQuestionIndex = 0; + this.score = 0; + this.userAnswers = new int[questionCount]; + // Initialize with -1 (no answer) + for (int i = 0; i < userAnswers.length; i++) { + userAnswers[i] = -1; + } + } + + /* + public Question getCurrentQuestion() { + if (currentQuestions == null || currentQuestionIndex >= currentQuestions.size()) { + return null; + } + return currentQuestions.get(currentQuestionIndex); + } + + public void submitAnswer(int answerIndex) { + if (currentQuestionIndex < userAnswers.length) { + userAnswers[currentQuestionIndex] = answerIndex; + } + + if (answerIndex != -1 && currentQuestions.get(currentQuestionIndex).isCorrect(answerIndex)) { + score++; + } + + currentQuestionIndex++; + } + + public boolean hasNextQuestion() { + return currentQuestions != null && currentQuestionIndex < currentQuestions.size(); + } + + public int getCurrentQuestionNumber() { + return currentQuestionIndex + 1; + } + */ + public int getTotalQuestions() { + return currentQuestions != null ? currentQuestions.size() : 0; + } + + public int getScore() { + return score; + } + + public double getPercentage() { + return getTotalQuestions() > 0 ? (double) score / getTotalQuestions() * 100 : 0; + } + + public int[] getUserAnswers() { + return userAnswers; + } + + public List getCurrentQuestions() { + return currentQuestions; + } +} diff --git a/src/com/mathlearning/model/Question.java b/src/com/mathlearning/model/Question.java new file mode 100644 index 0000000..3094ba2 --- /dev/null +++ b/src/com/mathlearning/model/Question.java @@ -0,0 +1,36 @@ +package com.mathlearning.model; + +public class Question { + private String questionText; + private String[] options; + private int correctAnswerIndex; + private String level; + + public Question(String questionText, String[] options, int correctAnswerIndex, String level) { + this.questionText = questionText; + this.options = options; + this.correctAnswerIndex = correctAnswerIndex; + this.level = level; + } + + // Getters + public String getQuestionText() { + return questionText; + } + + public String[] getOptions() { + return options; + } + + public int getCorrectAnswerIndex() { + return correctAnswerIndex; + } + + public String getLevel() { + return level; + } + + public boolean isCorrect(int selectedIndex) { + return selectedIndex == correctAnswerIndex; + } +} diff --git a/src/com/mathlearning/model/QuestionGenerator.java b/src/com/mathlearning/model/QuestionGenerator.java new file mode 100644 index 0000000..afc3dc0 --- /dev/null +++ b/src/com/mathlearning/model/QuestionGenerator.java @@ -0,0 +1,414 @@ +package com.mathlearning.model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +public class QuestionGenerator { + private Random random = new Random(); + + public List generateQuestions(String level, int count) { + List questions = new ArrayList<>(); + Set questionTexts = new HashSet<>(); // 防止重复题目 + + for (int i = 0; i < count; i++) { + Question question; + int attempt = 0; + do { + question = generateQuestion(level, i); + attempt++; + } while (questionTexts.contains(question.getQuestionText()) && attempt < 10); + + if (question != null) { + questionTexts.add(question.getQuestionText()); + questions.add(question); + } + } + + return questions; + } + + private Question generateQuestion(String level, int index) { + switch (level) { + case "小学": + return generatePrimaryQuestion(index); + case "初中": + return generateMiddleSchoolQuestion(index); + case "高中": + return generateHighSchoolQuestion(index); + default: + return generatePrimaryQuestion(index); + } + } + + private Question generatePrimaryQuestion(int index) { + // 小学:操作数2-5个,确保结果不为负数 + int operandCount = random.nextInt(4) + 2; // 2-5个操作数 + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + // 生成操作数 (1-100) + for (int i = 0; i < operandCount; i++) { + operands.add(random.nextInt(100) + 1); + } + + // 生成运算符 (+, -, *, /) + for (int i = 0; i < operandCount - 1; i++) { + operators.add(getRandomOperation("+-*/")); + } + + // 构建表达式并计算结果,确保不为负数 + String questionText; + int result; + int attempts = 0; + + do { + // 随机决定是否使用括号 + boolean useParentheses = random.nextBoolean() && operandCount >= 3; + + if (useParentheses) { + // 使用括号的表达式 + int parenPos = random.nextInt(operandCount - 1); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < operandCount; i++) { + if (i == parenPos) { + sb.append("("); + } + sb.append(operands.get(i)); + if (i == parenPos + 1) { + sb.append(")"); + } + if (i < operandCount - 1) { + sb.append(" ").append(operators.get(i)).append(" "); + } + } + + questionText = sb.toString(); + result = evaluateExpressionWithParentheses(operands, operators, parenPos); + } else { + // 不使用括号的表达式 + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < operandCount; i++) { + sb.append(operands.get(i)); + if (i < operandCount - 1) { + sb.append(" ").append(operators.get(i)).append(" "); + } + } + + questionText = sb.toString(); + result = evaluateSequential(operands, operators); + } + + attempts++; + // 如果结果为负数,重新生成操作数和运算符 + if (result < 0) { + operands.clear(); + operators.clear(); + for (int i = 0; i < operandCount; i++) { + operands.add(random.nextInt(100) + 1); + } + for (int i = 0; i < operandCount - 1; i++) { + operators.add(getRandomOperation("+-*/")); + } + } + } while (result < 0 && attempts < 10); + + // 如果还是负数,强制使用加法 + if (result < 0) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < operandCount; i++) { + sb.append(operands.get(i)); + if (i < operandCount - 1) { + sb.append(" + "); + } + } + questionText = sb.toString(); + result = operands.stream().mapToInt(Integer::intValue).sum(); + } + + return generateOptions(questionText + " = ?", result, "小学"); + } + + private Question generateMiddleSchoolQuestion(int index) { + // 初中:操作数1-5个,至少包含平方或开根号 + int operandCount = random.nextInt(5) + 1; // 1-5个操作数 + boolean hasSquareOrSqrt = false; + String questionText; + int result; + int attempts = 0; + + do { + int type = random.nextInt(4); + switch (type) { + case 0: + // a² ± b × c + int a = random.nextInt(10) + 1; // 1-10的平方 + int b = random.nextInt(100) + 1; + int c = random.nextInt(100) + 1; + char op = random.nextBoolean() ? '+' : '-'; + questionText = a + "² " + op + " " + b + " × " + c; + result = (op == '+') ? (a * a + b * c) : (a * a - b * c); + hasSquareOrSqrt = true; + break; + + case 1: + // √a ± b + int sqrtBase = findPerfectSquare(100); // 100以内的完全平方数 + int sqrtVal = (int) Math.sqrt(sqrtBase); + int d = random.nextInt(100) + 1; + char op2 = random.nextBoolean() ? '+' : '-'; + questionText = "√" + sqrtBase + " " + op2 + " " + d; + result = (op2 == '+') ? (sqrtVal + d) : (sqrtVal - d); + hasSquareOrSqrt = true; + break; + + case 2: + // a × b² ± c + int e = random.nextInt(100) + 1; + int f = random.nextInt(10) + 1; + int g = random.nextInt(100) + 1; + char op3 = random.nextBoolean() ? '+' : '-'; + questionText = e + " × " + f + "² " + op3 + " " + g; + result = (op3 == '+') ? (e * f * f + g) : (e * f * f - g); + hasSquareOrSqrt = true; + break; + + default: + // (a + b)² ÷ c + int h = random.nextInt(50) + 1; + int i = random.nextInt(50) + 1; + int j = random.nextInt(20) + 1; + questionText = "(" + h + " + " + i + ")² ÷ " + j; + result = (h + i) * (h + i) / j; + hasSquareOrSqrt = true; + break; + } + attempts++; + } while (!hasSquareOrSqrt && attempts < 10); + + return generateOptions(questionText + " = ?", result, "初中"); + } + + private Question generateHighSchoolQuestion(int index) { + // 高中:操作数1-5个,至少包含sin,cos,tan + int operandCount = random.nextInt(5) + 1; // 1-5个操作数 + boolean hasTrigFunction = false; + String questionText; + double result; + int attempts = 0; + + // 常见角度值 + double[] angles = {0, 30, 45, 60, 90, 120, 135, 150, 180}; + String[] angleStrs = {"0°", "30°", "45°", "60°", "90°", "120°", "135°", "150°", "180°"}; + double[] sinValues = {0, 0.5, Math.sqrt(2)/2, Math.sqrt(3)/2, 1, Math.sqrt(3)/2, Math.sqrt(2)/2, 0.5, 0}; + double[] cosValues = {1, Math.sqrt(3)/2, Math.sqrt(2)/2, 0.5, 0, -0.5, -Math.sqrt(2)/2, -Math.sqrt(3)/2, -1}; + double[] tanValues = {0, Math.sqrt(3)/3, 1, Math.sqrt(3), Double.POSITIVE_INFINITY, -Math.sqrt(3), -1, -Math.sqrt(3)/3, 0}; + + do { + int type = random.nextInt(4); + switch (type) { + case 0: + // sin(a) ± b + int idx1 = random.nextInt(angles.length); + int b = random.nextInt(100) + 1; + char op = random.nextBoolean() ? '+' : '-'; + questionText = "sin(" + angleStrs[idx1] + ") " + op + " " + b; + result = (op == '+') ? (sinValues[idx1] + b) : (sinValues[idx1] - b); + hasTrigFunction = true; + break; + + case 1: + // a × cos(b) + int a = random.nextInt(100) + 1; + int idx2 = random.nextInt(angles.length); + questionText = a + " × cos(" + angleStrs[idx2] + ")"; + result = a * cosValues[idx2]; + hasTrigFunction = true; + break; + + case 2: + // tan(a) ÷ b (避免tan(90°)) + int idx3 = random.nextInt(angles.length); + while (angles[idx3] == 90) { + idx3 = random.nextInt(angles.length); + } + int c = random.nextInt(20) + 1; + questionText = "tan(" + angleStrs[idx3] + ") ÷ " + c; + result = tanValues[idx3] / c; + hasTrigFunction = true; + break; + + default: + // sin(a) × cos(b) ± c + int idx4 = random.nextInt(angles.length); + int idx5 = random.nextInt(angles.length); + int d = random.nextInt(100) + 1; + char op2 = random.nextBoolean() ? '+' : '-'; + questionText = "sin(" + angleStrs[idx4] + ") × cos(" + angleStrs[idx5] + ") " + op2 + " " + d; + result = (op2 == '+') ? (sinValues[idx4] * cosValues[idx5] + d) : (sinValues[idx4] * cosValues[idx5] - d); + hasTrigFunction = true; + break; + } + attempts++; + } while (!hasTrigFunction && attempts < 10); + + return generateOptions(questionText + " = ?", result, "高中"); + } + + // 辅助方法 + private char getRandomOperation(String operations) { + return operations.charAt(random.nextInt(operations.length())); + } + + private int evaluateSequential(List operands, List operators) { + int result = operands.get(0); + for (int i = 0; i < operators.size(); i++) { + result = calculate(result, operands.get(i + 1), operators.get(i)); + } + return result; + } + + private int evaluateExpressionWithParentheses(List operands, List operators, int parenPos) { + // 先计算括号内的值 + int parenResult = calculate(operands.get(parenPos), operands.get(parenPos + 1), operators.get(parenPos)); + + // 构建新的操作数和运算符列表 + List newOperands = new ArrayList<>(); + List newOperators = new ArrayList<>(); + + for (int i = 0; i < operands.size(); i++) { + if (i == parenPos) { + newOperands.add(parenResult); + } else if (i != parenPos + 1) { + newOperands.add(operands.get(i)); + } + } + + for (int i = 0; i < operators.size(); i++) { + if (i != parenPos) { + newOperators.add(operators.get(i)); + } + } + + // 顺序计算剩余部分 + return evaluateSequential(newOperands, newOperators); + } + + private int calculate(int a, int b, char op) { + switch (op) { + case '+': return a + b; + case '-': + // 确保减法结果不为负数 + return Math.max(a - b, 0); + case '*': return a * b; + case '/': + // 确保除法能整除且除数不为0 + if (b == 0) return a; + return a / b; + default: return a + b; + } + } + + private int findPerfectSquare(int max) { + // 找到小于等于max的完全平方数 + List perfectSquares = new ArrayList<>(); + for (int i = 1; i * i <= max; i++) { + perfectSquares.add(i * i); + } + return perfectSquares.get(random.nextInt(perfectSquares.size())); + } + + private Question generateOptions(String questionText, int answer, String level) { + String[] options = new String[4]; + Set usedValues = new HashSet<>(); + + options[0] = String.valueOf(answer); + usedValues.add(answer); + + for (int i = 1; i < 4; i++) { + int wrongAnswer; + int attempts = 0; + do { + int range = Math.max(3, Math.abs(answer) / 5 + 1); + int offset = random.nextInt(range * 2 + 1) - range; + wrongAnswer = answer + offset; + // 确保错误答案不为负数 + wrongAnswer = Math.max(wrongAnswer, 0); + attempts++; + } while (usedValues.contains(wrongAnswer) && attempts < 20); + + if (usedValues.contains(wrongAnswer)) { + wrongAnswer = answer + i * 10 + 5; + wrongAnswer = Math.max(wrongAnswer, 0); + } + + options[i] = String.valueOf(wrongAnswer); + usedValues.add(wrongAnswer); + } + + shuffleArray(options); + int correctIndex = findCorrectIndex(options, String.valueOf(answer)); + + return new Question(questionText, options, correctIndex, level); + } + + private Question generateOptions(String questionText, double answer, String level) { + String[] options = new String[4]; + Set usedValues = new HashSet<>(); + + String correctAnswer = formatDouble(answer); + options[0] = correctAnswer; + usedValues.add(correctAnswer); + + for (int i = 1; i < 4; i++) { + String wrongAnswer; + int attempts = 0; + do { + double offset = (random.nextDouble() - 0.5) * 2.0; + double wrongValue = answer + offset; + wrongAnswer = formatDouble(wrongValue); + attempts++; + } while (usedValues.contains(wrongAnswer) && attempts < 20); + + options[i] = wrongAnswer; + usedValues.add(wrongAnswer); + } + + shuffleArray(options); + int correctIndex = findCorrectIndex(options, correctAnswer); + + return new Question(questionText, options, correctIndex, level); + } + + private String formatDouble(double value) { + if (Double.isInfinite(value)) { + return "∞"; + } + if (Double.isNaN(value)) { + return "无解"; + } + // 保留3位小数,去除多余的0 + return String.format("%.3f", value).replaceAll("0*$", "").replaceAll("\\.$", ""); + } + + private int findCorrectIndex(String[] options, String correctAnswer) { + for (int i = 0; i < options.length; i++) { + if (options[i].equals(correctAnswer)) { + return i; + } + } + return 0; + } + + private void shuffleArray(String[] array) { + for (int i = array.length - 1; i > 0; i--) { + int index = random.nextInt(i + 1); + String temp = array[index]; + array[index] = array[i]; + array[i] = temp; + } + } +} \ No newline at end of file diff --git a/src/com/mathlearning/model/User.java b/src/com/mathlearning/model/User.java new file mode 100644 index 0000000..04e178b --- /dev/null +++ b/src/com/mathlearning/model/User.java @@ -0,0 +1,48 @@ +package com.mathlearning.model; + +public class User { + private String email; + private String password; + private String registrationCode; + private boolean isRegistered; + + public User(String email, String registrationCode) { + this.email = email; + this.registrationCode = registrationCode; + this.isRegistered = false; + this.password = null; + } + + // Getters and setters + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRegistrationCode() { + return registrationCode; + } + + public void setRegistrationCode(String registrationCode) { + this.registrationCode = registrationCode; + } + + public boolean isRegistered() { + return isRegistered; + } + + public void setRegistered(boolean registered) { + isRegistered = registered; + } +} diff --git a/src/com/mathlearning/service/AuthService.java b/src/com/mathlearning/service/AuthService.java new file mode 100644 index 0000000..e8e8f57 --- /dev/null +++ b/src/com/mathlearning/service/AuthService.java @@ -0,0 +1,132 @@ +package com.mathlearning.service; + +import java.util.Random; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class AuthService { + private Random random = new Random(); + private Map registrationCodes = new ConcurrentHashMap<>(); + + // 邮箱格式正则表达式 + private static final String EMAIL_REGEX = + "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; + private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); + + // 验证码信息类 + private static class RegistrationCodeInfo { + String code; + long createTime; + + RegistrationCodeInfo(String code) { + this.code = code; + this.createTime = System.currentTimeMillis(); + } + + boolean isValid() { + // 验证码有效期为2分钟(120秒) + return (System.currentTimeMillis() - createTime) < 120 * 1000; + } + } + + public String generateRegistrationCode(String email) { + // 生成6位数字验证码 + String code = String.format("%06d", random.nextInt(1000000)); + + // 存储验证码信息 + registrationCodes.put(email, new RegistrationCodeInfo(code)); + + // 清理过期验证码 + cleanupExpiredCodes(); + + return code; + } + + public boolean validateRegistrationCode(String email, String code) { + cleanupExpiredCodes(); + + RegistrationCodeInfo info = registrationCodes.get(email); + if (info != null && info.isValid() && info.code.equals(code)) { + // 验证成功后移除验证码 + registrationCodes.remove(email); + return true; + } + return false; + } + + private void cleanupExpiredCodes() { + registrationCodes.entrySet().removeIf(entry -> !entry.getValue().isValid()); + } + + public boolean validatePassword(String password) { + if (password == null || password.length() < 6 || password.length() > 10) { + return false; + } + + boolean hasUpper = false; + boolean hasLower = false; + boolean hasDigit = false; + + for (char c : password.toCharArray()) { + if (Character.isUpperCase(c)) hasUpper = true; + if (Character.isLowerCase(c)) hasLower = true; + if (Character.isDigit(c)) hasDigit = true; + } + + return hasUpper && hasLower && hasDigit; + } + + public boolean validateEmail(String email) { + if (email == null || email.trim().isEmpty()) { + return false; + } + // 额外的验证:检查常见的邮箱提供商 + String lowerEmail = email.toLowerCase(); + // 支持常见的邮箱提供商 + boolean isCommonProvider = + lowerEmail.contains("@qq.com") + || lowerEmail.contains("@163.com") + || lowerEmail.contains("@126.com") + || lowerEmail.contains("@gmail.com") + || lowerEmail.contains("@outlook.com") + || lowerEmail.contains("@hotmail.com") + || lowerEmail.contains("@sina.com") + || lowerEmail.contains("@sohu.com") + || lowerEmail.contains("@yahoo.com") + || lowerEmail.contains("@foxmail.com"); + if (!isCommonProvider) { + System.out.println("警告: 使用不常见的邮箱提供商: " + email); + return false; + } + return true; + } + + // 获取详细的邮箱验证信息 + public String getEmailValidationMessage(String email) { + if (email == null || email.trim().isEmpty()) { + return "邮箱地址不能为空"; + } else if (email.length() > 50) { + return "邮箱地址过长"; + } else if (!EMAIL_PATTERN.matcher(email).matches()) { + return "邮箱格式不正确,请使用正确的邮箱格式(如:username@example.com)"; + } else if (email.contains("..") || email.startsWith(".") || email.endsWith(".")) { + return "邮箱格式不正确,不能以点开头或结尾,也不能连续使用点"; + } + if (validateEmail(email)) { + return "邮箱格式正确"; + } else { + return "邮箱域名不正确"; + } + } + + // 获取验证码剩余有效时间(秒) + public int getRemainingTime(String email) { + RegistrationCodeInfo info = registrationCodes.get(email); + if (info != null && info.isValid()) { + long elapsed = System.currentTimeMillis() - info.createTime; + return Math.max(0, (int) ((120 * 1000 - elapsed) / 1000)); + } + return 0; + } +} diff --git a/src/com/mathlearning/service/EmailService.java b/src/com/mathlearning/service/EmailService.java new file mode 100644 index 0000000..b59fa50 --- /dev/null +++ b/src/com/mathlearning/service/EmailService.java @@ -0,0 +1,102 @@ +package com.mathlearning.service; + +import javax.mail.*; +import javax.mail.internet.*; +import java.util.Properties; +import java.util.Date; +import java.net.InetSocketAddress; +import java.net.Socket; + +public class EmailService { + // QQ邮箱配置 - 使用465端口 + private final String host = "smtp.qq.com"; + private final String username = "3602474328@qq.com"; + private final String password = "ahzzoakqvvuddbbi"; + + // 检查网络连接的方法 + public static boolean isNetworkAvailable() { + try (Socket socket = new Socket()) { + // 尝试连接一个可靠的服务器来检查网络 + socket.connect(new InetSocketAddress("www.qq.com", 80), 5000); + return true; + } catch (Exception e) { + System.out.println("网络连接检查失败: " + e.getMessage()); + return false; + } + } + + private Properties setQqServer() { + try { + // 配置QQ邮箱服务器 - 使用465端口和SSL + Properties props = new Properties(); + props.put("mail.smtp.host", host); + props.put("mail.smtp.port", "465"); // 使用465端口 + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.ssl.enable", "true"); // 启用SSL + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.put("mail.smtp.socketFactory.port", "465"); + props.put("mail.smtp.socketFactory.fallback", "false"); + props.put("mail.smtp.connectiontimeout", "10000"); // 连接超时10秒 + props.put("mail.smtp.timeout", "10000"); // 读取超时10秒 + return props; + } catch (Exception e) { + System.out.println("邮件发送过程中发生未知异常: " + e.getMessage()); + e.printStackTrace(); + return null; + } + } + + private Message setMessage(Session session, String email, String registrationCode) { + try { + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(username)); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email)); + message.setSubject("数学学习软件 - 注册验证码"); + message.setSentDate(new Date()); + + String content = + "您的注册验证码是: " + + registrationCode + + "\n\n" + + "验证码有效时间为2分钟,请尽快完成注册。\n\n" + + "如果不是您本人操作,请忽略此邮件。"; + + message.setText(content); + return message; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public boolean sendRegistrationCode(String email, String registrationCode) { + if (!isNetworkAvailable()) { + System.out.println("=== 网络连接异常 ==="); + return false; // 网络异常时返回false + } + try { + // 配置QQ邮箱服务器 - 使用465端口和SSL + Properties props = setQqServer(); + // 创建会话 + Session session = + Session.getInstance( + props, + new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + // 创建邮件 + Message message = setMessage(session, email, registrationCode); + // 发送邮件 + Transport.send(message); + System.out.println("邮件发送成功到: " + email); + return true; + } catch (MessagingException e) { + System.out.println("邮件发送失败 - 网络或邮箱服务异常: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } +} diff --git a/src/com/mathlearning/service/UserService.java b/src/com/mathlearning/service/UserService.java new file mode 100644 index 0000000..fe00e58 --- /dev/null +++ b/src/com/mathlearning/service/UserService.java @@ -0,0 +1,133 @@ +package com.mathlearning.service; + +import com.mathlearning.model.User; +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class UserService { + private static final String USER_DATA_FILE = "data/users.txt"; + private List users; + + public UserService() { + this.users = loadUsers(); + } + + public boolean registerUser(String email, String registrationCode) { + // Check if email already exists + for (User user : users) { + if (user.getEmail().equals(email)) { + return false; + } + } + + User newUser = new User(email, registrationCode); + users.add(newUser); + saveUsers(); + return true; + } + + public boolean completeRegistration(String email, String password) { + for (User user : users) { + if (user.getEmail().equals(email) && !user.isRegistered()) { + user.setPassword(password); + user.setRegistered(true); + saveUsers(); + return true; + } + } + return false; + } + + public boolean login(String email, String password) { + for (User user : users) { + if (user.getEmail().equals(email) + && user.isRegistered() + && user.getPassword().equals(password)) { + return true; + } + } + return false; + } + + public boolean changePassword(String email, String oldPassword, String newPassword) { + for (User user : users) { + if (user.getEmail().equals(email) && user.getPassword().equals(oldPassword)) { + user.setPassword(newPassword); + saveUsers(); + return true; + } + } + return false; + } + + public User getUserByEmail(String email) { + for (User user : users) { + if (user.getEmail().equals(email)) { + return user; + } + } + return null; + } + + public boolean updateUser(User updatedUser) { + for (int i = 0; i < users.size(); i++) { + if (users.get(i).getEmail().equals(updatedUser.getEmail())) { + users.set(i, updatedUser); + saveUsers(); + return true; + } + } + return false; + } + + private List loadUsers() { + List userList = new ArrayList<>(); + try { + File file = new File(USER_DATA_FILE); + if (!file.exists()) { + return userList; + } + + BufferedReader reader = new BufferedReader(new FileReader(file)); + String line; + while ((line = reader.readLine()) != null) { + String[] parts = line.split("\\|"); + if (parts.length >= 4) { + User user = new User(parts[0], parts[1]); + user.setPassword(parts[2]); + user.setRegistered(Boolean.parseBoolean(parts[3])); + userList.add(user); + } + } + reader.close(); + } catch (IOException e) { + System.out.println("加载用户数据失败: " + e.getMessage()); + } + return userList; + } + + private void saveUsers() { + try { + File file = new File(USER_DATA_FILE); + file.getParentFile().mkdirs(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(file)); + for (User user : users) { + String line = + user.getEmail() + + "|" + + user.getRegistrationCode() + + "|" + + (user.getPassword() != null ? user.getPassword() : "") + + "|" + + user.isRegistered(); + writer.write(line); + writer.newLine(); + } + writer.close(); + } catch (IOException e) { + System.out.println("保存用户数据失败: " + e.getMessage()); + } + } +} diff --git a/src/com/mathlearning/view/LevelSelectionFrame.java b/src/com/mathlearning/view/LevelSelectionFrame.java new file mode 100644 index 0000000..f72cc0a --- /dev/null +++ b/src/com/mathlearning/view/LevelSelectionFrame.java @@ -0,0 +1,137 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class LevelSelectionFrame extends JFrame { + private AuthController authController; + + public LevelSelectionFrame(AuthController authController) { + this.authController = authController; + initializeUI(); + } + + private void initializeUI() { + setTitle("数学学习软件 - 选择难度"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(500, 450); + 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()); + + // 左侧:返回登录按钮 + JPanel leftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + JButton backButton = new JButton("← 返回登录"); + backButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + backButton.setBackground(new Color(180, 180, 180)); + backButton.setForeground(Color.BLACK); + backButton.setFocusPainted(false); + backButton.setBorder(BorderFactory.createEmptyBorder(5, 15, 5, 15)); + backButton.addActionListener(e -> backToLogin()); + leftPanel.add(backButton); + + // 右侧:修改密码按钮 + JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton changePasswordButton = new JButton("修改密码"); + changePasswordButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + changePasswordButton.setBackground(new Color(255, 165, 0)); + changePasswordButton.setForeground(Color.WHITE); + changePasswordButton.setFocusPainted(false); + changePasswordButton.setBorder(BorderFactory.createEmptyBorder(5, 15, 5, 15)); + changePasswordButton.addActionListener(e -> openChangePasswordDialog()); + rightPanel.add(changePasswordButton); + + topPanel.add(leftPanel, BorderLayout.WEST); + topPanel.add(rightPanel, BorderLayout.EAST); + mainPanel.add(topPanel, BorderLayout.NORTH); + + // 主内容区域 + JLabel titleLabel = new JLabel("选择学习难度", JLabel.CENTER); + titleLabel.setFont(new Font("Microsoft YaHei", Font.BOLD, 24)); + titleLabel.setBorder(BorderFactory.createEmptyBorder(20, 0, 30, 0)); + mainPanel.add(titleLabel, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new GridLayout(3, 1, 20, 20)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 50, 10, 50)); + + JButton primaryButton = createLevelButton("小学", new Color(76, 175, 120)); + JButton middleButton = createLevelButton("初中", new Color(255, 152, 100)); + JButton highButton = createLevelButton("高中", new Color(244, 67, 104)); + + buttonPanel.add(primaryButton); + buttonPanel.add(middleButton); + buttonPanel.add(highButton); + + mainPanel.add(buttonPanel, BorderLayout.CENTER); + + // 底部显示当前用户信息 + if (authController.getCurrentUser() != null) { + JLabel userInfoLabel = + new JLabel("当前用户: " + authController.getCurrentUser().getEmail(), JLabel.CENTER); + userInfoLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + userInfoLabel.setForeground(Color.GRAY); + userInfoLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); + mainPanel.add(userInfoLabel, BorderLayout.SOUTH); + } + + primaryButton.addActionListener(e -> openQuestionCountFrame("小学")); + middleButton.addActionListener(e -> openQuestionCountFrame("初中")); + highButton.addActionListener(e -> openQuestionCountFrame("高中")); + + add(mainPanel); + } + + private JButton createLevelButton(String text, Color color) { + JButton button = new JButton(text); + button.setFont(new Font("Microsoft YaHei", Font.BOLD, 20)); + button.setBackground(color); + button.setForeground(Color.WHITE); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(15, 0, 15, 0)); + button.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + // 添加鼠标悬停效果 + button.addMouseListener( + new java.awt.event.MouseAdapter() { + public void mouseEntered(java.awt.event.MouseEvent evt) { + button.setBackground(color.darker()); + } + + public void mouseExited(java.awt.event.MouseEvent evt) { + button.setBackground(color); + } + }); + + return button; + } + + private void backToLogin() { + int result = + JOptionPane.showConfirmDialog(this, "确定要退出登录吗?", "确认退出", JOptionPane.YES_NO_OPTION); + + if (result == JOptionPane.YES_OPTION) { + LoginFrame loginFrame = new LoginFrame(); + loginFrame.setVisible(true); + this.dispose(); + } + } + + private void openQuestionCountFrame(String level) { + QuestionCountFrame countFrame = new QuestionCountFrame(this, level); + countFrame.setVisible(true); + this.dispose(); + } + + // 修改密码对话框 - 不需要输入邮箱 + private void openChangePasswordDialog() { + PasswordChangeFrame passwordChangeFrame = new PasswordChangeFrame(authController); + } +} diff --git a/src/com/mathlearning/view/LoginFrame.java b/src/com/mathlearning/view/LoginFrame.java new file mode 100644 index 0000000..461e8b8 --- /dev/null +++ b/src/com/mathlearning/view/LoginFrame.java @@ -0,0 +1,157 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class LoginFrame extends JFrame { + private AuthController authController; + private JTextField emailField; + private JPasswordField passwordField; + + public LoginFrame() { + this.authController = new AuthController(); + initializeUI(); + } + + private void initializeUI() { + setTitle("数学学习软件 - 登录"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(500, 400); + setLocationRelativeTo(null); + setResizable(false); + + // 使用更灵活的布局 + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(30, 40, 30, 40)); + + // 标题 + JLabel titleLabel = new JLabel("数学学习软件", JLabel.CENTER); + titleLabel.setFont(new Font("Microsoft YaHei", Font.BOLD, 28)); + titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 30, 0)); + mainPanel.add(titleLabel); + + // 添加一些间距 + mainPanel.add(Box.createRigidArea(new Dimension(0, 20))); + + // 邮箱输入区域 + JPanel emailPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + emailPanel.setMaximumSize(new Dimension(400, 60)); + + JLabel emailLabel = new JLabel("邮箱:"); + emailLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 16)); + emailLabel.setPreferredSize(new Dimension(80, 30)); + + emailField = new JTextField(); + emailField.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + emailField.setPreferredSize(new Dimension(250, 35)); + + emailPanel.add(emailLabel); + emailPanel.add(emailField); + mainPanel.add(emailPanel); + + // 添加间距 + mainPanel.add(Box.createRigidArea(new Dimension(0, 15))); + + // 密码输入区域 + JPanel passwordPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + passwordPanel.setMaximumSize(new Dimension(400, 60)); + + JLabel passwordLabel = new JLabel("密码:"); + passwordLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 16)); + passwordLabel.setPreferredSize(new Dimension(80, 30)); + + passwordField = new JPasswordField(); + passwordField.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + passwordField.setPreferredSize(new Dimension(250, 35)); + + passwordPanel.add(passwordLabel); + passwordPanel.add(passwordField); + mainPanel.add(passwordPanel); + + // 添加间距 + mainPanel.add(Box.createRigidArea(new Dimension(0, 30))); + + // 按钮区域 - 只有登录和注册按钮 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 0)); + buttonPanel.setMaximumSize(new Dimension(400, 50)); + + JButton loginButton = createStyledButton("登录", new Color(70, 130, 180)); + JButton registerButton = createStyledButton("注册", new Color(60, 179, 113)); + + buttonPanel.add(loginButton); + buttonPanel.add(registerButton); + + mainPanel.add(buttonPanel); + + // 添加底部间距 + mainPanel.add(Box.createVerticalGlue()); + + // 添加操作监听器 + loginButton.addActionListener(new LoginAction()); + registerButton.addActionListener(e -> openRegisterFrame()); + + add(mainPanel); + } + + private JButton createStyledButton(String text, Color color) { + JButton button = new JButton(text); + button.setFont(new Font("Microsoft YaHei", Font.BOLD, 14)); + button.setBackground(color); + button.setForeground(Color.WHITE); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20)); + button.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + // 添加鼠标悬停效果 + button.addMouseListener( + new java.awt.event.MouseAdapter() { + public void mouseEntered(java.awt.event.MouseEvent evt) { + button.setBackground(color.darker()); + } + + public void mouseExited(java.awt.event.MouseEvent evt) { + button.setBackground(color); + } + }); + + return button; + } + + private class LoginAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String email = emailField.getText().trim(); + String password = new String(passwordField.getPassword()); + + if (email.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(LoginFrame.this, "请输入邮箱和密码", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + if (authController.login(email, password)) { + JOptionPane.showMessageDialog( + LoginFrame.this, "登录成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + openLevelSelectionFrame(); + } else { + JOptionPane.showMessageDialog(LoginFrame.this, "邮箱或密码错误", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + + private void openRegisterFrame() { + RegisterFrame registerFrame = new RegisterFrame(); + registerFrame.setVisible(true); + this.dispose(); + } + + private void openLevelSelectionFrame() { + LevelSelectionFrame levelFrame = new LevelSelectionFrame(authController); + levelFrame.setVisible(true); + this.dispose(); + } +} diff --git a/src/com/mathlearning/view/PasswordChangeFrame.java b/src/com/mathlearning/view/PasswordChangeFrame.java new file mode 100644 index 0000000..d3fdd01 --- /dev/null +++ b/src/com/mathlearning/view/PasswordChangeFrame.java @@ -0,0 +1,92 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; + +import javax.swing.*; +import java.awt.*; + +public class PasswordChangeFrame extends JFrame { + private AuthController authController; + + public PasswordChangeFrame(AuthController authController) { + this.authController = authController; + initializeUI(); + } + + private JPanel createLabeledField(String labelText, JComponent field) { + JPanel panel = new JPanel(new BorderLayout(10, 0)); + JLabel label = new JLabel(labelText); + label.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + label.setPreferredSize(new Dimension(80, 25)); + panel.add(label, BorderLayout.WEST); + panel.add(field, BorderLayout.CENTER); + return panel; + } + + private void initializeUI() { + JPanel panel = new JPanel(new GridLayout(4, 1, 10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + + // 显示当前用户信息 + JLabel userLabel = new JLabel("当前用户: " + authController.getCurrentUserEmail()); + userLabel.setFont(new Font("Microsoft YaHei", Font.BOLD, 12)); + userLabel.setForeground(new Color(0, 100, 200)); + + JPasswordField oldPasswordField = new JPasswordField(); + JPasswordField newPasswordField = new JPasswordField(); + JPasswordField confirmPasswordField = new JPasswordField(); + + // 设置字体和提示文本 + Font font = new Font("Microsoft YaHei", Font.PLAIN, 14); + oldPasswordField.setFont(font); + newPasswordField.setFont(font); + confirmPasswordField.setFont(font); + + // 创建带标签的面板 + JPanel oldPassPanel = createLabeledField("原密码:", oldPasswordField); + JPanel newPassPanel = createLabeledField("新密码:", newPasswordField); + JPanel confirmPassPanel = createLabeledField("确认新密码:", confirmPasswordField); + + panel.add(userLabel); + panel.add(oldPassPanel); + panel.add(newPassPanel); + panel.add(confirmPassPanel); + + int option = + JOptionPane.showConfirmDialog( + this, panel, "修改密码", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + + if (option == JOptionPane.OK_OPTION) { + String oldPassword = new String(oldPasswordField.getPassword()); + String newPassword = new String(newPasswordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + // 验证输入是否为空 + if (oldPassword.isEmpty() || newPassword.isEmpty() || confirmPassword.isEmpty()) { + JOptionPane.showMessageDialog(this, "请填写所有密码字段", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + // 检查新密码和确认密码是否一致 + if (!newPassword.equals(confirmPassword)) { + JOptionPane.showMessageDialog(this, "新密码和确认密码不一致", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + // 使用当前登录用户的邮箱修改密码 + if (authController.getCurrentUser() != null) { + if (authController.changePassword(oldPassword, newPassword)) { + JOptionPane.showMessageDialog(this, "密码修改成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog( + this, + "密码修改失败!\n" + "可能的原因:\n" + "• 原密码不正确\n" + "• 新密码不符合要求(6-10位,包含大小写字母和数字)", + "错误", + JOptionPane.ERROR_MESSAGE); + } + } else { + JOptionPane.showMessageDialog(this, "未找到登录用户信息,请重新登录", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/src/com/mathlearning/view/PasswordSetupFrame.java b/src/com/mathlearning/view/PasswordSetupFrame.java new file mode 100644 index 0000000..2216c59 --- /dev/null +++ b/src/com/mathlearning/view/PasswordSetupFrame.java @@ -0,0 +1,129 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class PasswordSetupFrame extends JFrame { + private String email; + private String code; // 存储已验证的验证码 + private AuthController authController; + private JPasswordField passwordField; + private JPasswordField confirmPasswordField; + private JLabel resultLabel; + + public PasswordSetupFrame(String email, String code, AuthController authController) { + this.email = email; + this.code = code; + this.authController = authController; + initializeUI(); + } + + private void initializeUI() { + setTitle("设置密码"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(450, 300); + 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()); + JButton backButton = new JButton("← 返回注册"); + backButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + backButton.setBackground(new Color(200, 200, 200)); + backButton.addActionListener(e -> backToRegister()); + topPanel.add(backButton, BorderLayout.WEST); + + mainPanel.add(topPanel, BorderLayout.NORTH); + + JLabel titleLabel = new JLabel("用户:" + email, JLabel.CENTER); + titleLabel.setFont(new Font("Microsoft YaHei", Font.BOLD, 16)); + mainPanel.add(titleLabel, BorderLayout.CENTER); + + JPanel formPanel = new JPanel(new GridLayout(3, 2, 10, 10)); + + JLabel passwordLabel = new JLabel("密码:"); + passwordLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + passwordField = new JPasswordField(); + + JLabel confirmLabel = new JLabel("确认密码:"); + confirmLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + confirmPasswordField = new JPasswordField(); + + JButton submitButton = new JButton("完成"); + submitButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + + formPanel.add(passwordLabel); + formPanel.add(passwordField); + formPanel.add(confirmLabel); + formPanel.add(confirmPasswordField); + formPanel.add(new JLabel()); + formPanel.add(submitButton); + + resultLabel = new JLabel("密码需6-10位,包含大小写字母和数字", JLabel.CENTER); + resultLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + resultLabel.setForeground(Color.BLUE); + + JPanel centerPanel = new JPanel(new BorderLayout()); + centerPanel.add(formPanel, BorderLayout.CENTER); + centerPanel.add(resultLabel, BorderLayout.SOUTH); + mainPanel.add(centerPanel, BorderLayout.SOUTH); + + submitButton.addActionListener(new SubmitAction()); + + add(mainPanel); + } + + private class SubmitAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String password = new String(passwordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + if (password.isEmpty() || confirmPassword.isEmpty()) { + resultLabel.setText("请填写所有字段"); + resultLabel.setForeground(Color.RED); + return; + } + + if (!password.equals(confirmPassword)) { + resultLabel.setText("两次输入的密码不一致"); + resultLabel.setForeground(Color.RED); + return; + } + + // 使用已验证的验证码完成注册 + if (authController.completeRegistration(email, code, password)) { + JOptionPane.showMessageDialog( + PasswordSetupFrame.this, "注册成功!", "成功", JOptionPane.INFORMATION_MESSAGE); + + // 自动登录并跳转到难度选择界面 + if (authController.login(email, password)) { + LevelSelectionFrame levelFrame = new LevelSelectionFrame(authController); + levelFrame.setVisible(true); + dispose(); + } else { + // 如果自动登录失败,跳转到登录界面 + LoginFrame loginFrame = new LoginFrame(); + loginFrame.setVisible(true); + + dispose(); + } + } else { + resultLabel.setText("密码需6-10位,包含大小写字母和数字"); + resultLabel.setForeground(Color.RED); + } + } + } + + private void backToRegister() { + RegisterFrame registerFrame = new RegisterFrame(); + registerFrame.setVisible(true); + this.dispose(); + } +} diff --git a/src/com/mathlearning/view/QuestionCountFrame.java b/src/com/mathlearning/view/QuestionCountFrame.java new file mode 100644 index 0000000..e137a6b --- /dev/null +++ b/src/com/mathlearning/view/QuestionCountFrame.java @@ -0,0 +1,88 @@ +package com.mathlearning.view; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +public class QuestionCountFrame extends JFrame { + private String level; + private JTextField countField; + private LevelSelectionFrame parentFrame; + + public QuestionCountFrame(LevelSelectionFrame parent, String level) { + this.level = level; + this.parentFrame = parent; + initializeUI(); + } + + private void initializeUI() { + setTitle("题目数量 - " + level); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(300, 200); + setLocationRelativeTo(null); + setResizable(false); + + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + JLabel titleLabel = new JLabel(level + "数学题目", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18)); + mainPanel.add(titleLabel, BorderLayout.NORTH); + + JPanel formPanel = new JPanel(new FlowLayout()); + + JLabel countLabel = new JLabel("题目数量:"); + countLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + countField = new JTextField(10); + countField.setText("10"); // Default value + + formPanel.add(countLabel); + formPanel.add(countField); + + JButton startButton = new JButton("开始答题"); + startButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + + mainPanel.add(formPanel, BorderLayout.CENTER); + mainPanel.add(startButton, BorderLayout.SOUTH); + + startButton.addActionListener(new StartAction()); + + this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + this.addWindowListener( + new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + parentFrame.setVisible(true); + } + }); + add(mainPanel); + } + + private class StartAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + try { + int count = Integer.parseInt(countField.getText().trim()); + if (count < 10 || count > 30) { + JOptionPane.showMessageDialog( + QuestionCountFrame.this, "题目数量应在10-30之间", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + openQuizFrame(count); + } catch (NumberFormatException ex) { + JOptionPane.showMessageDialog( + QuestionCountFrame.this, "请输入有效的数字", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } + + private void openQuizFrame(int count) { + QuizFrame quizFrame = new QuizFrame(level, count, parentFrame); + quizFrame.setVisible(true); + this.dispose(); + } +} diff --git a/src/com/mathlearning/view/QuizFrame.java b/src/com/mathlearning/view/QuizFrame.java new file mode 100644 index 0000000..48590cf --- /dev/null +++ b/src/com/mathlearning/view/QuizFrame.java @@ -0,0 +1,313 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import com.mathlearning.controller.QuestionController; +import com.mathlearning.model.Question; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; + +public class QuizFrame extends JFrame { + private QuestionController questionController; + private String level; + private int totalQuestions; + private LevelSelectionFrame parentFrame; + private JLabel questionNumberLabel; + private JLabel questionTextLabel; + private JRadioButton[] optionButtons; + private ButtonGroup buttonGroup; + private JButton previousButton; + private JButton nextButton; + private JProgressBar progressBar; + + // 存储所有题目和用户选择 + private List allQuestions; + private int[] userSelections; + private int currentDisplayIndex; // 当前显示的题目索引 + + public QuizFrame(String level, int questionCount, LevelSelectionFrame parent) { + this.parentFrame = parent; + this.level = level; + this.totalQuestions = questionCount; + this.questionController = new QuestionController(); + this.questionController.startNewQuiz(level, questionCount); + + // 获取所有题目 + this.allQuestions = questionController.getCurrentQuestions(); + this.userSelections = new int[questionCount]; + this.currentDisplayIndex = 0; + + // 初始化选择为-1(未选择) + for (int i = 0; i < userSelections.length; i++) { + userSelections[i] = -1; + } + + initializeUI(); + showCurrentQuestion(); + } + + private void initializeUI() { + setTitle("数学答题 - " + level); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(700, 550); + setLocationRelativeTo(null); + setResizable(false); + + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 进度条 + progressBar = new JProgressBar(0, totalQuestions); + progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + mainPanel.add(progressBar, BorderLayout.NORTH); + + // 问题面板 + JPanel questionPanel = new JPanel(new BorderLayout(10, 20)); + questionPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + questionNumberLabel = new JLabel("", JLabel.CENTER); + questionNumberLabel.setFont(new Font("Microsoft YaHei", Font.BOLD, 18)); + questionNumberLabel.setForeground(new Color(0, 100, 200)); + questionPanel.add(questionNumberLabel, BorderLayout.NORTH); + + questionTextLabel = new JLabel("", JLabel.CENTER); + questionTextLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 20)); + questionTextLabel.setBorder(BorderFactory.createEmptyBorder(20, 10, 20, 10)); + questionPanel.add(questionTextLabel, BorderLayout.CENTER); + + mainPanel.add(questionPanel, BorderLayout.CENTER); + + // 选项面板 + JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 10, 10)); + optionsPanel.setBorder( + BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.GRAY), "请选择答案")); + + optionButtons = new JRadioButton[4]; + buttonGroup = new ButtonGroup(); + + for (int i = 0; i < 4; i++) { + optionButtons[i] = new JRadioButton(); + optionButtons[i].setFont(new Font("Microsoft YaHei", Font.PLAIN, 16)); + optionButtons[i].setBackground(Color.WHITE); + optionButtons[i].setFocusPainted(false); + optionButtons[i].setPreferredSize(new Dimension(400, 40)); + + final int index = i; + optionButtons[i].addActionListener( + e -> { + // 保存当前选择 + userSelections[currentDisplayIndex] = index; + System.out.println("用户选择: 第" + (currentDisplayIndex + 1) + "题 -> 选项" + index); + }); + + buttonGroup.add(optionButtons[i]); + optionsPanel.add(optionButtons[i]); + } + + JPanel optionsContainer = new JPanel(new FlowLayout(FlowLayout.CENTER)); + optionsContainer.add(optionsPanel); + mainPanel.add(optionsContainer, BorderLayout.SOUTH); + + // 控制面板 + JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + + previousButton = new JButton("上一题"); + previousButton.setFont(new Font("Microsoft YaHei", Font.BOLD, 14)); + previousButton.setBackground(new Color(169, 169, 169)); + previousButton.setForeground(Color.WHITE); + previousButton.setFocusPainted(false); + previousButton.setBorder(BorderFactory.createEmptyBorder(8, 20, 8, 20)); + previousButton.setEnabled(false); // 第一题时禁用 + + nextButton = new JButton("下一题"); + nextButton.setFont(new Font("Microsoft YaHei", Font.BOLD, 16)); + nextButton.setBackground(new Color(70, 130, 180)); + nextButton.setForeground(Color.WHITE); + nextButton.setFocusPainted(false); + nextButton.setBorder(BorderFactory.createEmptyBorder(10, 30, 10, 30)); + + controlPanel.add(previousButton); + controlPanel.add(nextButton); + + JPanel southPanel = new JPanel(new BorderLayout()); + southPanel.add(optionsContainer, BorderLayout.CENTER); + southPanel.add(controlPanel, BorderLayout.SOUTH); + mainPanel.add(southPanel, BorderLayout.SOUTH); + + // 添加事件监听器 + previousButton.addActionListener(new PreviousQuestionAction()); + nextButton.addActionListener(new NextQuestionAction()); + // 添加顶部退出答题按钮 + JPanel topPanel = new JPanel(new BorderLayout()); + + JButton exitButton = new JButton("退出答题"); + exitButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + exitButton.setBackground(new Color(220, 100, 100)); + exitButton.setForeground(Color.WHITE); + exitButton.addActionListener(e -> exitQuiz()); + + progressBar = new JProgressBar(0, totalQuestions); + progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + + topPanel.add(exitButton, BorderLayout.WEST); + topPanel.add(progressBar, BorderLayout.CENTER); + mainPanel.add(topPanel, BorderLayout.NORTH); + add(mainPanel); + } + + private void showCurrentQuestion() { + if (currentDisplayIndex >= allQuestions.size()) { + finishQuiz(); + return; + } + Question currentQuestion = allQuestions.get(currentDisplayIndex); + questionNumberLabel.setText( + String.format("第 %d 题 / 共 %d 题", currentDisplayIndex + 1, totalQuestions)); + questionTextLabel.setText( + "
" + + currentQuestion.getQuestionText() + + "
"); + String[] options = currentQuestion.getOptions(); + for (int i = 0; i < 4; i++) { + String optionText = + String.format( + "
%c. %s
", (char) ('A' + i), options[i]); + optionButtons[i].setText(optionText); + } + buttonGroup.clearSelection(); // 清除当前选择状态 + // 恢复之前的选择(如果有) + if (userSelections[currentDisplayIndex] != -1) { + int previousSelection = userSelections[currentDisplayIndex]; + if (previousSelection >= 0 && previousSelection < 4) { + optionButtons[previousSelection].setSelected(true); + } + } + // 更新进度条 + progressBar.setValue(currentDisplayIndex); + progressBar.setString( + String.format( + "%d/%d (%.0f%%)", + currentDisplayIndex, + totalQuestions, + ((double) currentDisplayIndex / totalQuestions) * 100)); + updateButtonStates(); // 更新按钮状态 + } + + private void exitQuiz() { + int result = + JOptionPane.showConfirmDialog(this, "确定要退出答题吗?所有进度将丢失。", "退出确认", JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.YES_OPTION) { + parentFrame.setVisible(true); + this.dispose(); + } + } + + private void updateButtonStates() { + // 更新上一题按钮状态 + previousButton.setEnabled(currentDisplayIndex > 0); + + // 更新下一题按钮文本 + if (currentDisplayIndex == totalQuestions - 1) { + nextButton.setText("完成答题"); + } else { + nextButton.setText("下一题"); + } + } + + private class PreviousQuestionAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + if (currentDisplayIndex > 0) { + currentDisplayIndex--; + showCurrentQuestion(); + } + } + } + + private class NextQuestionAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + // 检查是否选择了答案 + int selectedIndex = -1; + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isSelected()) { + selectedIndex = i; + break; + } + } + userSelections[currentDisplayIndex] = selectedIndex; // 保存当前选择 + // 如果没有选择答案,提示用户但允许继续(除了最后一题) + if (selectedIndex == -1) { + int result = + JOptionPane.showConfirmDialog( + QuizFrame.this, "您还没有选择答案,确定要继续下一题吗?", "确认", JOptionPane.YES_NO_OPTION); + if (result != JOptionPane.YES_OPTION) { + return; + } + } + // 前进到下一题或完成 + if (currentDisplayIndex < totalQuestions - 1) { + currentDisplayIndex++; + showCurrentQuestion(); + } else { + submitAllAnswers(); // 完成所有题目,提交答案并显示分数 + finishQuiz(); + } + } + } + + private void submitAllAnswers() { // 直接在本地计算分数 + final int calculatedScore = calculateLocalScore(); // 使用final + // 创建一个新的QuestionController来显示正确分数 + questionController = + new QuestionController() { + @Override + public int getScore() { + return calculatedScore; // 现在可以访问了 + } + + @Override + public int getTotalQuestions() { + return totalQuestions; + } + + @Override + public double getPercentage() { + return (double) calculatedScore / totalQuestions * 100; + } + + @Override + public int[] getUserAnswers() { + return userSelections; + } + + @Override + public java.util.List getCurrentQuestions() { + return allQuestions; + } + }; + } + + private int calculateLocalScore() { + int score = 0; + for (int i = 0; i < totalQuestions; i++) { + if (userSelections[i] != -1 && allQuestions.get(i).isCorrect(userSelections[i])) { + score++; + } + } + return score; + } + + private void finishQuiz() { + ScoreFrame scoreFrame = + new ScoreFrame(questionController, userSelections, allQuestions, parentFrame); + scoreFrame.setVisible(true); + this.dispose(); + } +} diff --git a/src/com/mathlearning/view/RegisterFrame.java b/src/com/mathlearning/view/RegisterFrame.java new file mode 100644 index 0000000..c5006db --- /dev/null +++ b/src/com/mathlearning/view/RegisterFrame.java @@ -0,0 +1,279 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.AuthController; +import com.mathlearning.service.EmailService; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +public class RegisterFrame extends JFrame { + private AuthController authController; + private JTextField emailField; + private JLabel resultLabel; + private JButton registerButton; + private Timer countdownTimer; + private int remainingTime = 0; + private JTextField codeField; + private JButton submitButton; + private String currentEmail; // 存储当前正在注册的邮箱 + + public RegisterFrame() { + this.authController = new AuthController(); + initializeUI(); + } + + private void initializeUI() { + setTitle("数学学习软件 - 注册"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(500, 300); // 保持原有宽度以显示更长的提示信息 + 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 FlowLayout(FlowLayout.LEFT)); + JButton backButton = new JButton("← 返回登录"); + backButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + backButton.setBackground(new Color(200, 250, 190)); + backButton.addActionListener(e -> backToLogin()); + topPanel.add(backButton); + mainPanel.add(topPanel, BorderLayout.NORTH); + + JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER); + titleLabel.setFont(new Font("Microsoft YaHei", Font.BOLD, 18)); + mainPanel.add(titleLabel, BorderLayout.CENTER); + + JPanel formPanel = new JPanel(new GridLayout(3, 2, 10, 10)); + formPanel.setLayout(null); + JLabel emailLabel = new JLabel("邮箱地址:"); + emailLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + emailLabel.setBounds(20, 20, 100, 20); + emailField = new JTextField(); + emailField.setBounds(100, 20, 180, 30); + // 添加邮箱输入框的焦点监听器,实现实时验证 + emailField.addFocusListener( + new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + // 获得焦点时不清除内容 + } + + @Override + public void focusLost(FocusEvent e) { + // 失去焦点时验证邮箱格式 + validateEmailInRealTime(); + } + }); + + // 添加输入监听器,实现输入时实时验证 + emailField + .getDocument() + .addDocumentListener( + new javax.swing.event.DocumentListener() { + public void insertUpdate(javax.swing.event.DocumentEvent e) { + validateEmailInRealTime(); + } + + public void removeUpdate(javax.swing.event.DocumentEvent e) { + validateEmailInRealTime(); + } + + public void changedUpdate(javax.swing.event.DocumentEvent e) { + validateEmailInRealTime(); + } + }); + + registerButton = new JButton("获取注册码"); + registerButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + registerButton.setBounds(285, 20, 140, 30); + registerButton.setEnabled(false); // 初始时禁用 + JLabel codeLabel = new JLabel("注册码:"); + codeLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + codeLabel.setBounds(20, 60, 100, 20); + codeField = new JTextField(); + codeField.setBounds(100, 60, 180, 30); + codeField.setEnabled(false); // 初始时禁用 + + submitButton = new JButton("注册"); + submitButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + submitButton.setBounds(285, 60, 140, 30); + submitButton.setEnabled(false); // 初始时禁用 + + formPanel.add(emailLabel); + formPanel.add(emailField); + formPanel.add(new JLabel()); + formPanel.add(registerButton); + formPanel.add(codeLabel); + formPanel.add(codeField); + formPanel.add(submitButton); + + resultLabel = new JLabel("请输入有效的邮箱地址", JLabel.CENTER); + resultLabel.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + resultLabel.setForeground(Color.BLUE); + + mainPanel.add(formPanel, BorderLayout.CENTER); + mainPanel.add(resultLabel, BorderLayout.SOUTH); + registerButton.addActionListener(new RegisterAction()); + submitButton.addActionListener(new SubmitAction()); + countdownTimer = new Timer(1000, new CountdownAction()); + + add(mainPanel); + } + + // 实时验证邮箱格式 + private void validateEmailInRealTime() { + try { + String email = emailField.getText().trim(); + + if (email.isEmpty()) { + resultLabel.setText("请输入邮箱地址"); + resultLabel.setForeground(Color.BLUE); + registerButton.setEnabled(false); + return; + } + + String validationMessage = authController.getEmailValidationMessage(email); + + if (validationMessage.equals("邮箱格式正确")) { + resultLabel.setText("✓ 邮箱格式正确"); + resultLabel.setForeground(new Color(0, 150, 0)); // 绿色 + registerButton.setEnabled(true); + } else { + resultLabel.setText("✗ " + validationMessage); + resultLabel.setForeground(Color.RED); + registerButton.setEnabled(false); + } + } catch (Exception e) { + resultLabel.setText("✗ 邮箱验证服务暂时不可用"); + resultLabel.setForeground(Color.RED); + registerButton.setEnabled(false); + } + } + + private class RegisterAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + if (EmailService.isNetworkAvailable()) { + String email = emailField.getText().trim(); + if (email.isEmpty()) { + resultLabel.setText("请输入邮箱地址"); + resultLabel.setForeground(Color.RED); + return; + } + String validationMessage = authController.getEmailValidationMessage(email); // 再次验证邮箱格式 + if (!validationMessage.equals("邮箱格式正确")) { + resultLabel.setText("✗ " + validationMessage); + resultLabel.setForeground(Color.RED); + return; + } + String result = authController.register(email); + resultLabel.setText(result); + if (result.contains("发送")) { + resultLabel.setForeground(Color.BLUE); + currentEmail = email; // 保存当前邮箱 + remainingTime = 120; // 开始倒计时 + registerButton.setEnabled(false); + codeField.setEnabled(true); // 启用验证码输入框 + submitButton.setEnabled(true); // 启用注册按钮 + updateRegisterButtonText(); + countdownTimer.start(); + } else { + resultLabel.setForeground(Color.RED); + } + } else { + showDialog(); + } + } + } + + private class SubmitAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String code = codeField.getText().trim(); + + if (code.isEmpty()) { + resultLabel.setText("请输入验证码"); + resultLabel.setForeground(Color.RED); + return; + } + + if (currentEmail == null) { + resultLabel.setText("请先获取验证码"); + resultLabel.setForeground(Color.RED); + return; + } + + // 使用AuthController验证验证码 + if (authController.isCodeRegistered(currentEmail, code)) { + countdownTimer.stop(); + resultLabel.setText("验证码正确,正在跳转..."); + resultLabel.setForeground(new Color(0, 150, 0)); + + // 延迟跳转到密码设置界面 + Timer openTimer = + new Timer( + 1000, + evt -> { + openPasswordSetupFrame(currentEmail, code); + }); + openTimer.setRepeats(false); + openTimer.start(); + } else { + resultLabel.setText("验证码错误或已过期,请重新输入"); + resultLabel.setForeground(Color.RED); + } + } + } + + private class CountdownAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + remainingTime--; + updateRegisterButtonText(); + + if (remainingTime <= 0) { + countdownTimer.stop(); + registerButton.setEnabled(true); + registerButton.setText("获取注册码"); + codeField.setEnabled(false); + submitButton.setEnabled(false); + resultLabel.setText("验证码已过期,请重新获取"); + resultLabel.setForeground(Color.RED); + } + } + } + + private void updateRegisterButtonText() { + registerButton.setText(String.format("重新发送(%ds)", remainingTime)); + } + + private void backToLogin() { + if (countdownTimer != null && countdownTimer.isRunning()) { + countdownTimer.stop(); + } + LoginFrame loginFrame = new LoginFrame(); + loginFrame.setVisible(true); + this.dispose(); + } + + private void openPasswordSetupFrame(String email, String code) { + try { + PasswordSetupFrame passwordFrame = new PasswordSetupFrame(email, code, authController); + passwordFrame.setVisible(true); + this.dispose(); + } catch (Exception e) { + System.out.println("打开密码设置界面时发生异常: " + e.getMessage()); + JOptionPane.showMessageDialog(this, "打开密码设置界面失败,请重试", "错误", JOptionPane.ERROR_MESSAGE); + } + } + + private void showDialog() { + JOptionPane.showMessageDialog(this, "网络连接失败,请稍后再试", "错误", JOptionPane.ERROR_MESSAGE); + } +} diff --git a/src/com/mathlearning/view/ScoreFrame.java b/src/com/mathlearning/view/ScoreFrame.java new file mode 100644 index 0000000..9e93212 --- /dev/null +++ b/src/com/mathlearning/view/ScoreFrame.java @@ -0,0 +1,111 @@ +package com.mathlearning.view; + +import com.mathlearning.controller.QuestionController; +import com.mathlearning.model.Question; +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class ScoreFrame extends JFrame { + private QuestionController questionController; + private int[] userSelections; + private List allQuestions; + private LevelSelectionFrame parentFrame; + + public ScoreFrame( + QuestionController questionController, + int[] userSelections, + List allQuestions, + LevelSelectionFrame parent) { + this.parentFrame = parent; + this.questionController = questionController; + this.userSelections = userSelections; + this.allQuestions = allQuestions; + initializeUI(); + } + + private void initializeUI() { + setTitle("答题结果"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(500, 400); + setLocationRelativeTo(null); + setResizable(false); + + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // Score summary + int score = questionController.getScore(); + int total = questionController.getTotalQuestions(); + double percentage = questionController.getPercentage(); + + JLabel scoreLabel = + new JLabel(String.format("得分: %d/%d (%.1f%%)", score, total, percentage), JLabel.CENTER); + scoreLabel.setFont(new Font("Microsoft YaHei", Font.BOLD, 24)); + + Color color; + if (percentage >= 80) color = Color.GREEN; + else if (percentage >= 60) color = Color.ORANGE; + else color = Color.RED; + + scoreLabel.setForeground(color); + mainPanel.add(scoreLabel, BorderLayout.NORTH); + + // Detailed results - 使用传递过来的题目和用户选择 + JTextArea detailsArea = new JTextArea(); + detailsArea.setEditable(false); + detailsArea.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); + + StringBuilder details = new StringBuilder(); + details.append("答题详情:\n\n"); + + for (int i = 0; i < allQuestions.size(); i++) { + Question q = allQuestions.get(i); + int userAnswer = userSelections[i]; + boolean isCorrect = (userAnswer != -1) && q.isCorrect(userAnswer); + + details.append(String.format("第%d题: %s\n", i + 1, q.getQuestionText())); + details.append( + String.format( + "你的答案: %s\n", + userAnswer == -1 + ? "未作答" + : (char) ('A' + userAnswer) + ". " + q.getOptions()[userAnswer])); + details.append( + String.format( + "正确答案: %s\n", + (char) ('A' + q.getCorrectAnswerIndex()) + + ". " + + q.getOptions()[q.getCorrectAnswerIndex()])); + details.append(isCorrect ? "✓ 正确\n" : "✗ 错误\n"); + details.append("--------------------\n"); + } + + detailsArea.setText(details.toString()); + JScrollPane scrollPane = new JScrollPane(detailsArea); + mainPanel.add(scrollPane, BorderLayout.CENTER); + + // Buttons + JPanel buttonPanel = new JPanel(new FlowLayout()); + JButton restartButton = new JButton("重新开始"); + JButton exitButton = new JButton("退出"); + + restartButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + exitButton.setFont(new Font("Microsoft YaHei", Font.PLAIN, 14)); + + buttonPanel.add(restartButton); + buttonPanel.add(exitButton); + + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + restartButton.addActionListener(e -> restartToLevelSelection()); + exitButton.addActionListener(e -> System.exit(0)); + + add(mainPanel); + } + + private void restartToLevelSelection() { + parentFrame.setVisible(true); + this.dispose(); + } +}