合并到develop #5

Merged
hnu202326010321 merged 11 commits from chenchenghuan_branch into develop 4 months ago

@ -0,0 +1,21 @@
import controller.NavigationController;
import javax.swing.UIManager;
import javax.swing.SwingUtilities;
public class MathLearningApp {
public static void main(String[] args) {
// Set system look and feel
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
// Start the application
SwingUtilities.invokeLater(() -> {
// 获取 NavigationController 实例并显示登录界面
NavigationController navigationController = NavigationController.getInstance();
navigationController.showLoginView();
});
}
}

@ -0,0 +1,41 @@
package controller;
import javax.swing.JOptionPane;
public abstract class BaseController {
protected void validateNotNull(Object obj, String fieldName) {
if (obj == null) {
throw new IllegalArgumentException(fieldName + "不能为空");
}
}
protected void validateStringNotEmpty(String str, String fieldName) {
if (str == null || str.trim().isEmpty()) {
throw new IllegalArgumentException(fieldName + "不能为空");
}
}
protected void validateStringLength(String str, String fieldName, int min, int max) {
validateStringNotEmpty(str, fieldName);
if (str.length() < min || str.length() > max) {
throw new IllegalArgumentException(fieldName + "长度应在" + min + "-" + max + "之间");
}
}
protected void showError(String message) {
JOptionPane.showMessageDialog(null, message, "错误", JOptionPane.ERROR_MESSAGE);
}
protected void showSuccess(String message) {
JOptionPane.showMessageDialog(null, message, "成功", JOptionPane.INFORMATION_MESSAGE);
}
protected void logInfo(String message) {
//System.out.println(" " + message);
}
protected void logError(String message) {
System.err.println("❌ " + message);
}
}

@ -0,0 +1,127 @@
package controller;
import model.User;
import view.LoginView;
import view.RegisterView;
import view.MainView;
import view.ExamView;
import view.ResultView;
public class NavigationController extends BaseController implements NavigationService {
private static NavigationController instance;
private LoginView loginView;
private RegisterView registerView;
private MainView mainView;
private ExamView examView;
private ResultView resultView;
private User currentUser;
// 单例模式
public static NavigationController getInstance() {
if (instance == null) {
instance = new NavigationController();
}
return instance;
}
private NavigationController() {
// 私有构造函数
}
@Override
public void showLoginView() {
if (loginView == null) {
loginView = new LoginView();
}
loginView.setVisible(true);
hideAllExcept(loginView);
logInfo("显示登录界面");
}
@Override
public void showRegisterView() {
if (registerView == null) {
registerView = new RegisterView();
}
registerView.setVisible(true);
hideAllExcept(registerView);
logInfo("显示注册界面");
}
@Override
public void showMainView(User user) {
setCurrentUser(user);
if (mainView != null) {
mainView.dispose();
}
mainView = new MainView(user);
mainView.setVisible(true);
hideAllExcept(mainView);
logInfo("显示主界面 - 用户: " + user.getUsername());
}
@Override
public void showMainView() {
if (currentUser != null) {
showMainView(currentUser);
} else {
showLoginView();
}
}
@Override
public void showExamView(String difficulty, int questionCount) {
try {
validateStringNotEmpty(difficulty, "难度");
if (questionCount <= 0) {
throw new IllegalArgumentException("题目数量必须大于0");
}
if (examView == null) {
examView = new ExamView();
}
examView.startNewExam(difficulty, questionCount);
examView.setVisible(true);
hideAllExcept(examView);
logInfo("显示考试界面 - " + difficulty + "难度, " + questionCount + "道题");
} catch (Exception e) {
logError("显示考试界面异常: " + e.getMessage());
showError("无法开始考试: " + e.getMessage());
}
}
@Override
public void showResultView(int score, int total, double percentage) {
try {
if (resultView == null) {
resultView = new ResultView();
}
resultView.setResults(score, total, percentage);
resultView.setVisible(true);
hideAllExcept(resultView);
logInfo("显示成绩界面 - 得分: " + score + "/" + total);
} catch (Exception e) {
logError("显示成绩界面异常: " + e.getMessage());
}
}
@Override
public void setCurrentUser(User user) {
this.currentUser = user;
logInfo("设置当前用户: " + (user != null ? user.getUsername() : "null"));
}
@Override
public User getCurrentUser() {
return currentUser;
}
private void hideAllExcept(javax.swing.JFrame visibleFrame) {
javax.swing.JFrame[] frames = {loginView, registerView, mainView, examView, resultView};
for (javax.swing.JFrame frame : frames) {
if (frame != null && frame != visibleFrame) {
frame.setVisible(false);
}
}
}
}

@ -0,0 +1,14 @@
package controller;
import model.User;
public interface NavigationService {
void showLoginView();
void showRegisterView();
void showMainView(User user);
void showMainView();
void showExamView(String difficulty, int questionCount);
void showResultView(int score, int total, double percentage);
void setCurrentUser(User user);
User getCurrentUser();
}

