柳意 1.修改出题逻辑——QuestionGenerator

huangsihui_branch
柳意 2 months ago
commit a196139e9a

29
.gitignore vendored

@ -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

8
.idea/.gitignore vendored

@ -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

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoogleJavaFormatSettings">
<option name="enabled" value="true" />
</component>
</project>

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="sun.mail.javax" type="repository">
<properties maven-id="com.sun.mail:javax.mail:1.6.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/sun/mail/javax.mail/1.6.2/javax.mail-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/javax/activation/activation/1.1/activation-1.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" project-jdk-name="23" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/MathLearningSoftware.iml" filepath="$PROJECT_DIR$/MathLearningSoftware.iml" />
</modules>
</component>
</project>

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="sun.mail.javax" level="project" />
</component>
</module>

@ -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

@ -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

@ -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);
});
}
}

@ -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;
}
}
}

@ -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("导航到分数界面");
}
}

@ -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<Question> 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<Question> getCurrentQuestions() {
return currentQuestions;
}
}

@ -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;
}
}

@ -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<Question> generateQuestions(String level, int count) {
List<Question> questions = new ArrayList<>();
Set<String> 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<Integer> operands = new ArrayList<>();
List<Character> 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<Integer> operands, List<Character> 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<Integer> operands, List<Character> operators, int parenPos) {
// 先计算括号内的值
int parenResult = calculate(operands.get(parenPos), operands.get(parenPos + 1), operators.get(parenPos));
// 构建新的操作数和运算符列表
List<Integer> newOperands = new ArrayList<>();
List<Character> 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<Integer> 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<Integer> 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<String> 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;
}
}
}

@ -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;
}
}

@ -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<String, RegistrationCodeInfo> 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;
}
}

@ -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;
}
}
}

@ -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<User> 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<User> loadUsers() {
List<User> 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());
}
}
}

@ -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);
}
}

@ -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();
}
}

@ -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);
}
}
}
}

@ -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();
}
}

@ -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();
}
}

@ -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<Question> 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(
"<html><div style='text-align: center;'>"
+ currentQuestion.getQuestionText()
+ "</div></html>");
String[] options = currentQuestion.getOptions();
for (int i = 0; i < 4; i++) {
String optionText =
String.format(
"<html><div style='padding: 5px;'>%c. %s</div></html>", (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<Question> 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();
}
}

@ -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);
}
}

@ -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<Question> allQuestions;
private LevelSelectionFrame parentFrame;
public ScoreFrame(
QuestionController questionController,
int[] userSelections,
List<Question> 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();
}
}
Loading…
Cancel
Save