diff --git a/README.md b/README.md deleted file mode 100644 index 8101d5a..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# mathlearningapp - diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..f6bc7fe --- /dev/null +++ b/doc/README.md @@ -0,0 +1,188 @@ +# mathlearningapp + +# mathlearningapp + +# 数学学习软件 - 版本演进文档 + +## 项目概述 + +数学学习软件是一个基于Java Swing开发的桌面应用程序,旨在为不同学习阶段(小学、初中、高中)的学生提供数学题目练习和测试功能。软件采用模块化设计,包含用户管理、题目生成、答题测试等核心功能。 + +## 版本演进历史 + +### 版本1.0 - 基础版本 + +#### 核心功能 +- **用户管理**:邮箱注册、登录、密码修改 +- **题目生成**:小学、初中、高中三个级别的题目生成器 +- **答题系统**:选择题形式,支持自定义题目数量 +- **成绩统计**:显示得分和正确率 + +#### 技术特点 +- 使用CardLayout实现多面板切换 +- 基础的用户验证系统 +- 简单的题目生成算法 + +#### 主要类结构 +``` +User - 用户类 +Question - 题目类 +QuestionGenerator - 题目生成器抽象类 +PrimaryQuestionGenerator - 小学题目生成器 +JuniorQuestionGenerator - 初中题目生成器 +SeniorQuestionGenerator - 高中题目生成器 +UserManager - 用户管理类 +MathLearningApp - 主应用程序类 +``` + +### 版本2.0 - 功能增强版 + +#### 新增功能 +- **用户名系统**:引入用户名概念,不再仅依赖邮箱 +- **界面美化**:改进UI设计,添加颜色和样式 +- **验证码系统**:注册时发送验证码 +- **欢迎界面**:登录后显示个性化欢迎信息 + +#### 技术改进 +- 改进的用户管理,支持用户名和邮箱双重索引 +- 增强的题目生成逻辑 +- 更友好的用户界面 + +### 版本3.0 - 算法优化版 + +#### 核心改进 +- **题目生成优化**: + - 小学:操作数数量扩展为1-5个 + - 初中:支持混合运算和平方/开方运算 + - 高中:三角函数使用特殊角度,确保结果合理 +- **表达式计算**:引入JavaScript引擎计算复杂表达式 +- **输入验证**:题目数量限制为10-30题 + +#### 技术升级 +- 更健壮的题目生成算法 +- 改进的错误处理机制 +- 优化的用户交互体验 + +### 版本4.0 - 企业级版本 + +#### 重大改进 +- **数据持久化**:用户数据序列化保存到文件 +- **表达式计算器**:自定义表达式计算引擎 +- **包管理**:添加com.hnu包结构 +- **代码重构**:大幅优化代码结构和算法 + +#### 新增特性 +- **文件存储**:用户数据自动保存和加载 +- **高级计算**:支持括号和运算优先级 +- **健壮性提升**:完善的异常处理和输入验证 + +## 核心功能详解 + +### 1. 用户管理系统 + +#### 版本演进 +- **1.0**:基于邮箱的简单用户管理 +- **2.0**:引入用户名系统,双重索引 +- **4.0**:数据持久化,文件存储 + +#### 功能特性 +- 用户注册(邮箱验证) +- 用户登录 +- 密码修改 +- 数据持久化(4.0+) + +### 2. 题目生成系统 + +#### 小学题目 +- **运算符**:+、-、*、/ +- **特点**:支持括号,2-4个操作数 +- **演进**:从简单运算到支持复杂表达式 + +#### 初中题目 +- **类型**:基本运算、平方、开方 +- **特点**:混合运算,确保完全平方数 +- **演进**:算法优化,结果更合理 + +#### 高中题目 +- **类型**:三角函数、复合表达式 +- **特点**:使用特殊角度,避免极端值 +- **演进**:从简单三角函数到复合表达式 + +### 3. 答题与评分系统 + +#### 答题流程 +1. 选择学习阶段 +2. 输入题目数量 +3. 逐题作答 +4. 查看成绩 + +#### 评分机制 +- 实时计分 +- 百分比显示 +- 成绩评级(优秀、良好、及格、加油) + +## 技术架构 + +### 设计模式应用 +- **工厂模式**:题目生成器 +- **单例模式**:用户管理 +- **策略模式**:不同级别的题目生成 + +### 核心算法 + +#### 表达式计算(版本4.0) +```java +// 支持括号和运算优先级 +public static double calculate(String expression) { + // 递归处理括号 + // 先乘除后加减 + // 完善的错误处理 +} +``` + +#### 题目生成 +```java +// 确保题目多样性和合理性 +public Question generateQuestion() { + // 操作数数量随机 + // 运算符随机选择 + // 错误答案生成算法 + // 选项随机排序 +} +``` + +## 部署与使用 + +### 环境要求 +- Java 8+ +- 支持Swing的Java环境 + +### 运行方式 +```bash +javac MathLearningApp.java +java MathLearningApp +``` + +### 测试账户 +- 用户名:测试用户 +- 密码:Test123 + +## 版本对比总结 + +| 特性 | 1.0 | 2.0 | 3.0 | 4.0 | +|------|-----|-----|-----|-----| +| 用户管理 | 基础 | 增强 | 增强 | 持久化 | +| 题目生成 | 简单 | 改进 | 优化 | 高级 | +| 界面设计 | 基础 | 美化 | 优化 | 专业 | +| 数据存储 | 内存 | 内存 | 内存 | 文件 | +| 计算能力 | 基础 | 基础 | 引擎 | 自定义 | +| 代码结构 | 简单 | 模块化 | 优化 | 企业级 | + +## 未来发展建议 + +1. **数据库集成**:替换文件存储为数据库 +2. **网络功能**:添加在线排名和题目分享 +3. **移动端**:开发Android和iOS版本 +4. **AI辅助**:集成智能题目推荐 +5. **多媒体**:添加题目解析视频功能 + diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..13275f1 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,30 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +.kotlin + +### 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/src/.idea/.gitignore b/src/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/src/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/.idea/.name b/src/.idea/.name new file mode 100644 index 0000000..696aa06 --- /dev/null +++ b/src/.idea/.name @@ -0,0 +1 @@ +MathLearningApp.java \ No newline at end of file diff --git a/src/.idea/inspectionProfiles/Project_Default.xml b/src/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..a374ccf --- /dev/null +++ b/src/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml new file mode 100644 index 0000000..28c56bd --- /dev/null +++ b/src/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml new file mode 100644 index 0000000..49b3317 --- /dev/null +++ b/src/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml new file mode 100644 index 0000000..288b36b --- /dev/null +++ b/src/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/MathExamGenerator.iml b/src/MathExamGenerator.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/src/MathExamGenerator.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/src/MathLearningApp.java b/src/src/MathLearningApp.java new file mode 100644 index 0000000..525a026 --- /dev/null +++ b/src/src/MathLearningApp.java @@ -0,0 +1,1310 @@ +import javax.swing.*; +import java.awt.*; +import java.util.*; +import java.util.regex.Pattern; +import java.util.List; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.HashMap; +import java.util.Map; +import java.io.*; +import java.nio.file.*; + +// 用户类 - 实现序列化接口 +class User implements Serializable { + private static final long serialVersionUID = 1L; + private String username; + private String email; + private String password; + private String verificationCode; + + public User(String username, String email, String password) { + this.username = username; + this.email = email; + this.password = password; + } + + public String getUsername() { return username; } + public String getEmail() { return email; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + public String getVerificationCode() { return verificationCode; } + public void setVerificationCode(String code) { this.verificationCode = code; } +} + +// 表达式计算工具类 +class ExpressionCalculator { + + // 计算表达式结果(支持加减乘除和括号) + public static double calculate(String expression) { + try { + // 移除问号和其他非表达式字符 + String cleanExpr = expression.replace(" = ?", "").trim(); + return evaluateExpression(cleanExpr); + } catch (Exception e) { + return 0; + } + } + + // 递归计算表达式 + private static double evaluateExpression(String expr) { + // 处理括号 + while (expr.contains("(")) { + int start = expr.lastIndexOf("("); + int end = expr.indexOf(")", start); + if (end == -1) break; + + String subExpr = expr.substring(start + 1, end); + double subResult = evaluateExpression(subExpr); + expr = expr.substring(0, start) + subResult + expr.substring(end + 1); + } + + return evaluateWithoutParentheses(expr); + } + + // 计算没有括号的表达式(先乘除后加减) + private static double evaluateWithoutParentheses(String expr) { + // 分割表达式为数字和操作符 + List numbers = new ArrayList<>(); + List operators = new ArrayList<>(); + + parseExpression(expr, numbers, operators); + + // 先处理乘除 + for (int i = 0; i < operators.size(); i++) { + char op = operators.get(i); + if (op == '*' || op == '/') { + double left = numbers.get(i); + double right = numbers.get(i + 1); + double result = performOperation(left, right, op); + + numbers.set(i, result); + numbers.remove(i + 1); + operators.remove(i); + i--; // 调整索引 + } + } + + // 再处理加减 + double result = numbers.get(0); + for (int i = 0; i < operators.size(); i++) { + char op = operators.get(i); + double right = numbers.get(i + 1); + result = performOperation(result, right, op); + } + + return result; + } + + // 解析表达式为数字和操作符列表 + private static void parseExpression(String expr, List numbers, List operators) { + StringBuilder currentNumber = new StringBuilder(); + + for (int i = 0; i < expr.length(); i++) { + char c = expr.charAt(i); + + if (c == '+' || c == '-' || c == '*' || c == '/') { + if (currentNumber.length() > 0) { + numbers.add(Double.parseDouble(currentNumber.toString())); + currentNumber.setLength(0); + } + operators.add(c); + } else if (Character.isDigit(c) || c == '.') { + currentNumber.append(c); + } + // 忽略空格和其他字符 + } + + // 添加最后一个数字 + if (currentNumber.length() > 0) { + numbers.add(Double.parseDouble(currentNumber.toString())); + } + } + + // 执行单个运算 + private static double performOperation(double left, double right, char operator) { + switch (operator) { + case '+': return left + right; + case '-': return left - right; + case '*': return left * right; + case '/': + if (right == 0) return 0; // 避免除零 + return left / right; + default: return 0; + } + } +} + +// 题目类 +class Question { + private String content; + private String[] options; + private int correctAnswer; + + public Question(String content, String[] options, int correctAnswer) { + this.content = content; + this.options = options; + this.correctAnswer = correctAnswer; + } + + public String getContent() { return content; } + public String[] getOptions() { return options; } + public int getCorrectAnswer() { return correctAnswer; } +} + +// 抽象题目生成器 +abstract class QuestionGenerator { + protected Random random = new Random(); + + public abstract Question generateQuestion(); + + protected int generateNumber() { + return random.nextInt(100) + 1; + } + + protected String[] generateOptionsWithCorrectAnswer(int correctAnswer) { + String[] options = new String[4]; + Set usedAnswers = new HashSet<>(); + + options[0] = String.valueOf(correctAnswer); + usedAnswers.add(options[0]); + + for (int i = 1; i < 4; i++) { + String wrongAnswer; + do { + int variation = random.nextInt(20) + 1; + int wrongValue = correctAnswer + (random.nextBoolean() ? variation : -variation); + if (wrongValue < 0) wrongValue = -wrongValue; + wrongAnswer = String.valueOf(wrongValue); + } while (usedAnswers.contains(wrongAnswer)); + + usedAnswers.add(wrongAnswer); + options[i] = wrongAnswer; + } + + List optionList = new ArrayList<>(Arrays.asList(options)); + Collections.shuffle(optionList); + return optionList.toArray(new String[0]); + } + + protected int findCorrectAnswerIndex(String[] options, String correctAnswer) { + for (int i = 0; i < options.length; i++) { + if (options[i].equals(correctAnswer)) { + return i; + } + } + return 0; + } +} + +// 小学题目生成器 +class PrimaryQuestionGenerator extends QuestionGenerator { + @Override + public Question generateQuestion() { + int operandCount = Math.max(2, random.nextInt(3) + 2); // 2-4个操作数 + StringBuilder question = new StringBuilder(); + + // 随机决定是否添加括号(只在有3个以上操作数时) + boolean hasParentheses = random.nextBoolean() && operandCount >= 3; + int parenthesesPosition = 0; + if (hasParentheses && operandCount >= 3) { + parenthesesPosition = random.nextInt(operandCount - 1); + } + + for (int i = 0; i < operandCount; i++) { + if (hasParentheses && i == parenthesesPosition) { + question.append("("); + } + + question.append(generateNumber()); + + if (hasParentheses && i == parenthesesPosition + 1) { + question.append(")"); + } + + if (i < operandCount - 1) { + question.append(" ").append(generatePrimaryOperator()).append(" "); + } + } + + question.append(" = ?"); + + // 使用新的计算器计算正确答案 + double result = ExpressionCalculator.calculate(question.toString()); + int correctAnswer = (int) Math.round(result); + + // 确保答案合理 + if (correctAnswer < 0) correctAnswer = Math.abs(correctAnswer); + if (correctAnswer > 10000) correctAnswer = correctAnswer % 1000; + + String[] options = generateOptionsWithCorrectAnswer(correctAnswer); + int correctIndex = findCorrectAnswerIndex(options, String.valueOf(correctAnswer)); + + return new Question(question.toString(), options, correctIndex); + } + + // 小学只使用加减乘,避免复杂除法 + private String generatePrimaryOperator() { + String[] operators = {"+", "-", "*"}; + return operators[random.nextInt(operators.length)]; + } +} + +// 初中题目生成器 +class JuniorQuestionGenerator extends QuestionGenerator { + @Override + public Question generateQuestion() { + if (random.nextBoolean()) { + // 平方或开根号题目 + int num = generateNumber(); + if (random.nextBoolean()) { + // 平方运算 + String question = num + "² = ?"; + int correctAnswer = num * num; + String[] options = generateOptionsWithCorrectAnswer(correctAnswer); + int correctIndex = findCorrectAnswerIndex(options, String.valueOf(correctAnswer)); + return new Question(question, options, correctIndex); + } else { + // 开根号运算 - 确保是完全平方数 + int squared = num * num; + String question = "√" + squared + " = ?"; + int correctAnswer = num; + String[] options = generateOptionsWithCorrectAnswer(correctAnswer); + int correctIndex = findCorrectAnswerIndex(options, String.valueOf(correctAnswer)); + return new Question(question, options, correctIndex); + } + } else { + // 混合运算题目 + int operandCount = random.nextInt(3) + 2; // 2-4个操作数 + StringBuilder question = new StringBuilder(); + + for (int i = 0; i < operandCount; i++) { + question.append(generateNumber()); + if (i < operandCount - 1) { + question.append(" ").append(generateJuniorOperator()).append(" "); + } + } + + question.append(" = ?"); + + // 使用新的计算器 + double result = ExpressionCalculator.calculate(question.toString()); + int correctAnswer = (int) Math.round(result); + + // 确保答案合理 + if (Math.abs(correctAnswer) > 10000) { + correctAnswer = correctAnswer % 1000; + } + + String[] options = generateOptionsWithCorrectAnswer(correctAnswer); + int correctIndex = findCorrectAnswerIndex(options, String.valueOf(correctAnswer)); + return new Question(question.toString(), options, correctIndex); + } + } + + private String generateJuniorOperator() { + String[] operators = {"+", "-", "*", "/"}; + return operators[random.nextInt(operators.length)]; + } +} + +// 高中题目生成器 +class SeniorQuestionGenerator extends QuestionGenerator { + private final String[] trigFunctions = {"sin", "cos", "tan"}; + + @Override + public Question generateQuestion() { + if (random.nextBoolean()) { + // 三角函数题目 + return generateTrigQuestion(); + } else { + // 复合表达式题目 + return generateComplexExpression(); + } + } + + private Question generateTrigQuestion() { + String trigFunction = trigFunctions[random.nextInt(trigFunctions.length)]; + // 使用特殊角度确保结果合理 + int[] specialAngles = {0, 30, 45, 60, 90, 120, 135, 150, 180, 210, 225, 240, 270, 300, 315, 330, 360}; + int angle = specialAngles[random.nextInt(specialAngles.length)]; + + String question = trigFunction + "(" + angle + "°) = ?"; + + double result; + switch (trigFunction) { + case "sin": + result = Math.sin(Math.toRadians(angle)); + break; + case "cos": + result = Math.cos(Math.toRadians(angle)); + break; + case "tan": + result = Math.tan(Math.toRadians(angle)); + // 处理tan函数在90°和270°的无穷大情况 + if (angle % 180 == 90 && angle % 360 != 270) { + result = Double.POSITIVE_INFINITY; + } + break; + default: + result = 0; + } + + // 四舍五入到两位小数,处理浮点数精度问题 + double roundedResult = Math.round(result * 100.0) / 100.0; + + // 处理无穷大情况 + if (Double.isInfinite(roundedResult)) { + roundedResult = 1000; // 用一个大的数值表示无穷大 + } + + String[] options = generateTrigOptionsWithCorrectAnswer(roundedResult); + int correctIndex = findCorrectAnswerIndex(options, String.format("%.2f", roundedResult)); + return new Question(question, options, correctIndex); + } + + private Question generateComplexExpression() { + // 生成包含三角函数和基本运算的表达式 + int operandCount = random.nextInt(2) + 2; // 2-3个操作数 + StringBuilder question = new StringBuilder(); + + for (int i = 0; i < operandCount; i++) { + if (random.nextBoolean() && i < operandCount - 1) { + // 添加三角函数 + String trigFunction = trigFunctions[random.nextInt(trigFunctions.length)]; + int[] specialAngles = {0, 30, 45, 60, 90, 180, 270, 360}; + int angle = specialAngles[random.nextInt(specialAngles.length)]; + question.append(trigFunction).append("(").append(angle).append("°)"); + } else { + // 添加普通数字 + question.append(generateNumber()); + } + + if (i < operandCount - 1) { + question.append(" ").append(generateSeniorOperator()).append(" "); + } + } + + question.append(" = ?"); + + // 对于复杂表达式,简化处理 + double result = ExpressionCalculator.calculate(question.toString()); + int correctAnswer = (int) Math.round(result); + + String[] options = generateOptionsWithCorrectAnswer(correctAnswer); + int correctIndex = findCorrectAnswerIndex(options, String.valueOf(correctAnswer)); + return new Question(question.toString(), options, correctIndex); + } + + private String generateSeniorOperator() { + String[] operators = {"+", "-", "*", "/"}; + return operators[random.nextInt(operators.length)]; + } + + private String[] generateTrigOptionsWithCorrectAnswer(double correctAnswer) { + String[] options = new String[4]; + Set usedAnswers = new HashSet<>(); + + String correctAnswerStr = String.format("%.2f", correctAnswer); + options[0] = correctAnswerStr; + usedAnswers.add(correctAnswerStr); + + for (int i = 1; i < 4; i++) { + String wrongAnswer; + do { + double variation = (random.nextDouble() * 2 - 1) * 0.5; + double wrongValue = correctAnswer + variation; + wrongAnswer = String.format("%.2f", wrongValue); + } while (usedAnswers.contains(wrongAnswer)); + + usedAnswers.add(wrongAnswer); + options[i] = wrongAnswer; + } + + List optionList = new ArrayList<>(Arrays.asList(options)); + Collections.shuffle(optionList); + return optionList.toArray(new String[0]); + } +} + +// 模拟邮箱工具类 +class EmailUtil { + public static boolean sendVerificationCode(String toEmail, String code) { + // 模拟发送验证码,显示在对话框中 + System.out.println("模拟发送验证码到 " + toEmail + ": " + code); + + // 在实际项目中,这里应该集成真实的邮箱发送功能 + // 暂时使用模拟成功,显示验证码给用户 + JOptionPane.showMessageDialog(null, + "验证码已发送到: " + toEmail + "\n验证码: " + code + "\n\n(这是模拟发送,实际项目需配置真实邮箱)", + "验证码信息", + JOptionPane.INFORMATION_MESSAGE); + return true; + } +} + +// 用户管理类 +class UserManager { + private Map usersByEmail; + private Map usersByUsername; + private final String USER_DATA_DIR = "user_data"; + private final String USER_DATA_FILE = "users.dat"; + + public UserManager() { + usersByEmail = new HashMap<>(); + usersByUsername = new HashMap<>(); + + // 创建用户数据目录 + createUserDataDirectory(); + + // 从文件加载用户数据 + loadUsersFromFile(); + + // 如果没有用户数据,添加一些测试用户 + if (usersByEmail.isEmpty()) { + addUser("测试用户", "test@test.com", "Test123"); + addUser("示例用户", "user@example.com", "User123"); + saveUsersToFile(); + } + } + + private void createUserDataDirectory() { + try { + Files.createDirectories(Paths.get(USER_DATA_DIR)); + } catch (IOException e) { + System.err.println("创建用户数据目录失败: " + e.getMessage()); + } + } + + @SuppressWarnings("unchecked") + private void loadUsersFromFile() { + File userFile = new File(USER_DATA_DIR, USER_DATA_FILE); + if (!userFile.exists()) { + return; + } + + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(userFile))) { + List userList = (List) ois.readObject(); + for (User user : userList) { + usersByEmail.put(user.getEmail(), user); + usersByUsername.put(user.getUsername(), user); + } + System.out.println("成功加载 " + userList.size() + " 个用户数据"); + } catch (IOException | ClassNotFoundException e) { + System.err.println("加载用户数据失败: " + e.getMessage()); + } + } + + private void saveUsersToFile() { + File userFile = new File(USER_DATA_DIR, USER_DATA_FILE); + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(userFile))) { + List userList = new ArrayList<>(usersByEmail.values()); + oos.writeObject(userList); + System.out.println("成功保存 " + userList.size() + " 个用户数据"); + } catch (IOException e) { + System.err.println("保存用户数据失败: " + e.getMessage()); + JOptionPane.showMessageDialog(null, + "用户数据保存失败: " + e.getMessage(), + "错误", + JOptionPane.ERROR_MESSAGE); + } + } + + private void addUser(String username, String email, String password) { + User user = new User(username, email, password); + usersByEmail.put(email, user); + usersByUsername.put(username, user); + } + + // 在UserManager类中添加新的updatePasswordByUsername方法 + public boolean updatePasswordByUsername(String username, String oldPassword, String newPassword) { + User user = usersByUsername.get(username); + if (user != null && user.getPassword().equals(oldPassword)) { + user.setPassword(newPassword); + saveUsersToFile(); // 保存更改到文件 + return true; + } + return false; + } + + public boolean registerUser(String username, String email, String password) { + if (usersByEmail.containsKey(email) || usersByUsername.containsKey(username)) { + return false; + } + + User user = new User(username, email, password); + usersByEmail.put(email, user); + usersByUsername.put(username, user); + saveUsersToFile(); // 保存新用户到文件 + return true; + } + + public User login(String username, String password) { + User user = usersByUsername.get(username); + if (user != null && user.getPassword().equals(password)) { + return user; + } + return null; + } + + public boolean updatePassword(String email, String oldPassword, String newPassword) { + User user = usersByEmail.get(email); + if (user != null && user.getPassword().equals(oldPassword)) { + user.setPassword(newPassword); + saveUsersToFile(); // 保存更改到文件 + return true; + } + return false; + } + + public boolean isEmailRegistered(String email) { + return usersByEmail.containsKey(email); + } + + public boolean isUsernameRegistered(String username) { + return usersByUsername.containsKey(username); + } +} + +// 主应用程序 +public class MathLearningApp { + private JFrame mainFrame; + private CardLayout cardLayout; + private JPanel mainPanel; + private UserManager userManager; + private User currentUser; + private List currentExam; + private int currentQuestionIndex; + private int score; + + // UI组件 + private JTextField emailField; + private JPasswordField passwordField; + private JLabel questionLabel; + private ButtonGroup optionGroup; + private JRadioButton[] optionButtons; + private JLabel scoreLabel; + private JLabel welcomeLabel; + + public MathLearningApp() { + userManager = new UserManager(); + initializeUI(); + } + + private void initializeUI() { + mainFrame = new JFrame("数学学习软件"); + mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + mainFrame.setSize(600, 500); + mainFrame.setLocationRelativeTo(null); + + cardLayout = new CardLayout(); + mainPanel = new JPanel(cardLayout); + + createLoginPanel(); + createRegisterPanel(); + createSetPasswordPanel(); + createLevelSelectionPanel(); + createQuestionCountPanel(); + createExamPanel(); + createScorePanel(); + createChangePasswordPanel(); + + mainFrame.add(mainPanel); + mainFrame.setVisible(true); + } + + private void createLoginPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(15, 15, 15, 15); + gbc.fill = GridBagConstraints.HORIZONTAL; + + JLabel titleLabel = new JLabel("数学学习软件", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + titleLabel.setForeground(new Color(0, 100, 200)); + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; + panel.add(titleLabel, gbc); + + JLabel subtitleLabel = new JLabel("用户登录", JLabel.CENTER); + subtitleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + subtitleLabel.setForeground(Color.GRAY); + gbc.gridy = 1; + panel.add(subtitleLabel, gbc); + + gbc.gridwidth = 1; + gbc.gridy = 2; gbc.gridx = 0; + JLabel usernameLabel = new JLabel("用户名:"); + usernameLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); + panel.add(usernameLabel, gbc); + + gbc.gridx = 1; + emailField = new JTextField(20); + emailField.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + panel.add(emailField, gbc); + + gbc.gridy = 3; gbc.gridx = 0; + JLabel passwordLabel = new JLabel("密码:"); + passwordLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); + panel.add(passwordLabel, gbc); + + gbc.gridx = 1; + passwordField = new JPasswordField(20); + passwordField.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + panel.add(passwordField, gbc); + + gbc.gridy = 4; gbc.gridx = 0; gbc.gridwidth = 2; + JPanel buttonPanel = new JPanel(new FlowLayout()); + + JButton loginButton = createStyledButton("登录", new Color(70, 130, 180)); + loginButton.addActionListener(e -> handleLogin()); + buttonPanel.add(loginButton); + + JButton registerButton = createStyledButton("注册", new Color(60, 179, 113)); + registerButton.addActionListener(e -> cardLayout.show(mainPanel, "Register")); + buttonPanel.add(registerButton); + + JButton changePasswordButton = createStyledButton("修改密码", new Color(218, 165, 32)); + changePasswordButton.addActionListener(e -> cardLayout.show(mainPanel, "ChangePassword")); + buttonPanel.add(changePasswordButton); + + panel.add(buttonPanel, gbc); + + // 添加测试账号提示 + gbc.gridy = 5; + JLabel testAccountLabel = new JLabel("测试账号: 测试用户 / Test123", JLabel.CENTER); + testAccountLabel.setFont(new Font("微软雅黑", Font.ITALIC, 12)); + testAccountLabel.setForeground(Color.GRAY); + panel.add(testAccountLabel, gbc); + + mainPanel.add(panel, "Login"); + } + + private void createRegisterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + 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; + panel.add(titleLabel, gbc); + + gbc.gridwidth = 1; + gbc.gridy = 1; gbc.gridx = 0; + panel.add(new JLabel("用户名:"), gbc); + + gbc.gridx = 1; + JTextField usernameField = new JTextField(20); + panel.add(usernameField, gbc); + + gbc.gridy = 2; gbc.gridx = 0; + panel.add(new JLabel("邮箱:"), gbc); + + gbc.gridx = 1; + JTextField registerEmailField = new JTextField(20); + panel.add(registerEmailField, gbc); + + gbc.gridy = 3; gbc.gridx = 0; gbc.gridwidth = 2; + JButton sendCodeButton = createStyledButton("发送验证码", new Color(70, 130, 180)); + panel.add(sendCodeButton, gbc); + + gbc.gridwidth = 1; + gbc.gridy = 4; gbc.gridx = 0; + panel.add(new JLabel("验证码:"), gbc); + + gbc.gridx = 1; + JTextField registerCodeField = new JTextField(20); + panel.add(registerCodeField, gbc); + + gbc.gridy = 5; gbc.gridx = 0; gbc.gridwidth = 2; + JButton registerButton = createStyledButton("注册", new Color(60, 179, 113)); + registerButton.addActionListener(e -> { + String username = usernameField.getText(); + String email = registerEmailField.getText(); + String code = registerCodeField.getText(); + + if (username.isEmpty() || email.isEmpty() || code.isEmpty()) { + JOptionPane.showMessageDialog(panel, "请填写所有信息"); + return; + } + + if (userManager.isUsernameRegistered(username)) { + JOptionPane.showMessageDialog(panel, "用户名已被使用"); + return; + } + + // 验证验证码(简化处理,只要6位就通过) + if (code.length() == 6) { + // 保存用户信息用于后续设置密码 + mainPanel.putClientProperty("registerUsername", username); + mainPanel.putClientProperty("registerEmail", email); + cardLayout.show(mainPanel, "SetPassword"); + } else { + JOptionPane.showMessageDialog(panel, "验证码必须是6位数字"); + } + }); + panel.add(registerButton, gbc); + + gbc.gridy = 6; + JButton backButton = createStyledButton("返回登录", new Color(169, 169, 169)); + backButton.addActionListener(e -> cardLayout.show(mainPanel, "Login")); + panel.add(backButton, gbc); + + sendCodeButton.addActionListener(e -> { + String username = usernameField.getText(); + String email = registerEmailField.getText(); + + if (username.isEmpty() || email.isEmpty()) { + JOptionPane.showMessageDialog(panel, "请填写用户名和邮箱"); + return; + } + + if (userManager.isUsernameRegistered(username)) { + JOptionPane.showMessageDialog(panel, "用户名已被使用"); + return; + } + + if (isValidEmail(email)) { + if (userManager.isEmailRegistered(email)) { + JOptionPane.showMessageDialog(panel, "该邮箱已被注册"); + return; + } + + String verificationCode = generateVerificationCode(); + if (EmailUtil.sendVerificationCode(email, verificationCode)) { + // 保存验证码用于验证 + mainPanel.putClientProperty("verificationCode", verificationCode); + JOptionPane.showMessageDialog(panel, "验证码已发送,请查看对话框"); + } + } else { + JOptionPane.showMessageDialog(panel, "请输入有效的邮箱地址"); + } + }); + + mainPanel.add(panel, "Register"); + } + + private void createSetPasswordPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + 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; + panel.add(titleLabel, gbc); + + // 添加密码要求说明 + JLabel requirementLabel = new JLabel("密码要求:6-10位,包含大小写字母和数字", JLabel.CENTER); + requirementLabel.setFont(new Font("微软雅黑", Font.ITALIC, 12)); + requirementLabel.setForeground(Color.RED); + gbc.gridy = 1; gbc.gridwidth = 2; + panel.add(requirementLabel, gbc); + + gbc.gridwidth = 1; + gbc.gridy = 2; gbc.gridx = 0; + panel.add(new JLabel("密码:"), gbc); + + gbc.gridx = 1; + JPasswordField setPasswordField = new JPasswordField(20); + panel.add(setPasswordField, gbc); + + gbc.gridy = 3; gbc.gridx = 0; + panel.add(new JLabel("确认密码:"), gbc); + + gbc.gridx = 1; + JPasswordField confirmSetPasswordField = new JPasswordField(20); + panel.add(confirmSetPasswordField, gbc); + + gbc.gridy = 4; gbc.gridx = 0; gbc.gridwidth = 2; + JButton setPasswordButton = createStyledButton("设置密码", new Color(60, 179, 113)); + setPasswordButton.addActionListener(e -> { + String password = new String(setPasswordField.getPassword()); + String confirmPassword = new String(confirmSetPasswordField.getPassword()); + String username = (String) mainPanel.getClientProperty("registerUsername"); + String email = (String) mainPanel.getClientProperty("registerEmail"); + + if (validatePassword(password, confirmPassword)) { + if (userManager.registerUser(username, email, password)) { + JOptionPane.showMessageDialog(panel, "注册成功!"); + cardLayout.show(mainPanel, "Login"); + } else { + JOptionPane.showMessageDialog(panel, "注册失败,用户已存在"); + } + } + }); + panel.add(setPasswordButton, gbc); + + mainPanel.add(panel, "SetPassword"); + } + + private void createLevelSelectionPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30)); + panel.setBackground(new Color(240, 248, 255)); + + JLabel titleLabel = new JLabel("选择学习阶段", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + titleLabel.setForeground(new Color(0, 100, 200)); + panel.add(titleLabel, BorderLayout.NORTH); + + welcomeLabel = new JLabel("", JLabel.CENTER); + welcomeLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + welcomeLabel.setForeground(Color.DARK_GRAY); + panel.add(welcomeLabel, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new GridLayout(3, 1, 20, 20)); + buttonPanel.setBorder(BorderFactory.createEmptyBorder(20, 50, 20, 50)); + buttonPanel.setBackground(new Color(240, 248, 255)); + + JButton primaryButton = createLevelButton("小学", new Color(135, 206, 250)); + primaryButton.addActionListener(e -> startExam("小学")); + buttonPanel.add(primaryButton); + + JButton juniorButton = createLevelButton("初中", new Color(100, 149, 237)); + juniorButton.addActionListener(e -> startExam("初中")); + buttonPanel.add(juniorButton); + + JButton seniorButton = createLevelButton("高中", new Color(65, 105, 225)); + seniorButton.addActionListener(e -> startExam("高中")); + buttonPanel.add(seniorButton); + + panel.add(buttonPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "LevelSelection"); + } + + private void createQuestionCountPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(15, 15, 15, 15); + + JLabel titleLabel = new JLabel("请输入题目数量", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + gbc.gridx = 0; gbc.gridy = 0; + panel.add(titleLabel, gbc); + + JTextField countField = new JTextField(10); + countField.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + gbc.gridy = 1; + panel.add(countField, gbc); + + JLabel hintLabel = new JLabel("(建议10-30题)", JLabel.CENTER); + hintLabel.setFont(new Font("微软雅黑", Font.ITALIC, 12)); + hintLabel.setForeground(Color.GRAY); + gbc.gridy = 2; + panel.add(hintLabel, gbc); + + JButton startButton = createStyledButton("开始答题", new Color(60, 179, 113)); + startButton.addActionListener(e -> { + try { + int count = Integer.parseInt(countField.getText()); + if (count >= 10 && count <= 30) { + generateExamQuestions(count); + cardLayout.show(mainPanel, "Exam"); + } else { + JOptionPane.showMessageDialog(panel, "请输入10-30之间的有效题目数量"); + } + } catch (NumberFormatException ex) { + JOptionPane.showMessageDialog(panel, "请输入有效的数字"); + } + }); + gbc.gridy = 3; + panel.add(startButton, gbc); + + mainPanel.add(panel, "QuestionCount"); + } + + private void createExamPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + panel.setBackground(new Color(240, 248, 255)); + + questionLabel = new JLabel("", JLabel.CENTER); + questionLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + questionLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 20, 10)); + panel.add(questionLabel, BorderLayout.NORTH); + + JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 15, 15)); + optionsPanel.setBorder(BorderFactory.createEmptyBorder(20, 50, 20, 50)); + optionsPanel.setBackground(new Color(240, 248, 255)); + + 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, 16)); + optionButtons[i].setBackground(new Color(240, 248, 255)); + optionGroup.add(optionButtons[i]); + optionsPanel.add(optionButtons[i]); + } + + panel.add(optionsPanel, BorderLayout.CENTER); + + JButton submitButton = createStyledButton("提交答案", new Color(70, 130, 180)); + submitButton.addActionListener(e -> handleAnswerSubmission()); + panel.add(submitButton, BorderLayout.SOUTH); + + mainPanel.add(panel, "Exam"); + } + + private void createScorePanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(40, 40, 40, 40)); + panel.setBackground(new Color(240, 248, 255)); + + JLabel titleLabel = new JLabel("答题完成", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 28)); + titleLabel.setForeground(new Color(0, 100, 200)); + panel.add(titleLabel, BorderLayout.NORTH); + + scoreLabel = new JLabel("", JLabel.CENTER); + scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 36)); + scoreLabel.setForeground(new Color(220, 20, 60)); + panel.add(scoreLabel, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new FlowLayout()); + + JButton continueButton = createStyledButton("继续做题", new Color(60, 179, 113)); + continueButton.addActionListener(e -> cardLayout.show(mainPanel, "LevelSelection")); + buttonPanel.add(continueButton); + + JButton exitButton = createStyledButton("退出登录", new Color(205, 92, 92)); + exitButton.addActionListener(e -> { + currentUser = null; + cardLayout.show(mainPanel, "Login"); + }); + buttonPanel.add(exitButton); + + panel.add(buttonPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "Score"); + } + + private void createChangePasswordPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(10, 10, 10, 10); + 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; + panel.add(titleLabel, gbc); + + gbc.gridwidth = 1; + gbc.gridy = 1; gbc.gridx = 0; + panel.add(new JLabel("用户名:"), gbc); + + gbc.gridx = 1; + JTextField changePasswordEmailField = new JTextField(20); + panel.add(changePasswordEmailField, gbc); + + gbc.gridy = 2; gbc.gridx = 0; + panel.add(new JLabel("原密码:"), gbc); + + gbc.gridx = 1; + JPasswordField oldPasswordField = new JPasswordField(20); + panel.add(oldPasswordField, gbc); + + gbc.gridy = 3; gbc.gridx = 0; + panel.add(new JLabel("新密码:"), gbc); + + gbc.gridx = 1; + JPasswordField newPasswordField = new JPasswordField(20); + panel.add(newPasswordField, gbc); + + gbc.gridy = 4; gbc.gridx = 0; + panel.add(new JLabel("确认新密码:"), gbc); + + gbc.gridx = 1; + JPasswordField confirmNewPasswordField = new JPasswordField(20); + panel.add(confirmNewPasswordField, gbc); + + gbc.gridy = 5; gbc.gridx = 0; gbc.gridwidth = 2; + JButton changePasswordButton = createStyledButton("修改密码", new Color(218, 165, 32)); + changePasswordButton.addActionListener(e -> { + String username = changePasswordEmailField.getText(); + String oldPassword = new String(oldPasswordField.getPassword()); + String newPassword = new String(newPasswordField.getPassword()); + String confirmNewPassword = new String(confirmNewPasswordField.getPassword()); + + if (username.isEmpty() || oldPassword.isEmpty() || newPassword.isEmpty() || confirmNewPassword.isEmpty()) { + JOptionPane.showMessageDialog(panel, "请填写所有信息"); + return; + } + + if (!newPassword.equals(confirmNewPassword)) { + JOptionPane.showMessageDialog(panel, "新密码和确认密码不匹配"); + return; + } + + if (!validatePasswordFormat(newPassword)) { + JOptionPane.showMessageDialog(panel, "密码必须是6-10位,包含大小写字母和数字"); + return; + } + + if (userManager.updatePasswordByUsername(username, oldPassword, newPassword)) { + JOptionPane.showMessageDialog(panel, "密码修改成功"); + cardLayout.show(mainPanel, "Login"); + } else { + JOptionPane.showMessageDialog(panel, "用户名或原密码错误"); + } + }); + panel.add(changePasswordButton, gbc); + + gbc.gridy = 6; + JButton backButton = createStyledButton("返回登录", new Color(169, 169, 169)); + backButton.addActionListener(e -> cardLayout.show(mainPanel, "Login")); + panel.add(backButton, gbc); + + mainPanel.add(panel, "ChangePassword"); + } + + // 事件处理方法 + private void handleLogin() { + String username = emailField.getText(); + String password = new String(passwordField.getPassword()); + + if (username.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(mainPanel, "请填写用户名和密码"); + return; + } + + currentUser = userManager.login(username, password); + if (currentUser != null) { + welcomeLabel.setText("欢迎, " + currentUser.getUsername() + "!"); + JOptionPane.showMessageDialog(mainPanel, "登录成功!"); + cardLayout.show(mainPanel, "LevelSelection"); + } else { + JOptionPane.showMessageDialog(mainPanel, "用户名或密码错误"); + } + } + + private void startExam(String level) { + mainPanel.putClientProperty("currentLevel", level); + cardLayout.show(mainPanel, "QuestionCount"); + } + + private void generateExamQuestions(int count) { + String level = (String) mainPanel.getClientProperty("currentLevel"); + QuestionGenerator generator; + + switch (level) { + case "小学": generator = new PrimaryQuestionGenerator(); break; + case "初中": generator = new JuniorQuestionGenerator(); break; + case "高中": generator = new SeniorQuestionGenerator(); break; + default: generator = new PrimaryQuestionGenerator(); + } + + currentExam = new ArrayList<>(); + Set generatedQuestions = new HashSet<>(); + + for (int i = 0; i < count; i++) { + Question question; + int attempts = 0; + do { + question = generator.generateQuestion(); + attempts++; + } while (generatedQuestions.contains(question.getContent()) && attempts < 10); + + if (attempts < 10) { + currentExam.add(question); + generatedQuestions.add(question.getContent()); + } + } + + currentQuestionIndex = 0; + score = 0; + + // 开始考试时重置选项状态 + resetOptionButtons(); + displayCurrentQuestion(); + } + + private void displayCurrentQuestion() { + if (currentQuestionIndex < currentExam.size()) { + Question question = currentExam.get(currentQuestionIndex); + questionLabel.setText("
题目 " + (currentQuestionIndex + 1) + "/" + + currentExam.size() + "
" + question.getContent() + "
"); + + String[] options = question.getOptions(); + for (int i = 0; i < 4; i++) { + optionButtons[i].setText((char)('A' + i) + ". " + options[i]); + } + + // 重置选项状态 + resetOptionButtons(); + } + } + + private void resetOptionButtons() { + if (optionGroup != null) { + optionGroup.clearSelection(); + } + if (optionButtons != null) { + for (JRadioButton button : optionButtons) { + if (button != null) { + button.setSelected(false); + } + } + } + } + + private void handleAnswerSubmission() { + int selectedIndex = -1; + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isSelected()) { + selectedIndex = i; + break; + } + } + + if (selectedIndex == -1) { + JOptionPane.showMessageDialog(mainPanel, "请选择一个答案"); + return; + } + + Question currentQuestion = currentExam.get(currentQuestionIndex); + if (selectedIndex == currentQuestion.getCorrectAnswer()) { + score++; + } + + currentQuestionIndex++; + + if (currentQuestionIndex < currentExam.size()) { + displayCurrentQuestion(); + } else { + showScore(); + } + } + + private void showScore() { + int totalQuestions = currentExam.size(); + int percentScore = (int) Math.round((double) score / totalQuestions * 100); + + String comment; + Color color; + if (percentScore >= 90) { + comment = "优秀!"; + color = new Color(0, 128, 0); + } else if (percentScore >= 80) { + comment = "良好!"; + color = new Color(0, 100, 0); + } else if (percentScore >= 60) { + comment = "及格!"; + color = new Color(218, 165, 32); + } else { + comment = "加油!"; + color = new Color(220, 20, 60); + } + + scoreLabel.setText("
" + + "得分: " + percentScore + "分
" + + "(" + score + "/" + totalQuestions + ")
" + + "" + comment + "
"); + cardLayout.show(mainPanel, "Score"); + } + + // 工具方法 + private JButton createStyledButton(String text, Color color) { + JButton button = new JButton(text); + button.setFont(new Font("微软雅黑", 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 JButton createLevelButton(String text, Color color) { + JButton button = new JButton(text); + button.setFont(new Font("微软雅黑", Font.BOLD, 18)); + 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 String getHexColor(Color color) { + return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue()); + } + + private boolean isValidEmail(String email) { + String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; + Pattern pattern = Pattern.compile(emailRegex); + return pattern.matcher(email).matches(); + } + + private boolean validatePassword(String password, String confirmPassword) { + if (!password.equals(confirmPassword)) { + JOptionPane.showMessageDialog(mainPanel, "两次输入的密码不一致"); + return false; + } + + return validatePasswordFormat(password); + } + + private boolean validatePasswordFormat(String password) { + if (password.length() < 6 || password.length() > 10) { + JOptionPane.showMessageDialog(mainPanel, "密码长度应为6-10位"); + return false; + } + + boolean hasUpper = false, hasLower = false, 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; + } + + if (!hasUpper || !hasLower || !hasDigit) { + JOptionPane.showMessageDialog(mainPanel, "密码必须包含大小写字母和数字"); + return false; + } + + return true; + } + + private String generateVerificationCode() { + Random random = new Random(); + return String.format("%06d", random.nextInt(1000000)); + } + + public static void main(String[] args) { + try { + UIManager.setLookAndFeel(UIManager.getLookAndFeel()); + } catch (Exception e) { + e.printStackTrace(); + } + + SwingUtilities.invokeLater(() -> new MathLearningApp()); + } +} \ No newline at end of file diff --git a/src/user_data/users.dat b/src/user_data/users.dat new file mode 100644 index 0000000..209d73f Binary files /dev/null and b/src/user_data/users.dat differ diff --git a/src/users.txt b/src/users.txt new file mode 100644 index 0000000..3cdf169 --- /dev/null +++ b/src/users.txt @@ -0,0 +1,3 @@ +luguo:2830398107@qq.com:Qi1234 +测试用户:test@test.com:Test123 +示例用户:user@example.com:User123