@ -0,0 +1,117 @@
package controller;
import model.Question;
import model.QuestionGenerator;
import utils.FileUtil;
import java.util.List;
import java.util.Arrays;
public class QuestionController extends BaseController implements QuestionService {
private QuestionGenerator generator = new QuestionGenerator();
private List<Question> currentQuestions;
private int currentQuestionIndex = 0;
private int score = 0;
private int[] userAnswers;
private String currentDifficulty;
private int currentQuestionCount;
@Override
public void startNewExam(String difficulty, int questionCount) {
try {
validateStringNotEmpty(difficulty, "难度");
if (questionCount <= 0 || questionCount > 100) {
throw new IllegalArgumentException("题目数量必须在1-100之间");
}
currentQuestions = generator.generateQuestions(questionCount, difficulty);
currentQuestionIndex = 0;
score = 0;
userAnswers = new int[questionCount];
Arrays.fill(userAnswers, -1);
currentDifficulty = difficulty;
currentQuestionCount = questionCount;
FileUtil.saveCurrentExam(currentQuestions, difficulty, questionCount);
logInfo("生成新试卷: " + difficulty + " 难度, " + questionCount + " 道题");
} catch (Exception e) {
logError("开始新考试异常: " + e.getMessage());
throw e;
}
}
@Override
public String getCurrentExamInfo() {
if (currentDifficulty != null && currentQuestionCount > 0) {
return currentDifficulty + "难度 - " + currentQuestionCount + "道题";
}
return "暂无试卷";
}
@Override
public Question getCurrentQuestion() {
if (currentQuestions == null || currentQuestionIndex >= currentQuestions.size()) {
return null;
}
return currentQuestions.get(currentQuestionIndex);
}
@Override
public void submitAnswer(int answer) {
if (currentQuestionIndex < userAnswers.length) {
userAnswers[currentQuestionIndex] = answer;
logInfo("提交答案: 第" + getCurrentQuestionNumber() + "题, 答案: " + answer);
}
}
@Override
public boolean nextQuestion() {
currentQuestionIndex++;
boolean hasNext = currentQuestionIndex < currentQuestions.size();
logInfo("下一题: " + (hasNext ? "有" : "无"));
return hasNext;
}
@Override
public boolean previousQuestion() {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
logInfo("上一题: 第" + getCurrentQuestionNumber() + "题");
return true;
}
return false;
}
@Override
public int calculateScore() {
score = 0;
for (int i = 0; i < currentQuestions.size(); i++) {
if (userAnswers[i] != -1 && currentQuestions.get(i).isCorrect(userAnswers[i])) {
score++;
}
}
logInfo("计算得分: " + score + "/" + currentQuestions.size());
return score;
}
@Override
public double getPercentage() {
double percentage = (double) score / currentQuestions.size() * 100;
logInfo("正确率: " + String.format("%.1f%%", percentage));
return percentage;
}
@Override
public int getCurrentQuestionNumber() {
return currentQuestionIndex + 1;
}
@Override
public int getTotalQuestions() {
return currentQuestions != null ? currentQuestions.size() : 0;
}
@Override
public int getUserAnswerForCurrentQuestion() {
return userAnswers[currentQuestionIndex];
}
}

@ -0,0 +1,17 @@
package controller;
import model.Question;
public interface QuestionService {
void startNewExam(String difficulty, int questionCount);
Question getCurrentQuestion();
void submitAnswer(int answer);
boolean nextQuestion();
boolean previousQuestion();
int calculateScore();
double getPercentage();
int getCurrentQuestionNumber();
int getTotalQuestions();
int getUserAnswerForCurrentQuestion();
String getCurrentExamInfo();
}

@ -0,0 +1,156 @@
package controller;
import model.User;
import utils.FileUtil;
import utils.ValidationUtil;
import utils.EmailUtil;
public class UserController extends BaseController implements UserService {
@Override
public String registerUser(String username, String email) {
try {
validateStringLength(username, "用户名", 2, 20);
validateStringNotEmpty(email, "邮箱");
if (!ValidationUtil.isValidEmail(email)) {
return "邮箱格式不正确";
}
if (FileUtil.usernameExists(username)) {
return "用户名已被使用";
}
if (FileUtil.emailExists(email)) {
return "该邮箱已被注册";
}
String registrationCode = EmailUtil.generateRegistrationCode();
User user = new User(username, email, registrationCode);
boolean sendSuccess = EmailUtil.sendRegistrationCode(email, username, registrationCode);
if (sendSuccess) {
FileUtil.saveUser(user);
logInfo("用户注册成功: " + username);
return "注册码已发送到您的邮箱,请查收!";
} else {
return "邮件发送失败,请检查邮箱地址或稍后重试";
}
} catch (IllegalArgumentException e) {
return e.getMessage();
} catch (Exception e) {
logError("用户注册异常: " + e.getMessage());
return "系统错误,请稍后重试";
}
}
@Override
public String completeRegistration(String username, String code, String password, String confirmPassword) {
try {
validateStringNotEmpty(username, "用户名");
validateStringNotEmpty(code, "注册码");
validateStringNotEmpty(password, "密码");
validateStringNotEmpty(confirmPassword, "确认密码");
User user = FileUtil.loadUserByUsername(username);
if (user == null) {
return "用户不存在或注册码未发送";
}
if (!user.getRegistrationCode().equals(code)) {
return "注册码不正确";
}
if (!password.equals(confirmPassword)) {
return "两次输入的密码不一致";
}
if (!ValidationUtil.isValidPassword(password)) {
return "密码必须为6-10位且包含大小写字母和数字";
}
user.setPassword(password);
user.setRegistered(true);
FileUtil.saveUser(user);
logInfo("用户完成注册: " + username);
return "注册成功!";
} catch (Exception e) {
logError("完成注册异常: " + e.getMessage());
return "系统错误,请稍后重试";
}
}
@Override
public User login(String loginId, String password) {
try {
validateStringNotEmpty(loginId, "登录ID");
validateStringNotEmpty(password, "密码");
User user;
if (loginId.contains("@")) {
user = FileUtil.loadUserByEmail(loginId);
} else {
user = FileUtil.loadUserByUsername(loginId);
}
if (user == null) {
logInfo("登录失败: 用户不存在 - " + loginId);
return null;
}
if (user.getPassword() == null || !user.getPassword().equals(password)) {
logInfo("登录失败: 密码错误 - " + loginId);
return null;
}
logInfo("登录成功: " + user.getUsername());
return user;
} catch (Exception e) {
logError("登录异常: " + e.getMessage());
return null;
}
}
@Override
public String changePassword(User user, String oldPassword, String newPassword, String confirmPassword) {
try {
validateNotNull(user, "用户");
validateStringNotEmpty(oldPassword, "原密码");
validateStringNotEmpty(newPassword, "新密码");
validateStringNotEmpty(confirmPassword, "确认密码");
if (!user.getPassword().equals(oldPassword)) {
return "原密码不正确";
}
if (!newPassword.equals(confirmPassword)) {
return "两次输入的新密码不一致";
}
if (!ValidationUtil.isValidPassword(newPassword)) {
return "新密码必须为6-10位且包含大小写字母和数字";
}
user.setPassword(newPassword);
FileUtil.saveUser(user);
logInfo("密码修改成功: " + user.getUsername());
return "密码修改成功";
} catch (Exception e) {
logError("修改密码异常: " + e.getMessage());
return "系统错误,请稍后重试";
}
}
@Override
public void cleanupUnregisteredUsers() {
try {
FileUtil.cleanupUnregisteredUsers();
logInfo("执行未注册用户清理");
} catch (Exception e) {
logError("清理未注册用户异常: " + e.getMessage());
}
}
}

@ -0,0 +1,11 @@
package controller;
import model.User;
public interface UserService {
String registerUser(String username, String email);
String completeRegistration(String username, String code, String password, String confirmPassword);
User login(String loginId, String password);
String changePassword(User user, String oldPassword, String newPassword, String confirmPassword);
void cleanupUnregisteredUsers();
}

@ -0,0 +1,36 @@
package model;
import java.io.Serializable;
public class Question implements Serializable {
private static final long serialVersionUID = 1L;
private String question;
private String[] options;
private int correctAnswer;
private String difficulty;
public Question(String question, String[] options, int correctAnswer, String difficulty) {
this.question = question;
this.options = options;
this.correctAnswer = correctAnswer;
this.difficulty = difficulty;
}
// Getters
public String getQuestion() { return question; }
public String[] getOptions() { return options; }
public boolean isCorrect(int selectedAnswer) {
return selectedAnswer == correctAnswer;
}
@Override
public String toString() {
return "Question{" +
"question='" + question + '\'' +
", difficulty='" + difficulty + '\'' +
'}';
}
}

@ -0,0 +1,282 @@
package model;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Random;
import java.text.DecimalFormat;
public class QuestionGenerator {
private Random random = new Random();
private DecimalFormat df = new DecimalFormat("#.##"); // 保留两位小数的格式化器
public List<Question> generateQuestions(int count, String difficulty) {
List<Question> questions = new ArrayList<>();
Set<String> existingQuestions = new HashSet<>();
while (questions.size() < count) {
Question question = generateQuestion(difficulty);
String questionKey = question.getQuestion() + Arrays.toString(question.getOptions());
if (!existingQuestions.contains(questionKey)) {
questions.add(question);
existingQuestions.add(questionKey);
}
}
return questions;
}
private Question generateQuestion(String difficulty) {
switch (difficulty) {
case "小学":
return generatePrimaryQuestion();
case "初中":
return generateMiddleSchoolQuestion();
case "高中":
return generateHighSchoolQuestion();
default:
return generatePrimaryQuestion();
}
}
private Question generatePrimaryQuestion() {
int num1 = random.nextInt(50) + 1;
int num2 = random.nextInt(50) + 1;
int num3 = random.nextInt(50) + 1;
char[] operators = {'+', '-', '*', '/'};
char operator1 = operators[random.nextInt(operators.length)];
char operator2 = operators[random.nextInt(operators.length)];
String question;
double correctAnswer;
// 随机决定是否使用括号
if (random.nextBoolean()) {
// 有括号的情况
switch (random.nextInt(3)) {
case 0: // (a op b) op c
correctAnswer = calculate(calculate(num1, num2, operator1), num3, operator2);
question = "(" + num1 + " " + getOperatorSymbol(operator1) + " " + num2 + ") " +
getOperatorSymbol(operator2) + " " + num3 + " = ?";
break;
case 1: // a op (b op c)
correctAnswer = calculate(num1, calculate(num2, num3, operator2), operator1);
question = num1 + " " + getOperatorSymbol(operator1) + " (" + num2 + " " +
getOperatorSymbol(operator2) + " " + num3 + ") = ?";
break;
default: // 无括号
correctAnswer = calculate(calculate(num1, num2, operator1), num3, operator2);
question = num1 + " " + getOperatorSymbol(operator1) + " " + num2 + " " +
getOperatorSymbol(operator2) + " " + num3 + " = ?";
break;
}
} else {
if (operator1 == '/') {
num2 = random.nextInt(10) + 1; // 避免除零
}
correctAnswer = calculate(num1, num2, operator1);
question = num1 + " " + getOperatorSymbol(operator1) + " " + num2 + " = ?";
}
correctAnswer = Double.parseDouble(df.format(correctAnswer));
String[] options = generateOptions(correctAnswer);
return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "小学");
}
private Question generateMiddleSchoolQuestion() {
String question;
double correctAnswer;
if (random.nextBoolean()) {
// 平方或平方根题目
int num = random.nextInt(20) + 1;
if (random.nextBoolean()) {
correctAnswer = Math.pow(num, 2);
question = num + "² = ?";
} else {
int squared = num * num;
correctAnswer = num;
question = "√" + squared + " = ?";
}
} else {
// 带加减乘除的题目
int num1 = random.nextInt(50) + 1;
int num2 = random.nextInt(50) + 1;
int num3 = random.nextInt(20) + 1;
char[] operators = {'+', '-', '*', '/'};
char operator1 = operators[random.nextInt(operators.length)];
char operator2 = operators[random.nextInt(operators.length)];
// 随机组合平方、平方根和基本运算
if (random.nextBoolean()) {
// 平方与基本运算组合
correctAnswer = calculate(Math.pow(num1, 2), num2, operator1);
question = num1 + "² " + getOperatorSymbol(operator1) + " " + num2 + " = ?";
} else {
// 平方根与基本运算组合
int squared = num3 * num3;
correctAnswer = calculate(num3, num2, operator1);
question = "√" + squared + " " + getOperatorSymbol(operator1) + " " + num2 + " = ?";
}
}
correctAnswer = Double.parseDouble(df.format(correctAnswer));
String[] options = generateOptions(correctAnswer);
return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "初中");
}
private Question generateHighSchoolQuestion() {
if (random.nextBoolean()) {
return HighSchoolQuestionOne();
} else {
// 三角函数与基本运算组合
return HighSchoolQuestionTwo();
}
}
private Question HighSchoolQuestionOne() {
String question;
double correctAnswer;
double angle = random.nextInt(360);
String[] trigFunctions = {"sin", "cos", "tan"};
String trigFunction = trigFunctions[random.nextInt(trigFunctions.length)];
switch (trigFunction) {
case "sin":
correctAnswer = Math.sin(Math.toRadians(angle));
question = "sin(" + angle + "°) = ?";
break;
case "cos":
correctAnswer = Math.cos(Math.toRadians(angle));
question = "cos(" + angle + "°) = ?";
break;
case "tan":
angle = getSafeTanAngle();
correctAnswer = Math.tan(Math.toRadians(angle));
question = "tan(" + angle + "°) = ?";
break;
default:
correctAnswer = 0;
question = "";
}
correctAnswer = Double.parseDouble(df.format(correctAnswer));
String[] options = generateOptions(correctAnswer);
return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "高中");
}
private Question HighSchoolQuestionTwo() {
String question;
double correctAnswer;
double angle = random.nextInt(360);
int num = random.nextInt(20) + 1;
char[] operators = {'+', '-', '*', '/'};
char operator = operators[random.nextInt(operators.length)];
String[] trigFunctions = {"sin", "cos", "tan"};
String trigFunction = trigFunctions[random.nextInt(trigFunctions.length)];
double trigValue;
switch (trigFunction) {
case "sin":
trigValue = Math.sin(Math.toRadians(angle));
question = "sin(" + angle + "°) " + getOperatorSymbol(operator) + " " + num + " = ?";
break;
case "cos":
trigValue = Math.cos(Math.toRadians(angle));
question = "cos(" + angle + "°) " + getOperatorSymbol(operator) + " " + num + " = ?";
break;
case "tan":
angle = getSafeTanAngle();
trigValue = Math.tan(Math.toRadians(angle));
question = "tan(" + angle + "°) " + getOperatorSymbol(operator) + " " + num + " = ?";
break;
default:
trigValue = 0;
question = "";
}
trigValue = Double.parseDouble(df.format(trigValue));
correctAnswer = calculate(trigValue, num, operator);
String[] options = generateOptions(correctAnswer);
return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "高中");
}
// 获取安全的tan角度更严格的范围
private double getSafeTanAngle() {
// 为tan函数提供更安全的角度范围避免无限大的值
// 排除85°-95°, 265°-275° 等接近90°和270°的角度
List<Double> safeTanAngles = new ArrayList<>();
for (int i = 0; i < 360; i++) {
if (isSafeForTan(i)) {
safeTanAngles.add((double) i);
}
}
return safeTanAngles.get(random.nextInt(safeTanAngles.size()));
}
// 检查角度是否对tan函数安全更严格的条件
private boolean isSafeForTan(double angle) {
// 排除接近90°、270°的角度以及排除tan值过大的角度
return !((angle >= 80 && angle <= 100) ||
(angle >= 260 && angle <= 280) ||
(angle >= 170 && angle <= 190) ||
(angle >= 350 || angle <= 10));
}
// 计算方法
private double calculate(double a, double b, char operator) {
switch (operator) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return b != 0 ? a / b : a; // 避免除零
default:
return 0;
}
}
// 获取运算符符号
private String getOperatorSymbol(char operator) {
switch (operator) {
case '+':
return "+";
case '-':
return "-";
case '*':
return "×";
case '/':
return "÷";
default:
return "";
}
}
private String[] generateOptions(double correctAnswer) {
String[] options = new String[4];
int correctIndex = random.nextInt(4);
for (int i = 0; i < 4; i++) {
if (i == correctIndex) {
options[i] = df.format(correctAnswer);
} else {
double wrongAnswer;
do {
double variation = (random.nextDouble() - 0.5) * 10;
wrongAnswer = correctAnswer + variation;
wrongAnswer = Double.parseDouble(df.format(wrongAnswer));
} while (Math.abs(wrongAnswer - correctAnswer) < 0.1);
options[i] = String.valueOf(wrongAnswer);
}
}
return options;
}
private int getCorrectOptionIndex(String[] options, double correctAnswer) {
for (int i = 0; i < options.length; i++) {
if (Double.parseDouble(options[i]) == correctAnswer) {
return i;
}
}
return 0;
}
}

@ -0,0 +1,30 @@
package model;
public class User {
private String username; // 新增:用户名
private String email;
private String password;
private String registrationCode;
private boolean isRegistered;
public User(String username, String email, String registrationCode) {
this.username = username;
this.email = email;
this.registrationCode = registrationCode;
this.isRegistered = false;
}
// Getters and Setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getRegistrationCode() { return registrationCode; }
public boolean isRegistered() { return isRegistered; }
public void setRegistered(boolean registered) { isRegistered = registered; }
}

@ -0,0 +1,111 @@
package utils;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
import java.util.Random;
public class EmailUtil {
private static final Random random = new Random();
// SMTP服务器配置 - 请修改为您的邮箱配置
private static final String SMTP_HOST = "smtp.qq.com"; // QQ邮箱SMTP
private static final String SMTP_PORT = "587"; // 端口
private static final String FROM_EMAIL = "2536082954@qq.com"; // 发件邮箱
private static final String EMAIL_PASSWORD = "uihuasdsosbvdiia"; // 授权码
private static final String FROM_NAME = "数学学习软件";
public static String generateRegistrationCode() {
return String.format("%06d", random.nextInt(1000000));
}
/**
*
*/
public static boolean sendRegistrationCode(String userEmail, String username, String code) {
// 配置邮件服务器
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.port", SMTP_PORT);
// 超时设置
props.put("mail.smtp.timeout", "10000");
props.put("mail.smtp.connectiontimeout", "10000");
// 创建认证器
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(FROM_EMAIL, EMAIL_PASSWORD);
}
};
Session session = Session.getInstance(props, authenticator);
try {
// 创建邮件
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(FROM_EMAIL, FROM_NAME, "UTF-8"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(userEmail));
message.setSubject("数学学习软件 - 注册验证码", "UTF-8");
// 邮件内容HTML格式
String htmlContent = buildEmailContent(username, code);
message.setContent(htmlContent, "text/html;charset=UTF-8");
// 发送邮件
Transport.send(message);
System.out.println("✅ 注册码已成功发送到: " + userEmail);
return true;
} catch (Exception e) {
System.err.println("❌ 邮件发送失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
*
*/
private static String buildEmailContent(String username, String code) {
return "<!DOCTYPE html>" +
"<html>" +
"<head>" +
" <meta charset=\"UTF-8\">" +
" <style>" +
" body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }" +
" .container { max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }" +
" .header { color: #1890ff; text-align: center; margin-bottom: 30px; }" +
" .code-box { background: #f0f8ff; padding: 20px; text-align: center; margin: 25px 0; border-radius: 8px; border: 2px dashed #1890ff; }" +
" .code { font-size: 32px; font-weight: bold; color: #ff4d4f; letter-spacing: 5px; }" +
" .footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; color: #666; font-size: 12px; }" +
" </style>" +
"</head>" +
"<body>" +
" <div class=\"container\">" +
" <div class=\"header\">" +
" <h1>数学学习软件 - 注册验证码</h1>" +
" </div>" +
" <p>亲爱的 <strong>" + username + "</strong>,您好!</p>" +
" <p>您正在注册数学学习软件,请使用以下验证码完成注册:</p>" +
" <div class=\"code-box\">" +
" <div class=\"code\">" + code + "</div>" +
" </div>" +
" <p>如果这不是您的操作,请忽略此邮件。</p>" +
" <div class=\"footer\">" +
" <p>此为系统邮件,请勿回复</p>" +
" <p>数学学习软件团队</p>" +
" </div>" +
" </div>" +
"</body>" +
"</html>";
}
}

@ -0,0 +1,200 @@
package utils;
import model.Question;
import model.User;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class FileUtil {
private static final String USERS_FILE = "data/users.json";
private static final String CURRENT_EXAM_FILE = "data/current_exam.json";
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
static {
new File("data").mkdirs();
}
/**
*
*/
public static void cleanupUnregisteredUsers() {
try {
Map<String, User> users = loadAllUsers();
boolean hasChanges = false;
long currentTime = System.currentTimeMillis();
// 使用迭代器安全删除
Iterator<Map.Entry<String, User>> iterator = users.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, User> entry = iterator.next();
User user = entry.getValue();
// 删除条件:未设置密码 或 未完成注册 且 创建时间超过24小时
if ((user.getPassword() == null || user.getPassword().isEmpty() || !user.isRegistered())) {
iterator.remove();
hasChanges = true;
System.out.println("清理未注册用户: " + user.getUsername() + " (" + user.getEmail() + ")");
}
}
if (hasChanges) {
saveAllUsers(users);
} else {
System.out.println("没有需要清理的未注册用户");
}
} catch (Exception e) {
System.err.println("清理未注册用户时出错: " + e.getMessage());
e.printStackTrace();
}
}
public static void saveAllUsers(Map<String, User> users) {
try (FileWriter writer = new FileWriter(USERS_FILE)) {
gson.toJson(users, writer);
} catch (IOException e) {
System.err.println("保存用户数据失败: " + e.getMessage());
}
}
// 用户相关方法保持不变...
public static void saveUser(User user) {
try {
Map<String, User> users = loadAllUsers();
users.put(user.getUsername(), user);
saveAllUsers(users);
} catch (Exception e) {
System.err.println("保存用户数据失败: " + e.getMessage());
}
}
public static Map<String, User> loadAllUsers() {
try {
File file = new File(USERS_FILE);
if (!file.exists()) {
return new HashMap<>();
}
try (FileReader reader = new FileReader(file)) {
Type type = new TypeToken<Map<String, User>>(){}.getType();
Map<String, User> users = gson.fromJson(reader, type);
// 修复现有数据中的问题
if (users != null) {
for (Map.Entry<String, User> entry : users.entrySet()) {
User user = entry.getValue();
// 确保用户名不为空
if (user.getUsername() == null) {
user.setUsername(entry.getKey());
}
// 确保密码不为null
if (user.getPassword() == null) {
user.setPassword("");
}
}
}
return users != null ? users : new HashMap<>();
}
} catch (IOException e) {
System.err.println("加载用户数据失败: " + e.getMessage());
return new HashMap<>();
}
}
public static User loadUserByUsername(String username) {
Map<String, User> users = loadAllUsers();
return users.get(username);
}
public static User loadUserByEmail(String email) {
Map<String, User> users = loadAllUsers();
for (User user : users.values()) {
if (user.getEmail().equals(email)) {
return user;
}
}
return null;
}
public static boolean usernameExists(String username) {
return loadAllUsers().containsKey(username);
}
public static boolean emailExists(String email) {
return loadUserByEmail(email) != null;
}
// ========== 试卷保存相关方法 ==========
/**
*
*/
public static void saveCurrentExam(List<Question> questions, String difficulty, int questionCount) {
try {
ExamData examData = new ExamData(questions, difficulty, questionCount, new Date());
try (FileWriter writer = new FileWriter(CURRENT_EXAM_FILE)) {
gson.toJson(examData, writer);
}
System.out.println("✅ 试卷已保存到本地: " + CURRENT_EXAM_FILE);
} catch (IOException e) {
System.err.println("保存试卷失败: " + e.getMessage());
}
}
/**
*
*/
public static ExamData loadCurrentExam() {
try {
File file = new File(CURRENT_EXAM_FILE);
if (!file.exists()) {
return null;
}
try (FileReader reader = new FileReader(file)) {
return gson.fromJson(reader, ExamData.class);
}
} catch (IOException e) {
System.err.println("加载试卷失败: " + e.getMessage());
return null;
}
}
/**
*
*/
public static class ExamData {
private List<Question> questions;
private String difficulty;
private int questionCount;
private Date generateTime;
public ExamData(List<Question> questions, String difficulty, int questionCount, Date generateTime) {
this.questions = questions;
this.difficulty = difficulty;
this.questionCount = questionCount;
this.generateTime = generateTime;
}
// Getters
public List<Question> getQuestions() { return questions; }
public String getDifficulty() { return difficulty; }
public int getQuestionCount() { return questionCount; }
public Date getGenerateTime() { return generateTime; }
}
}

@ -0,0 +1,39 @@
package utils;
import java.util.regex.Pattern;
public class ValidationUtil {
public static boolean isValidEmail(String email) {
String emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$";
return Pattern.compile(emailRegex).matcher(email).matches();
}
public static boolean isValidPassword(String password) {
// 6-10位必须包含大小写字母和数字
if (password.length() < 6 || password.length() > 10) {
return false;
}
boolean hasUpperCase = false;
boolean hasLowerCase = false;
boolean hasDigit = false;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) hasUpperCase = true;
if (Character.isLowerCase(c)) hasLowerCase = true;
if (Character.isDigit(c)) hasDigit = true;
}
return hasUpperCase && hasLowerCase && hasDigit;
}
public static boolean isNumeric(String str) {
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

@ -0,0 +1,131 @@
package view;
import controller.UserController;
import model.User;
import utils.ValidationUtil;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JButton;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.Font;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChangePasswordDialog extends JDialog {
private User currentUser;
private UserController userController;
private JPasswordField oldPasswordField;
private JPasswordField newPasswordField;
private JPasswordField confirmPasswordField;
public ChangePasswordDialog(JFrame parent, User user) {
super(parent, "修改密码", true);
this.currentUser = user;
this.userController = new UserController();
initializeUI();
}
private void initializeUI() {
setSize(400, 300);
setLocationRelativeTo(getParent());
setResizable(false);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Title
JLabel titleLabel = new JLabel("修改密码", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Form panel
JPanel formPanel = new JPanel(new GridLayout(4, 2, 10, 10));
JLabel oldPasswordLabel = new JLabel("原密码:");
oldPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
oldPasswordField = new JPasswordField();
JLabel newPasswordLabel = new JLabel("新密码:");
newPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
newPasswordField = new JPasswordField();
JLabel confirmPasswordLabel = new JLabel("确认新密码:");
confirmPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
confirmPasswordField = new JPasswordField();
JButton confirmButton = new JButton("确认修改");
confirmButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
JButton cancelButton = new JButton("取消");
cancelButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
formPanel.add(oldPasswordLabel);
formPanel.add(oldPasswordField);
formPanel.add(newPasswordLabel);
formPanel.add(newPasswordField);
formPanel.add(confirmPasswordLabel);
formPanel.add(confirmPasswordField);
formPanel.add(confirmButton);
formPanel.add(cancelButton);
mainPanel.add(formPanel, BorderLayout.CENTER);
// 密码要求提示
JLabel hintLabel = new JLabel(
"<html><body style='text-align: center'>" +
"密码要求6-10位必须包含大小写字母和数字<br>" +
"例如Abc123、Test456" +
"</body></html>", JLabel.CENTER);
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
hintLabel.setForeground(Color.GRAY);
mainPanel.add(hintLabel, BorderLayout.SOUTH);
// Add action listeners
confirmButton.addActionListener(new ConfirmAction());
cancelButton.addActionListener(e -> dispose());
// Enter key support
oldPasswordField.addActionListener(new ConfirmAction());
newPasswordField.addActionListener(new ConfirmAction());
confirmPasswordField.addActionListener(new ConfirmAction());
add(mainPanel);
}
private class ConfirmAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
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(ChangePasswordDialog.this,
"请填写所有密码字段", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
// 调用控制器修改密码
String result = userController.changePassword(currentUser, oldPassword, newPassword, confirmPassword);
if (result.equals("密码修改成功")) {
JOptionPane.showMessageDialog(ChangePasswordDialog.this,
"密码修改成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
dispose(); // 关闭对话框
} else {
JOptionPane.showMessageDialog(ChangePasswordDialog.this,
result, "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
}

@ -0,0 +1,281 @@
package view;
import controller.NavigationController;
import controller.NavigationService;
import controller.QuestionController;
import model.Question;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JRadioButton;
import javax.swing.JButton;
import javax.swing.ButtonGroup;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class ExamView extends JFrame {
private QuestionController questionController;
private NavigationService navigationService;
private JLabel questionLabel;
private JLabel progressLabel;
private ButtonGroup optionGroup;
private JRadioButton[] optionButtons;
private JButton prevButton;
private JButton nextButton;
private JButton submitButton;
private JLabel hintLabel;
public ExamView() {
questionController = new QuestionController();
navigationService = NavigationController.getInstance();
initializeUI();
}
private void initializeUI() {
setTitle("数学学习软件 - 答题界面");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(600, 450);
setLocationRelativeTo(null);
setResizable(false);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Progress label
progressLabel = new JLabel("", JLabel.CENTER);
progressLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
mainPanel.add(progressLabel, BorderLayout.NORTH);
// Question panel
JPanel questionPanel = new JPanel(new BorderLayout(10, 10));
questionLabel = new JLabel("", JLabel.CENTER);
questionLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18));
questionPanel.add(questionLabel, BorderLayout.NORTH);
// Options panel
JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 10, 10));
optionGroup = new ButtonGroup();
optionButtons = new JRadioButton[4];
for (int i = 0; i < 4; i++) {
optionButtons[i] = new JRadioButton();
optionButtons[i].setFont(new Font("微软雅黑", Font.PLAIN, 14));
optionGroup.add(optionButtons[i]);
optionsPanel.add(optionButtons[i]);
optionButtons[i].addItemListener(new OptionSelectionListener());
}
questionPanel.add(optionsPanel, BorderLayout.CENTER);
mainPanel.add(questionPanel, BorderLayout.CENTER);
// Hint label
hintLabel = new JLabel("请选择答案后继续", JLabel.CENTER);
hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
hintLabel.setForeground(Color.RED);
hintLabel.setVisible(false);
mainPanel.add(hintLabel, BorderLayout.SOUTH);
// Button panel
JPanel buttonPanel = new JPanel(new FlowLayout());
prevButton = new JButton("上一题");
nextButton = new JButton("下一题");
submitButton = new JButton("提交试卷");
prevButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
nextButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
submitButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
nextButton.setEnabled(false);
buttonPanel.add(prevButton);
buttonPanel.add(nextButton);
buttonPanel.add(submitButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
// Add action listeners
prevButton.addActionListener(new PrevButtonAction());
nextButton.addActionListener(new NextButtonAction());
submitButton.addActionListener(new SubmitButtonAction());
add(mainPanel);
}
public void startNewExam(String difficulty, int questionCount) {
questionController.startNewExam(difficulty, questionCount);
displayCurrentQuestion();
updateNavigationButtons();
updateNextButtonState();
}
private void displayCurrentQuestion() {
Question question = questionController.getCurrentQuestion();
if (question == null) return;
progressLabel.setText(String.format("第 %d 题 / 共 %d 题",
questionController.getCurrentQuestionNumber(),
questionController.getTotalQuestions()));
questionLabel.setText(question.getQuestion());
String[] options = question.getOptions();
for (int i = 0; i < 4; i++) {
optionButtons[i].setText((char)('A' + i) + ". " + options[i]);
}
// Restore user's previous selection
int userAnswer = questionController.getUserAnswerForCurrentQuestion();
optionGroup.clearSelection();
if (userAnswer != -1) {
optionButtons[userAnswer].setSelected(true);
nextButton.setEnabled(canGoNext());
hintLabel.setVisible(false);
} else {
nextButton.setEnabled(false);
}
}
private void updateNavigationButtons() {
int current = questionController.getCurrentQuestionNumber();
int total = questionController.getTotalQuestions();
prevButton.setEnabled(current > 1);
// 修改这里:最后一题时禁用下一题按钮
boolean isLastQuestion = current == total;
nextButton.setEnabled(!isLastQuestion && isCurrentQuestionAnswered());
submitButton.setEnabled(isLastQuestion && isCurrentQuestionAnswered());
}
private void updateNextButtonState() {
boolean isLastQuestion = questionController.getCurrentQuestionNumber() == questionController.getTotalQuestions();
if (isLastQuestion) {
// 最后一题:禁用下一题,启用提交按钮(如果已回答)
nextButton.setEnabled(false);
submitButton.setEnabled(isCurrentQuestionAnswered());
} else {
// 不是最后一题:根据是否回答控制下一题按钮
nextButton.setEnabled(isCurrentQuestionAnswered());
submitButton.setEnabled(false);
}
hintLabel.setVisible(!isCurrentQuestionAnswered());
}
private boolean canGoNext() {
int current = questionController.getCurrentQuestionNumber();
int total = questionController.getTotalQuestions();
return current < total && isCurrentQuestionAnswered();
}
private boolean isCurrentQuestionAnswered() {
for (int i = 0; i < 4; i++) {
if (optionButtons[i].isSelected()) {
return true;
}
}
return false;
}
private void saveCurrentAnswer() {
for (int i = 0; i < 4; i++) {
if (optionButtons[i].isSelected()) {
questionController.submitAnswer(i);
break;
}
}
}
// 选项选择监听器
private class OptionSelectionListener implements ItemListener {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
saveCurrentAnswer(); // 立即保存答案
updateNextButtonState();
updateNavigationButtons();
}
}
}
private class PrevButtonAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
saveCurrentAnswer();
if (questionController.previousQuestion()) {
displayCurrentQuestion();
updateNavigationButtons();
updateNextButtonState();
}
}
}
private class NextButtonAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
int current = questionController.getCurrentQuestionNumber();
int total = questionController.getTotalQuestions();
// 检查是否是最后一题
if (current >= total) {
JOptionPane.showMessageDialog(ExamView.this,
"已经是最后一题了,请点击提交试卷!", "提示",
JOptionPane.INFORMATION_MESSAGE);
return;
}
if (!isCurrentQuestionAnswered()) {
JOptionPane.showMessageDialog(ExamView.this,
"请先选择答案!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
saveCurrentAnswer();
if (questionController.nextQuestion()) {
displayCurrentQuestion();
updateNavigationButtons();
updateNextButtonState();
} else {
// 如果无法切换到下一题,可能是最后一题
JOptionPane.showMessageDialog(ExamView.this,
"无法切换到下一题,请点击提交试卷!", "提示",
JOptionPane.INFORMATION_MESSAGE);
}
}
}
private class SubmitButtonAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (!isCurrentQuestionAnswered()) {
JOptionPane.showMessageDialog(ExamView.this,
"请先完成本题!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
saveCurrentAnswer();
int score = questionController.calculateScore();
int total = questionController.getTotalQuestions();
double percentage = questionController.getPercentage();
// 关闭当前窗口
dispose();
// 显示结果页面
navigationService.showResultView(score, total, percentage); // 修改
}
}
}

@ -0,0 +1,130 @@
package view;
import controller.NavigationController;
import controller.NavigationService;
import controller.UserController;
import controller.UserService;
import model.User;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import javax.swing.JButton;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class LoginView extends JFrame {
private JTextField usernameField;
private JPasswordField passwordField;
private UserService userController;
private NavigationService navigationService;
public LoginView() {
userController = new UserController();
navigationService = NavigationController.getInstance();
initializeUI();
cleanupUnregisteredUsers();
}
private void cleanupUnregisteredUsers() {
try {
userController.cleanupUnregisteredUsers();
} catch (Exception e) {
System.err.println("清理未注册用户时出错: " + e.getMessage());
}
}
private void initializeUI() {
setTitle("数学学习软件 - 登录");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 300);
setLocationRelativeTo(null);
setResizable(false);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Title
JLabel titleLabel = new JLabel("用户登录", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Form panel
JPanel formPanel = new JPanel(new GridLayout(3, 2, 10, 10));
JLabel usernameLabel = new JLabel("用户名:");
usernameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
usernameField = new JTextField();
JLabel passwordLabel = new JLabel("密码:");
passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
passwordField = new JPasswordField();
formPanel.add(usernameLabel);
formPanel.add(usernameField);
formPanel.add(passwordLabel);
formPanel.add(passwordField);
// Buttons
JPanel buttonPanel = new JPanel(new FlowLayout());
JButton loginButton = new JButton("登录");
JButton registerButton = new JButton("注册");
loginButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
registerButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
buttonPanel.add(loginButton);
buttonPanel.add(registerButton);
formPanel.add(new JLabel()); // Empty cell
formPanel.add(buttonPanel);
mainPanel.add(formPanel, BorderLayout.CENTER);
// Add action listeners
loginButton.addActionListener(new LoginAction());
registerButton.addActionListener(e -> {
navigationService.showRegisterView();
});
// Enter key support
usernameField.addActionListener(new LoginAction());
passwordField.addActionListener(new LoginAction());
add(mainPanel);
}
private class LoginAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String username = usernameField.getText().trim();
String password = new String(passwordField.getPassword());
if (username.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(LoginView.this,
"请输入用户名和密码", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
User user = userController.login(username, password);
if (user != null) {
JOptionPane.showMessageDialog(LoginView.this,
"登录成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
navigationService.showMainView(user);
} else {
JOptionPane.showMessageDialog(LoginView.this,
"用户名或密码错误", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
}

@ -0,0 +1,126 @@
package view;
import controller.NavigationController;
import controller.NavigationService;
import controller.UserController;
import model.User;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JComboBox;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainView extends JFrame {
private JComboBox<String> difficultyComboBox;
private JTextField countField;
private User currentUser; // 保存当前登录用户
private NavigationService navigationService;
public MainView(User user) {
this.currentUser = user;
this.navigationService = NavigationController.getInstance();
initializeUI();
}
private void initializeUI() {
setTitle("数学学习软件 - 主界面");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(450, 350);
setLocationRelativeTo(null);
setResizable(false);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Title - 显示欢迎信息
JLabel titleLabel = new JLabel("欢迎 " + currentUser.getUsername() + "", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Form panel
JPanel formPanel = new JPanel(new GridLayout(3, 2, 10, 10));
JLabel difficultyLabel = new JLabel("难度级别:");
difficultyLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
String[] difficulties = {"小学", "初中", "高中"};
difficultyComboBox = new JComboBox<>(difficulties);
difficultyComboBox.setFont(new Font("微软雅黑", Font.PLAIN, 14));
JLabel countLabel = new JLabel("题目数量:");
countLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
countField = new JTextField("10");
countField.setFont(new Font("微软雅黑", Font.PLAIN, 14));
JButton startButton = new JButton("开始答题");
startButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
formPanel.add(difficultyLabel);
formPanel.add(difficultyComboBox);
formPanel.add(countLabel);
formPanel.add(countField);
formPanel.add(new JLabel()); // Empty cell
formPanel.add(startButton);
mainPanel.add(formPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel(new FlowLayout());
JButton changePasswordButton = new JButton("修改密码");
JButton logoutButton = new JButton("退出登录");
changePasswordButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
logoutButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
buttonPanel.add(changePasswordButton);
buttonPanel.add(logoutButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
// Add action listeners
startButton.addActionListener(new StartExamAction());
changePasswordButton.addActionListener(new ChangePasswordAction());
logoutButton.addActionListener(e -> navigationService.showLoginView()); // 修改
add(mainPanel);
}
private class StartExamAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String difficulty = (String) difficultyComboBox.getSelectedItem();
String countText = countField.getText().trim();
try {
int count = Integer.parseInt(countText);
if (count < 10 || count > 30) {
JOptionPane.showMessageDialog(MainView.this,
"题目数量必须在10-30之间", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
navigationService.showExamView(difficulty, count); // 修改
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(MainView.this,
"请输入有效的数字", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
private class ChangePasswordAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// 打开修改密码对话框
new ChangePasswordDialog(MainView.this, currentUser).setVisible(true);
}
}
}

@ -0,0 +1,164 @@
package view;
import controller.NavigationController;
import controller.NavigationService;
import controller.UserController;
import model.User;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import javax.swing.JButton;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RegisterView extends JFrame {
private JTextField usernameField; // 用户名输入框
private JTextField emailField;
private JTextField codeField;
private JPasswordField passwordField;
private JPasswordField confirmPasswordField;
private UserController userController;
private NavigationService navigationService; // 新增
private String currentUsername; // 保存当前注册的用户名
public RegisterView() {
userController = new UserController();
navigationService = NavigationController.getInstance(); // 新增
initializeUI();
}
private void initializeUI() {
setTitle("数学学习软件 - 注册");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(550, 500);
setLocationRelativeTo(null);
setResizable(false);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Title
JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Form panel - 增加用户名字段
JPanel formPanel = new JPanel(new GridLayout(8, 2, 10, 10));
JLabel usernameLabel = new JLabel("用户名:");
usernameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
usernameField = new JTextField();
JLabel emailLabel = new JLabel("邮箱:");
emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
emailField = new JTextField();
JLabel codeLabel = new JLabel("注册码:");
codeLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
codeField = new JTextField();
JLabel passwordLabel = new JLabel("密码:");
passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
passwordField = new JPasswordField();
JLabel confirmPasswordLabel = new JLabel("确认密码:");
confirmPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
confirmPasswordField = new JPasswordField();
JButton sendCodeButton = new JButton("发送注册码");
sendCodeButton.setFont(new Font("微软雅黑", Font.PLAIN, 12));
JButton registerButton = new JButton("完成注册");
registerButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
// 布局表单
formPanel.add(usernameLabel);
formPanel.add(usernameField);
formPanel.add(emailLabel);
formPanel.add(emailField);
formPanel.add(sendCodeButton);
formPanel.add(new JLabel());
formPanel.add(codeLabel);
formPanel.add(codeField);
formPanel.add(passwordLabel);
formPanel.add(passwordField);
formPanel.add(confirmPasswordLabel);
formPanel.add(confirmPasswordField);
formPanel.add(registerButton);
mainPanel.add(formPanel, BorderLayout.CENTER);
// Back button
JButton backButton = new JButton("返回登录");
backButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
backButton.addActionListener(e -> navigationService.showLoginView()); // 修改
JPanel bottomPanel = new JPanel(new FlowLayout());
bottomPanel.add(backButton);
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
// Add action listeners
sendCodeButton.addActionListener(new SendCodeAction());
registerButton.addActionListener(new RegisterAction());
add(mainPanel);
}
private class SendCodeAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String username = usernameField.getText().trim();
String email = emailField.getText().trim();
if (username.isEmpty() || email.isEmpty()) {
JOptionPane.showMessageDialog(RegisterView.this,
"请输入用户名和邮箱", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
String result = userController.registerUser(username, email);
JOptionPane.showMessageDialog(RegisterView.this, result);
if (result.contains("发送")) {
currentUsername = username;
}
}
}
private class RegisterAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String username = usernameField.getText().trim();
String code = codeField.getText().trim();
String password = new String(passwordField.getPassword());
String confirmPassword = new String(confirmPasswordField.getPassword());
if (username.isEmpty() || code.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()) {
JOptionPane.showMessageDialog(RegisterView.this,
"请填写所有字段", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
String result = userController.completeRegistration(username, code, password, confirmPassword);
if (result.equals("注册成功!")) {
JOptionPane.showMessageDialog(RegisterView.this,
result, "成功", JOptionPane.INFORMATION_MESSAGE);
navigationService.showLoginView(); // 修改
} else {
JOptionPane.showMessageDialog(RegisterView.this,
result, "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
}

@ -0,0 +1,79 @@
package view;
import controller.NavigationController;
import controller.NavigationService;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.BorderFactory;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ResultView extends JFrame {
private JLabel scoreLabel;
private JLabel percentageLabel;
private NavigationService navigationService;
public ResultView() {
navigationService = NavigationController.getInstance(); // 新增
initializeUI();
}
private void initializeUI() {
setTitle("数学学习软件 - 成绩界面");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 300);
setLocationRelativeTo(null);
setResizable(false);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Title
JLabel titleLabel = new JLabel("考试结果", JLabel.CENTER);
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24));
mainPanel.add(titleLabel, BorderLayout.NORTH);
// Result panel
JPanel resultPanel = new JPanel(new GridLayout(2, 1, 10, 10));
scoreLabel = new JLabel("", JLabel.CENTER);
scoreLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18));
percentageLabel = new JLabel("", JLabel.CENTER);
percentageLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18));
resultPanel.add(scoreLabel);
resultPanel.add(percentageLabel);
mainPanel.add(resultPanel, BorderLayout.CENTER);
// Button panel
JPanel buttonPanel = new JPanel(new FlowLayout());
JButton continueButton = new JButton("继续做题");
JButton exitButton = new JButton("退出系统");
continueButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
exitButton.setFont(new Font("微软雅黑", Font.PLAIN, 14));
buttonPanel.add(continueButton);
buttonPanel.add(exitButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
// Add action listeners
continueButton.addActionListener(e -> navigationService.showMainView()); // 修改
exitButton.addActionListener(e -> System.exit(0));
add(mainPanel);
}
public void setResults(int score, int total, double percentage) {
scoreLabel.setText(String.format("得分: %d / %d", score, total));
percentageLabel.setText(String.format("正确率: %.1f%%", percentage));
}
}
Loading…
Cancel
Save