From b0f095ae99a4814f78cd8a073fce67d5b4a04842 Mon Sep 17 00:00:00 2001 From: wrh <16681308616@163.com> Date: Fri, 10 Oct 2025 14:48:53 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MathLearningApp.java | 19 +++ src/controller/NavigationController.java | 87 ++++++++++++ src/controller/QuestionController.java | 96 +++++++++++++ src/controller/UserController.java | 112 +++++++++++++++ src/model/Question.java | 37 +++++ src/model/QuestionGenerator.java | 154 +++++++++++++++++++++ src/model/User.java | 32 +++++ src/utils/EmailUtil.java | 107 ++++++++++++++ src/utils/FileUtil.java | 138 ++++++++++++++++++ src/utils/ValidationUtil.java | 39 ++++++ src/view/ChangePasswordDialog.java | 121 ++++++++++++++++ src/view/ExamView.java | 169 +++++++++++++++++++++++ src/view/LoginView.java | 106 ++++++++++++++ src/view/MainView.java | 113 +++++++++++++++ src/view/RegisterView.java | 151 ++++++++++++++++++++ src/view/ResultView.java | 69 +++++++++ 16 files changed, 1550 insertions(+) create mode 100644 src/MathLearningApp.java create mode 100644 src/controller/NavigationController.java create mode 100644 src/controller/QuestionController.java create mode 100644 src/controller/UserController.java create mode 100644 src/model/Question.java create mode 100644 src/model/QuestionGenerator.java create mode 100644 src/model/User.java create mode 100644 src/utils/EmailUtil.java create mode 100644 src/utils/FileUtil.java create mode 100644 src/utils/ValidationUtil.java create mode 100644 src/view/ChangePasswordDialog.java create mode 100644 src/view/ExamView.java create mode 100644 src/view/LoginView.java create mode 100644 src/view/MainView.java create mode 100644 src/view/RegisterView.java create mode 100644 src/view/ResultView.java diff --git a/src/MathLearningApp.java b/src/MathLearningApp.java new file mode 100644 index 0000000..3f0ec15 --- /dev/null +++ b/src/MathLearningApp.java @@ -0,0 +1,19 @@ +import controller.NavigationController; +import javax.swing.*; + +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.showLoginView(); + }); + } +} \ No newline at end of file diff --git a/src/controller/NavigationController.java b/src/controller/NavigationController.java new file mode 100644 index 0000000..f437625 --- /dev/null +++ b/src/controller/NavigationController.java @@ -0,0 +1,87 @@ +package controller; + +import model.User; +import view.*; + +public class NavigationController { + private static LoginView loginView; + private static RegisterView registerView; + private static MainView mainView; + private static ExamView examView; + private static ResultView resultView; + private static User currentUser; // 保存当前登录用户 + + public static void showLoginView() { + if (loginView == null) { + loginView = new LoginView(); + } + loginView.setVisible(true); + hideAllExcept(loginView); + } + + public static void showRegisterView() { + if (registerView == null) { + registerView = new RegisterView(); + } + registerView.setVisible(true); + hideAllExcept(registerView); + } + + // 修改:传递用户信息到主界面 + public static void showMainView(User user) { + currentUser = user; + if (mainView != null) { + mainView.dispose(); // 关闭旧的主界面 + } + mainView = new MainView(user); // 创建新的主界面 + mainView.setVisible(true); + hideAllExcept(mainView); + } + + // 新增:无参数版本,使用当前保存的用户 + public static void showMainView() { + if (currentUser != null) { + showMainView(currentUser); + } else { + // 如果没有保存的用户,返回登录界面 + showLoginView(); + } + } + + public static void showExamView(String difficulty, int questionCount) { + if (examView == null) { + examView = new ExamView(); + } + examView.startNewExam(difficulty, questionCount); + examView.setVisible(true); + hideAllExcept(examView); + } + + public static void showResultView(int score, int total, double percentage) { + if (resultView == null) { + resultView = new ResultView(); + } + resultView.setResults(score, total, percentage); + resultView.setVisible(true); + hideAllExcept(resultView); + } + + private static 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); + } + } + } + + // 新增:设置当前用户(在登录时调用) + public static void setCurrentUser(User user) { + currentUser = user; + } + + // 新增:获取当前用户 + public static User getCurrentUser() { + return currentUser; + } +} \ No newline at end of file diff --git a/src/controller/QuestionController.java b/src/controller/QuestionController.java new file mode 100644 index 0000000..8d5e14f --- /dev/null +++ b/src/controller/QuestionController.java @@ -0,0 +1,96 @@ +package controller; + +import model.Question; +import model.QuestionGenerator; +import utils.FileUtil; +import java.util.*; + +public class QuestionController { + private QuestionGenerator generator = new QuestionGenerator(); + private List currentQuestions; + private int currentQuestionIndex = 0; + private int score = 0; + private int[] userAnswers; + private String currentDifficulty; + private int currentQuestionCount; + + public void startNewExam(String difficulty, int questionCount) { + 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); + + System.out.println("🎯 生成新试卷: " + difficulty + " 难度, " + questionCount + " 道题"); + } + + + + /** + * 获取当前试卷信息 + */ + public String getCurrentExamInfo() { + if (currentDifficulty != null && currentQuestionCount > 0) { + return currentDifficulty + "难度 - " + currentQuestionCount + "道题"; + } + return "暂无试卷"; + } + + // 其他方法保持不变... + public Question getCurrentQuestion() { + if (currentQuestions == null || currentQuestionIndex >= currentQuestions.size()) { + return null; + } + return currentQuestions.get(currentQuestionIndex); + } + + public void submitAnswer(int answer) { + if (currentQuestionIndex < userAnswers.length) { + userAnswers[currentQuestionIndex] = answer; + } + } + + public boolean nextQuestion() { + currentQuestionIndex++; + return currentQuestionIndex < currentQuestions.size(); + } + + public boolean previousQuestion() { + if (currentQuestionIndex > 0) { + currentQuestionIndex--; + return true; + } + return false; + } + + public int calculateScore() { + score = 0; + for (int i = 0; i < currentQuestions.size(); i++) { + if (userAnswers[i] != -1 && currentQuestions.get(i).isCorrect(userAnswers[i])) { + score++; + } + } + return score; + } + + public double getPercentage() { + return (double) score / currentQuestions.size() * 100; + } + + public int getCurrentQuestionNumber() { + return currentQuestionIndex + 1; + } + + public int getTotalQuestions() { + return currentQuestions != null ? currentQuestions.size() : 0; + } + + public int getUserAnswerForCurrentQuestion() { + return userAnswers[currentQuestionIndex]; + } +} \ No newline at end of file diff --git a/src/controller/UserController.java b/src/controller/UserController.java new file mode 100644 index 0000000..409070f --- /dev/null +++ b/src/controller/UserController.java @@ -0,0 +1,112 @@ +package controller; + +import model.User; +import utils.*; + +public class UserController { + + /** + * 注册用户 - 发送注册码 + */ + public String registerUser(String username, String email) { + // 验证用户名 + if (username == null || username.trim().isEmpty()) { + return "用户名不能为空"; + } + if (username.length() < 2 || username.length() > 20) { + return "用户名长度应为2-20个字符"; + } + + // 验证邮箱 + 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); + return "注册码已发送到您的邮箱,请查收!"; + } else { + return "邮件发送失败,请检查邮箱地址或稍后重试"; + } + } + + /** + * 完成注册 - 设置密码 + */ + public String completeRegistration(String username, String code, String password, String 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); + + return "注册成功!"; + } + + /** + * 用户登录 - 使用用户名登录 + */ + public User login(String username, String password) { + User user = FileUtil.loadUserByUsername(username); + if (user != null && user.isRegistered() && user.getPassword().equals(password)) { + return user; + } + return null; + } + + /** + * 修改密码 + */ + public String changePassword(User user, String oldPassword, String newPassword, String 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); + return "密码修改成功"; + } + +} \ No newline at end of file diff --git a/src/model/Question.java b/src/model/Question.java new file mode 100644 index 0000000..59a9585 --- /dev/null +++ b/src/model/Question.java @@ -0,0 +1,37 @@ +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 int getCorrectAnswer() { return correctAnswer; } + public String getDifficulty() { return difficulty; } + + public boolean isCorrect(int selectedAnswer) { + return selectedAnswer == correctAnswer; + } + + @Override + public String toString() { + return "Question{" + + "question='" + question + '\'' + + ", difficulty='" + difficulty + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/model/QuestionGenerator.java b/src/model/QuestionGenerator.java new file mode 100644 index 0000000..54cd50c --- /dev/null +++ b/src/model/QuestionGenerator.java @@ -0,0 +1,154 @@ +package model; + +import java.util.*; + +public class QuestionGenerator { + private Random random = new Random(); + + public List generateQuestions(int count, String difficulty) { + List questions = new ArrayList<>(); + Set 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; + char[] operators = {'+', '-', '*', '/'}; + char operator = operators[random.nextInt(operators.length)]; + + String question; + double correctAnswer; + + switch (operator) { + case '+': + correctAnswer = num1 + num2; + question = num1 + " + " + num2 + " = ?"; + break; + case '-': + correctAnswer = num1 - num2; + question = num1 + " - " + num2 + " = ?"; + break; + case '*': + correctAnswer = num1 * num2; + question = num1 + " × " + num2 + " = ?"; + break; + case '/': + num2 = random.nextInt(10) + 1; // Avoid division by zero + correctAnswer = (double) num1 / num2; + question = num1 + " ÷ " + num2 + " = ?"; + break; + default: + correctAnswer = 0; + question = ""; + } + + String[] options = generateOptions(correctAnswer); + return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "小学"); + } + + private Question generateMiddleSchoolQuestion() { + int num = random.nextInt(20) + 1; + String question; + double correctAnswer; + + if (random.nextBoolean()) { + // Square question + correctAnswer = Math.pow(num, 2); + question = num + "² = ?"; + } else { + // Square root question + int squared = num * num; + correctAnswer = num; + question = "√" + squared + " = ?"; + } + + String[] options = generateOptions(correctAnswer); + return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "初中"); + } + + private Question generateHighSchoolQuestion() { + double angle = random.nextInt(360); + String[] trigFunctions = {"sin", "cos", "tan"}; + String trigFunction = trigFunctions[random.nextInt(trigFunctions.length)]; + + String question; + double correctAnswer; + + 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": + correctAnswer = Math.tan(Math.toRadians(angle)); + question = "tan(" + angle + "°) = ?"; + break; + default: + correctAnswer = 0; + question = ""; + } + + // Round to 2 decimal places + correctAnswer = Math.round(correctAnswer * 100.0) / 100.0; + String[] options = generateOptions(correctAnswer); + return new Question(question, options, getCorrectOptionIndex(options, correctAnswer), "高中"); + } + + 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] = String.valueOf(correctAnswer); + } else { + double wrongAnswer; + do { + double variation = (random.nextDouble() - 0.5) * 10; + wrongAnswer = Math.round((correctAnswer + variation) * 100.0) / 100.0; + } 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; + } +} \ No newline at end of file diff --git a/src/model/User.java b/src/model/User.java new file mode 100644 index 0000000..376df79 --- /dev/null +++ b/src/model/User.java @@ -0,0 +1,32 @@ +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 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; } +} \ No newline at end of file diff --git a/src/utils/EmailUtil.java b/src/utils/EmailUtil.java new file mode 100644 index 0000000..f6a84f0 --- /dev/null +++ b/src/utils/EmailUtil.java @@ -0,0 +1,107 @@ +package utils; + +import javax.mail.*; +import javax.mail.internet.*; +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 "" + + "" + + "" + + " " + + " " + + "" + + "" + + "
" + + "
" + + "

数学学习软件 - 注册验证码

" + + "
" + + "

亲爱的 " + username + ",您好!

" + + "

您正在注册数学学习软件,请使用以下验证码完成注册:

" + + "
" + + "
" + code + "
" + + "
" + + "

验证码有效期30分钟,请尽快完成注册。

" + + "

如果这不是您的操作,请忽略此邮件。

" + + "
" + + "

此为系统邮件,请勿回复

" + + "

数学学习软件团队

" + + "
" + + "
" + + "" + + ""; + } +} \ No newline at end of file diff --git a/src/utils/FileUtil.java b/src/utils/FileUtil.java new file mode 100644 index 0000000..c6717d5 --- /dev/null +++ b/src/utils/FileUtil.java @@ -0,0 +1,138 @@ +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.*; +import java.lang.reflect.Type; +import java.util.*; + +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 saveUser(User user) { + try { + Map users = loadAllUsers(); + users.put(user.getUsername(), user); + + try (FileWriter writer = new FileWriter(USERS_FILE)) { + gson.toJson(users, writer); + } + } catch (IOException e) { + System.err.println("保存用户数据失败: " + e.getMessage()); + } + } + + public static Map loadAllUsers() { + try { + File file = new File(USERS_FILE); + if (!file.exists()) { + return new HashMap<>(); + } + + try (FileReader reader = new FileReader(file)) { + Type type = new TypeToken>(){}.getType(); + Map users = gson.fromJson(reader, type); + return users != null ? users : new HashMap<>(); + } + } catch (IOException e) { + System.err.println("加载用户数据失败: " + e.getMessage()); + return new HashMap<>(); + } + } + + public static User loadUserByUsername(String username) { + Map users = loadAllUsers(); + return users.get(username); + } + + public static User loadUserByEmail(String email) { + Map 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 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 questions; + private String difficulty; + private int questionCount; + private Date generateTime; + + public ExamData(List questions, String difficulty, int questionCount, Date generateTime) { + this.questions = questions; + this.difficulty = difficulty; + this.questionCount = questionCount; + this.generateTime = generateTime; + } + + // Getters + public List getQuestions() { return questions; } + public String getDifficulty() { return difficulty; } + public int getQuestionCount() { return questionCount; } + public Date getGenerateTime() { return generateTime; } + } +} \ No newline at end of file diff --git a/src/utils/ValidationUtil.java b/src/utils/ValidationUtil.java new file mode 100644 index 0000000..2e3cbe2 --- /dev/null +++ b/src/utils/ValidationUtil.java @@ -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; + } + } +} diff --git a/src/view/ChangePasswordDialog.java b/src/view/ChangePasswordDialog.java new file mode 100644 index 0000000..37ec126 --- /dev/null +++ b/src/view/ChangePasswordDialog.java @@ -0,0 +1,121 @@ +package view; + +import controller.UserController; +import model.User; +import utils.ValidationUtil; + +import javax.swing.*; +import java.awt.*; +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( + "" + + "密码要求:6-10位,必须包含大小写字母和数字
" + + "例如:Abc123、Test456" + + "", 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); + } + } + } +} \ No newline at end of file diff --git a/src/view/ExamView.java b/src/view/ExamView.java new file mode 100644 index 0000000..71179a6 --- /dev/null +++ b/src/view/ExamView.java @@ -0,0 +1,169 @@ +package view; + +import controller.NavigationController; +import controller.QuestionController; +import model.Question; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ExamView extends JFrame { + private QuestionController questionController; + private JLabel questionLabel; + private JLabel progressLabel; + private ButtonGroup optionGroup; + private JRadioButton[] optionButtons; + private JButton prevButton; + private JButton nextButton; + private JButton submitButton; + + public ExamView() { + questionController = new QuestionController(); + initializeUI(); + } + + private void initializeUI() { + setTitle("数学学习软件 - 答题界面"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(600, 400); + 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]); + } + + questionPanel.add(optionsPanel, BorderLayout.CENTER); + mainPanel.add(questionPanel, BorderLayout.CENTER); + + // 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)); + + 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(); + } + + 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); + } + } + + private void updateNavigationButtons() { + int current = questionController.getCurrentQuestionNumber(); + int total = questionController.getTotalQuestions(); + + prevButton.setEnabled(current > 1); + nextButton.setEnabled(current < total); + submitButton.setEnabled(current == total); + } + + private void saveCurrentAnswer() { + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isSelected()) { + questionController.submitAnswer(i); + break; + } + } + } + + private class PrevButtonAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + saveCurrentAnswer(); + if (questionController.previousQuestion()) { + displayCurrentQuestion(); + updateNavigationButtons(); + } + } + } + + private class NextButtonAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + saveCurrentAnswer(); + if (questionController.nextQuestion()) { + displayCurrentQuestion(); + updateNavigationButtons(); + } + } + } + + private class SubmitButtonAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + saveCurrentAnswer(); + + int score = questionController.calculateScore(); + int total = questionController.getTotalQuestions(); + double percentage = questionController.getPercentage(); + + NavigationController.showResultView(score, total, percentage); + } + } + +} \ No newline at end of file diff --git a/src/view/LoginView.java b/src/view/LoginView.java new file mode 100644 index 0000000..87d9153 --- /dev/null +++ b/src/view/LoginView.java @@ -0,0 +1,106 @@ +package view; + +import controller.NavigationController; +import controller.UserController; +import model.User; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class LoginView extends JFrame { + private JTextField usernameField; // 新增:用户名字段 + private JPasswordField passwordField; + private UserController userController; + + public LoginView() { + userController = new UserController(); + 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); + + // 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 -> { + NavigationController.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); + // 修改:传递用户信息到主界面 + NavigationController.showMainView(user); + } else { + JOptionPane.showMessageDialog(LoginView.this, + "用户名或密码错误", "错误", JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/src/view/MainView.java b/src/view/MainView.java new file mode 100644 index 0000000..f200a13 --- /dev/null +++ b/src/view/MainView.java @@ -0,0 +1,113 @@ +package view; + +import controller.NavigationController; +import controller.UserController; +import model.User; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class MainView extends JFrame { + private JComboBox difficultyComboBox; + private JTextField countField; + private User currentUser; // 保存当前登录用户 + + public MainView(User user) { + this.currentUser = user; + 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); + + // Button panel - 添加修改密码按钮 + 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 -> NavigationController.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 <= 0 || count > 100) { + JOptionPane.showMessageDialog(MainView.this, + "题目数量必须在1-100之间", "错误", JOptionPane.ERROR_MESSAGE); + return; + } + + NavigationController.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); + } + } +} \ No newline at end of file diff --git a/src/view/RegisterView.java b/src/view/RegisterView.java new file mode 100644 index 0000000..43ba002 --- /dev/null +++ b/src/view/RegisterView.java @@ -0,0 +1,151 @@ +package view; + +import controller.NavigationController; +import controller.UserController; +import model.User; + +import javax.swing.*; +import java.awt.*; +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 String currentUsername; // 保存当前注册的用户名 + + public RegisterView() { + userController = new UserController(); + 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 -> NavigationController.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); + NavigationController.showLoginView(); + } else { + JOptionPane.showMessageDialog(RegisterView.this, + result, "错误", JOptionPane.ERROR_MESSAGE); + } + } + } +} \ No newline at end of file diff --git a/src/view/ResultView.java b/src/view/ResultView.java new file mode 100644 index 0000000..a1ac85f --- /dev/null +++ b/src/view/ResultView.java @@ -0,0 +1,69 @@ +package view; + +import controller.NavigationController; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ResultView extends JFrame { + private JLabel scoreLabel; + private JLabel percentageLabel; + + public ResultView() { + 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 -> NavigationController.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)); + } +} \ No newline at end of file -- 2.34.1 From 662fe0f708ea85951dd6e0c1c92ebd0cb279d524 Mon Sep 17 00:00:00 2001 From: wrh <16681308616@163.com> Date: Fri, 10 Oct 2025 15:54:42 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/QuestionGenerator.java | 207 ++++++++++++++++++++++++------- 1 file changed, 165 insertions(+), 42 deletions(-) diff --git a/src/model/QuestionGenerator.java b/src/model/QuestionGenerator.java index 54cd50c..bb08f73 100644 --- a/src/model/QuestionGenerator.java +++ b/src/model/QuestionGenerator.java @@ -1,10 +1,11 @@ package model; import java.util.*; +import java.text.DecimalFormat; public class QuestionGenerator { private Random random = new Random(); - + private DecimalFormat df = new DecimalFormat("#.##"); // 保留两位小数的格式化器 public List generateQuestions(int count, String difficulty) { List questions = new ArrayList<>(); Set existingQuestions = new HashSet<>(); @@ -38,66 +39,102 @@ public class QuestionGenerator { private Question generatePrimaryQuestion() { int num1 = random.nextInt(50) + 1; int num2 = random.nextInt(50) + 1; + int num3 = random.nextInt(50) + 1; char[] operators = {'+', '-', '*', '/'}; - char operator = operators[random.nextInt(operators.length)]; - + char operator1 = operators[random.nextInt(operators.length)]; + char operator2 = operators[random.nextInt(operators.length)]; String question; double correctAnswer; - - switch (operator) { - case '+': - correctAnswer = num1 + num2; - question = num1 + " + " + num2 + " = ?"; - break; - case '-': - correctAnswer = num1 - num2; - question = num1 + " - " + num2 + " = ?"; - break; - case '*': - correctAnswer = num1 * num2; - question = num1 + " × " + num2 + " = ?"; - break; - case '/': - num2 = random.nextInt(10) + 1; // Avoid division by zero - correctAnswer = (double) num1 / num2; - question = num1 + " ÷ " + num2 + " = ?"; - break; - default: - correctAnswer = 0; - question = ""; + // 随机决定是否使用括号 + 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() { - int num = random.nextInt(20) + 1; String question; double correctAnswer; if (random.nextBoolean()) { - // Square question - correctAnswer = Math.pow(num, 2); - question = num + "² = ?"; + // 平方或平方根题目 + 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 { - // Square root question - int squared = num * num; - correctAnswer = num; - question = "√" + squared + " = ?"; - } + // 带加减乘除的题目 + 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() { - double angle = random.nextInt(360); - String[] trigFunctions = {"sin", "cos", "tan"}; - String trigFunction = trigFunctions[random.nextInt(trigFunctions.length)]; + 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": @@ -109,6 +146,7 @@ public class QuestionGenerator { question = "cos(" + angle + "°) = ?"; break; case "tan": + angle = getSafeTanAngle(); correctAnswer = Math.tan(Math.toRadians(angle)); question = "tan(" + angle + "°) = ?"; break; @@ -116,12 +154,96 @@ public class QuestionGenerator { 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)]; - // Round to 2 decimal places - correctAnswer = Math.round(correctAnswer * 100.0) / 100.0; + 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 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]; @@ -129,12 +251,13 @@ public class QuestionGenerator { for (int i = 0; i < 4; i++) { if (i == correctIndex) { - options[i] = String.valueOf(correctAnswer); + options[i] = df.format(correctAnswer); } else { double wrongAnswer; do { double variation = (random.nextDouble() - 0.5) * 10; - wrongAnswer = Math.round((correctAnswer + variation) * 100.0) / 100.0; + wrongAnswer = correctAnswer + variation; + wrongAnswer = Double.parseDouble(df.format(wrongAnswer)); } while (Math.abs(wrongAnswer - correctAnswer) < 0.1); options[i] = String.valueOf(wrongAnswer); } -- 2.34.1 From 1624db0d9c922e52d7003e3fc501da818ebe7498 Mon Sep 17 00:00:00 2001 From: wrh <16681308616@163.com> Date: Fri, 10 Oct 2025 19:29:28 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=BC=98=E5=8C=96?= =?UTF-8?q?=EF=BC=8C=E6=9C=AA=E6=88=90=E5=8A=9F=E6=B3=A8=E5=86=8C=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/QuestionController.java | 11 --- src/controller/UserController.java | 34 ++++++-- src/utils/FileUtil.java | 81 +++++++++++++------ src/view/ExamView.java | 106 ++++++++++++++++++++++++- src/view/LoginView.java | 8 ++ src/view/MainView.java | 4 +- 6 files changed, 197 insertions(+), 47 deletions(-) diff --git a/src/controller/QuestionController.java b/src/controller/QuestionController.java index 8d5e14f..0e6cc1c 100644 --- a/src/controller/QuestionController.java +++ b/src/controller/QuestionController.java @@ -30,17 +30,6 @@ public class QuestionController { } - - /** - * 获取当前试卷信息 - */ - public String getCurrentExamInfo() { - if (currentDifficulty != null && currentQuestionCount > 0) { - return currentDifficulty + "难度 - " + currentQuestionCount + "道题"; - } - return "暂无试卷"; - } - // 其他方法保持不变... public Question getCurrentQuestion() { if (currentQuestions == null || currentQuestionIndex >= currentQuestions.size()) { diff --git a/src/controller/UserController.java b/src/controller/UserController.java index 409070f..b72e2e4 100644 --- a/src/controller/UserController.java +++ b/src/controller/UserController.java @@ -5,6 +5,11 @@ import utils.*; public class UserController { + + public void cleanuers(){ + + FileUtil.cleanupUnregisteredUsers(); + } /** * 注册用户 - 发送注册码 */ @@ -80,12 +85,31 @@ public class UserController { /** * 用户登录 - 使用用户名登录 */ - public User login(String username, String password) { - User user = FileUtil.loadUserByUsername(username); - if (user != null && user.isRegistered() && user.getPassword().equals(password)) { - return user; + public User login(String loginId, String password) { + // 判断是邮箱还是用户名 + User user; + if (loginId.contains("@")) { + // 如果是邮箱格式,按邮箱查找 + user = FileUtil.loadUserByEmail(loginId); + } else { + // 否则按用户名查找 + user = FileUtil.loadUserByUsername(loginId); } - return null; + + // 验证用户 + if (user == null) { + return null; // 用户不存在 + } + + if (!user.isRegistered()) { + return null; // 用户未完成注册 + } + + if (user.getPassword() == null || !user.getPassword().equals(password)) { + return null; // 密码错误 + } + + return user; // 登录成功 } /** diff --git a/src/utils/FileUtil.java b/src/utils/FileUtil.java index c6717d5..50e3f21 100644 --- a/src/utils/FileUtil.java +++ b/src/utils/FileUtil.java @@ -17,17 +17,52 @@ public class FileUtil { static { new File("data").mkdirs(); } + /** + * 清理未注册成功的用户(未设置密码的用户) + */ + public static void cleanupUnregisteredUsers() { + + try { + Map users = loadAllUsers(); + boolean hasChanges = false; + // 使用迭代器安全删除 + Iterator> iterator = users.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + User user = entry.getValue(); + // 删除条件:未设置密码 或 未完成注册 + if (user.getPassword() == null || user.getPassword().isEmpty() || !user.isRegistered()) { + iterator.remove(); + hasChanges = true; + System.out.println("清理未注册用户: " + user.getUsername() + " (" + user.getEmail() + ")"); + } + } + if (hasChanges) { + saveAllUsers(users); + System.out.println("未注册用户清理完成"); + } else { + System.out.println("没有需要清理的未注册用户"); + } + + }catch (Exception e) { + System.err.println("清理未注册用户时出错: " + e.getMessage()); + } + } + public static void saveAllUsers(Map 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 users = loadAllUsers(); users.put(user.getUsername(), user); - - try (FileWriter writer = new FileWriter(USERS_FILE)) { - gson.toJson(users, writer); - } - } catch (IOException e) { + saveAllUsers(users); + } catch (Exception e) { System.err.println("保存用户数据失败: " + e.getMessage()); } } @@ -42,6 +77,22 @@ public class FileUtil { try (FileReader reader = new FileReader(file)) { Type type = new TypeToken>(){}.getType(); Map users = gson.fromJson(reader, type); + + // 修复现有数据中的问题 + if (users != null) { + for (Map.Entry 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) { @@ -92,26 +143,6 @@ public class FileUtil { } } - /** - * 从本地文件加载当前试卷 - */ - 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; - } - } - - /** * 试卷数据包装类 diff --git a/src/view/ExamView.java b/src/view/ExamView.java index 71179a6..52d4051 100644 --- a/src/view/ExamView.java +++ b/src/view/ExamView.java @@ -8,6 +8,8 @@ import javax.swing.*; import java.awt.*; 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; @@ -18,6 +20,7 @@ public class ExamView extends JFrame { private JButton prevButton; private JButton nextButton; private JButton submitButton; + private JLabel hintLabel; public ExamView() { questionController = new QuestionController(); @@ -27,7 +30,7 @@ public class ExamView extends JFrame { private void initializeUI() { setTitle("数学学习软件 - 答题界面"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(600, 400); + setSize(600, 450); setLocationRelativeTo(null); setResizable(false); @@ -55,11 +58,20 @@ public class ExamView extends JFrame { 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("上一题"); @@ -70,6 +82,8 @@ public class ExamView extends JFrame { 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); @@ -88,6 +102,7 @@ public class ExamView extends JFrame { questionController.startNewExam(difficulty, questionCount); displayCurrentQuestion(); updateNavigationButtons(); + updateNextButtonState(); } private void displayCurrentQuestion() { @@ -110,6 +125,10 @@ public class ExamView extends JFrame { optionGroup.clearSelection(); if (userAnswer != -1) { optionButtons[userAnswer].setSelected(true); + nextButton.setEnabled(canGoNext()); + hintLabel.setVisible(false); + } else { + nextButton.setEnabled(false); } } @@ -118,8 +137,42 @@ public class ExamView extends JFrame { int total = questionController.getTotalQuestions(); prevButton.setEnabled(current > 1); - nextButton.setEnabled(current < total); - submitButton.setEnabled(current == total); + + // 修改这里:最后一题时禁用下一题按钮 + 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() { @@ -131,6 +184,18 @@ public class ExamView extends JFrame { } } + // 选项选择监听器 + 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) { @@ -138,6 +203,7 @@ public class ExamView extends JFrame { if (questionController.previousQuestion()) { displayCurrentQuestion(); updateNavigationButtons(); + updateNextButtonState(); } } } @@ -145,10 +211,33 @@ public class ExamView extends JFrame { 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); } } } @@ -156,14 +245,23 @@ public class ExamView extends JFrame { 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(); + + // 显示结果页面 NavigationController.showResultView(score, total, percentage); } } - } \ No newline at end of file diff --git a/src/view/LoginView.java b/src/view/LoginView.java index 87d9153..f5c498f 100644 --- a/src/view/LoginView.java +++ b/src/view/LoginView.java @@ -17,6 +17,14 @@ public class LoginView extends JFrame { public LoginView() { userController = new UserController(); initializeUI(); + cleanupUnregisteredUsers(); + } + private void cleanupUnregisteredUsers() { + try { + userController.cleanuers(); + } catch (Exception e) { + System.err.println("清理未注册用户时出错: " + e.getMessage()); + } } private void initializeUI() { diff --git a/src/view/MainView.java b/src/view/MainView.java index f200a13..e48a96e 100644 --- a/src/view/MainView.java +++ b/src/view/MainView.java @@ -89,9 +89,9 @@ public class MainView extends JFrame { try { int count = Integer.parseInt(countText); - if (count <= 0 || count > 100) { + if (count < 10 || count > 30) { JOptionPane.showMessageDialog(MainView.this, - "题目数量必须在1-100之间", "错误", JOptionPane.ERROR_MESSAGE); + "题目数量必须在10-30之间", "错误", JOptionPane.ERROR_MESSAGE); return; } -- 2.34.1 From 310e4265e585b3eab894ba614b78d549495741f5 Mon Sep 17 00:00:00 2001 From: wrh <16681308616@163.com> Date: Fri, 10 Oct 2025 21:36:01 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E7=BB=86=E8=8A=82=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MathLearningApp.java | 4 +-- src/controller/NavigationController.java | 16 ++++----- src/controller/QuestionController.java | 3 +- src/controller/UserController.java | 8 ++--- src/model/Question.java | 3 +- src/model/QuestionGenerator.java | 7 +++- src/model/User.java | 2 -- src/utils/EmailUtil.java | 18 +++++----- src/utils/FileUtil.java | 11 ++++-- src/view/ChangePasswordDialog.java | 14 ++++++-- src/view/ExamView.java | 15 +++++++-- src/view/LoginView.java | 14 ++++++-- src/view/MainView.java | 14 ++++++-- src/view/RegisterView.java | 43 ++++++++++++++++++------ src/view/ResultView.java | 11 ++++-- 15 files changed, 127 insertions(+), 56 deletions(-) diff --git a/src/MathLearningApp.java b/src/MathLearningApp.java index 3f0ec15..30fed84 100644 --- a/src/MathLearningApp.java +++ b/src/MathLearningApp.java @@ -1,6 +1,6 @@ import controller.NavigationController; -import javax.swing.*; - +import javax.swing.UIManager; +import javax.swing.SwingUtilities; public class MathLearningApp { public static void main(String[] args) { // Set system look and feel diff --git a/src/controller/NavigationController.java b/src/controller/NavigationController.java index f437625..31f2d7b 100644 --- a/src/controller/NavigationController.java +++ b/src/controller/NavigationController.java @@ -1,7 +1,12 @@ package controller; import model.User; -import view.*; +import view.LoginView; +import view.RegisterView; +import view.MainView; +import view.ExamView; +import view.ResultView; + public class NavigationController { private static LoginView loginView; @@ -75,13 +80,4 @@ public class NavigationController { } } - // 新增:设置当前用户(在登录时调用) - public static void setCurrentUser(User user) { - currentUser = user; - } - - // 新增:获取当前用户 - public static User getCurrentUser() { - return currentUser; - } } \ No newline at end of file diff --git a/src/controller/QuestionController.java b/src/controller/QuestionController.java index 0e6cc1c..cfa0331 100644 --- a/src/controller/QuestionController.java +++ b/src/controller/QuestionController.java @@ -3,7 +3,8 @@ package controller; import model.Question; import model.QuestionGenerator; import utils.FileUtil; -import java.util.*; +import java.util.List; +import java.util.Arrays; public class QuestionController { private QuestionGenerator generator = new QuestionGenerator(); diff --git a/src/controller/UserController.java b/src/controller/UserController.java index b72e2e4..ddc09ee 100644 --- a/src/controller/UserController.java +++ b/src/controller/UserController.java @@ -1,7 +1,9 @@ package controller; import model.User; -import utils.*; +import utils.FileUtil; +import utils.ValidationUtil; +import utils.EmailUtil; public class UserController { @@ -101,10 +103,6 @@ public class UserController { return null; // 用户不存在 } - if (!user.isRegistered()) { - return null; // 用户未完成注册 - } - if (user.getPassword() == null || !user.getPassword().equals(password)) { return null; // 密码错误 } diff --git a/src/model/Question.java b/src/model/Question.java index 59a9585..09e3faa 100644 --- a/src/model/Question.java +++ b/src/model/Question.java @@ -20,8 +20,7 @@ public class Question implements Serializable { // Getters public String getQuestion() { return question; } public String[] getOptions() { return options; } - public int getCorrectAnswer() { return correctAnswer; } - public String getDifficulty() { return difficulty; } + public boolean isCorrect(int selectedAnswer) { return selectedAnswer == correctAnswer; diff --git a/src/model/QuestionGenerator.java b/src/model/QuestionGenerator.java index bb08f73..442751c 100644 --- a/src/model/QuestionGenerator.java +++ b/src/model/QuestionGenerator.java @@ -1,6 +1,11 @@ package model; -import java.util.*; +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 { diff --git a/src/model/User.java b/src/model/User.java index 376df79..ade2948 100644 --- a/src/model/User.java +++ b/src/model/User.java @@ -19,13 +19,11 @@ public class User { public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } - public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRegistrationCode() { return registrationCode; } - public void setRegistrationCode(String registrationCode) { this.registrationCode = registrationCode; } public boolean isRegistered() { return isRegistered; } public void setRegistered(boolean registered) { isRegistered = registered; } diff --git a/src/utils/EmailUtil.java b/src/utils/EmailUtil.java index f6a84f0..a379926 100644 --- a/src/utils/EmailUtil.java +++ b/src/utils/EmailUtil.java @@ -1,7 +1,12 @@ package utils; -import javax.mail.*; -import javax.mail.internet.*; +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; @@ -29,11 +34,9 @@ public class EmailUtil { 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 @@ -41,25 +44,20 @@ public class EmailUtil { 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(); @@ -94,7 +92,7 @@ public class EmailUtil { "
" + "
" + code + "
" + "
" + - "

验证码有效期30分钟,请尽快完成注册。

" + + "

注册码直到系统退出前都有效,请尽快完成注册。

" + "

如果这不是您的操作,请忽略此邮件。

" + "
" + "

此为系统邮件,请勿回复

" + diff --git a/src/utils/FileUtil.java b/src/utils/FileUtil.java index 50e3f21..8992081 100644 --- a/src/utils/FileUtil.java +++ b/src/utils/FileUtil.java @@ -5,9 +5,16 @@ import model.User; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import java.io.*; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; import java.lang.reflect.Type; -import java.util.*; +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"; diff --git a/src/view/ChangePasswordDialog.java b/src/view/ChangePasswordDialog.java index 37ec126..8931b77 100644 --- a/src/view/ChangePasswordDialog.java +++ b/src/view/ChangePasswordDialog.java @@ -4,8 +4,18 @@ import controller.UserController; import model.User; import utils.ValidationUtil; -import javax.swing.*; -import java.awt.*; +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; diff --git a/src/view/ExamView.java b/src/view/ExamView.java index 52d4051..7638fc8 100644 --- a/src/view/ExamView.java +++ b/src/view/ExamView.java @@ -4,8 +4,19 @@ import controller.NavigationController; import controller.QuestionController; import model.Question; -import javax.swing.*; -import java.awt.*; +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; diff --git a/src/view/LoginView.java b/src/view/LoginView.java index f5c498f..47cc10d 100644 --- a/src/view/LoginView.java +++ b/src/view/LoginView.java @@ -4,8 +4,18 @@ import controller.NavigationController; import controller.UserController; import model.User; -import javax.swing.*; -import java.awt.*; +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; diff --git a/src/view/MainView.java b/src/view/MainView.java index e48a96e..1d0cf04 100644 --- a/src/view/MainView.java +++ b/src/view/MainView.java @@ -4,8 +4,18 @@ import controller.NavigationController; import controller.UserController; import model.User; -import javax.swing.*; -import java.awt.*; +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; diff --git a/src/view/RegisterView.java b/src/view/RegisterView.java index 43ba002..83ec84b 100644 --- a/src/view/RegisterView.java +++ b/src/view/RegisterView.java @@ -4,19 +4,26 @@ import controller.NavigationController; import controller.UserController; import model.User; -import javax.swing.*; +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.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class RegisterView extends JFrame { - private JTextField usernameField; // 新增:用户名输入框 + private JTextField usernameField; private JTextField emailField; private JTextField codeField; private JPasswordField passwordField; private JPasswordField confirmPasswordField; private UserController userController; - private String currentUsername; // 保存当前注册的用户名 + private String currentUsername; public RegisterView() { userController = new UserController(); @@ -26,7 +33,7 @@ public class RegisterView extends JFrame { private void initializeUI() { setTitle("数学学习软件 - 注册"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(550, 500); // 增加高度以容纳用户名字段 + setSize(550, 550); // 增加高度以容纳所有组件 setLocationRelativeTo(null); setResizable(false); @@ -38,8 +45,8 @@ public class RegisterView extends JFrame { titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); mainPanel.add(titleLabel, BorderLayout.NORTH); - // Form panel - 增加用户名字段 - JPanel formPanel = new JPanel(new GridLayout(8, 2, 10, 10)); + // Form panel + JPanel formPanel = new JPanel(new GridLayout(6, 2, 10, 10)); JLabel usernameLabel = new JLabel("用户名:"); usernameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); @@ -73,7 +80,7 @@ public class RegisterView extends JFrame { formPanel.add(emailLabel); formPanel.add(emailField); formPanel.add(sendCodeButton); - formPanel.add(new JLabel()); + formPanel.add(new JLabel()); // 空标签占位 formPanel.add(codeLabel); formPanel.add(codeField); formPanel.add(passwordLabel); @@ -81,17 +88,31 @@ public class RegisterView extends JFrame { formPanel.add(confirmPasswordLabel); formPanel.add(confirmPasswordField); - formPanel.add(registerButton); - mainPanel.add(formPanel, BorderLayout.CENTER); + // 创建底部面板,包含密码提示和返回按钮 + JPanel bottomPanel = new JPanel(new BorderLayout(10, 10)); + + // 密码要求提示 + JLabel hintLabel = new JLabel( + "" + + "密码要求:6-10位,必须包含大小写字母和数字
" + + "例如:Abc123、Test456" + + "", JLabel.CENTER); + hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + hintLabel.setForeground(Color.GRAY); + bottomPanel.add(hintLabel, BorderLayout.CENTER); + // Back button JButton backButton = new JButton("返回登录"); backButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); backButton.addActionListener(e -> NavigationController.showLoginView()); - JPanel bottomPanel = new JPanel(new FlowLayout()); - bottomPanel.add(backButton); + JPanel buttonPanel = new JPanel(new FlowLayout()); + buttonPanel.add(registerButton); // 将注册按钮移到这里 + buttonPanel.add(backButton); + bottomPanel.add(buttonPanel, BorderLayout.SOUTH); + mainPanel.add(bottomPanel, BorderLayout.SOUTH); // Add action listeners diff --git a/src/view/ResultView.java b/src/view/ResultView.java index a1ac85f..8987f5f 100644 --- a/src/view/ResultView.java +++ b/src/view/ResultView.java @@ -2,8 +2,15 @@ package view; import controller.NavigationController; -import javax.swing.*; -import java.awt.*; +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; -- 2.34.1 From c8540e33f45ba629aa675085db6f775a89aa1cc7 Mon Sep 17 00:00:00 2001 From: wrh <16681308616@163.com> Date: Fri, 10 Oct 2025 22:36:22 +0800 Subject: [PATCH 5/6] hebing --- src/MathLearningApp.java | 6 +- src/controller/BaseController.java | 41 ++++ src/controller/NavigationController.java | 106 ++++++++--- src/controller/NavigationService.java | 14 ++ src/controller/QuestionController.java | 59 ++++-- src/controller/QuestionService.java | 17 ++ src/controller/UserController.java | 232 +++++++++++++---------- src/controller/UserService.java | 11 ++ src/utils/EmailUtil.java | 8 +- src/utils/FileUtil.java | 36 +++- src/view/ExamView.java | 5 +- src/view/LoginView.java | 26 ++- src/view/MainView.java | 7 +- src/view/RegisterView.java | 46 ++--- src/view/ResultView.java | 5 +- 15 files changed, 419 insertions(+), 200 deletions(-) create mode 100644 src/controller/BaseController.java create mode 100644 src/controller/NavigationService.java create mode 100644 src/controller/QuestionService.java create mode 100644 src/controller/UserService.java diff --git a/src/MathLearningApp.java b/src/MathLearningApp.java index 30fed84..83d6d67 100644 --- a/src/MathLearningApp.java +++ b/src/MathLearningApp.java @@ -1,11 +1,11 @@ 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(); @@ -13,7 +13,9 @@ public class MathLearningApp { // Start the application SwingUtilities.invokeLater(() -> { - NavigationController.showLoginView(); + // 获取 NavigationController 实例并显示登录界面 + NavigationController navigationController = NavigationController.getInstance(); + navigationController.showLoginView(); }); } } \ No newline at end of file diff --git a/src/controller/BaseController.java b/src/controller/BaseController.java new file mode 100644 index 0000000..8f73b70 --- /dev/null +++ b/src/controller/BaseController.java @@ -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); + } +} diff --git a/src/controller/NavigationController.java b/src/controller/NavigationController.java index 31f2d7b..eb5c3fb 100644 --- a/src/controller/NavigationController.java +++ b/src/controller/NavigationController.java @@ -7,71 +7,116 @@ 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 class NavigationController { - private static LoginView loginView; - private static RegisterView registerView; - private static MainView mainView; - private static ExamView examView; - private static ResultView resultView; - private static User currentUser; // 保存当前登录用户 + // 单例模式 + public static NavigationController getInstance() { + if (instance == null) { + instance = new NavigationController(); + } + return instance; + } + + private NavigationController() { + // 私有构造函数 + } - public static void showLoginView() { + @Override + public void showLoginView() { if (loginView == null) { loginView = new LoginView(); } loginView.setVisible(true); hideAllExcept(loginView); + logInfo("显示登录界面"); } - public static void showRegisterView() { + @Override + public void showRegisterView() { if (registerView == null) { registerView = new RegisterView(); } registerView.setVisible(true); hideAllExcept(registerView); + logInfo("显示注册界面"); } - // 修改:传递用户信息到主界面 - public static void showMainView(User user) { - currentUser = user; + @Override + public void showMainView(User user) { + setCurrentUser(user); if (mainView != null) { - mainView.dispose(); // 关闭旧的主界面 + mainView.dispose(); } - mainView = new MainView(user); // 创建新的主界面 + mainView = new MainView(user); mainView.setVisible(true); hideAllExcept(mainView); + logInfo("显示主界面 - 用户: " + user.getUsername()); } - // 新增:无参数版本,使用当前保存的用户 - public static void showMainView() { + @Override + public void showMainView() { if (currentUser != null) { showMainView(currentUser); } else { - // 如果没有保存的用户,返回登录界面 showLoginView(); } } - public static void showExamView(String difficulty, int questionCount) { - if (examView == null) { - examView = new ExamView(); + @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()); } - examView.startNewExam(difficulty, questionCount); - examView.setVisible(true); - hideAllExcept(examView); } - public static void showResultView(int score, int total, double percentage) { - if (resultView == null) { - resultView = new ResultView(); + @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()); } - resultView.setResults(score, total, percentage); - resultView.setVisible(true); - hideAllExcept(resultView); } - private static void hideAllExcept(javax.swing.JFrame visibleFrame) { + @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) { @@ -79,5 +124,4 @@ public class NavigationController { } } } - } \ No newline at end of file diff --git a/src/controller/NavigationService.java b/src/controller/NavigationService.java new file mode 100644 index 0000000..53adf3a --- /dev/null +++ b/src/controller/NavigationService.java @@ -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(); +} diff --git a/src/controller/QuestionController.java b/src/controller/QuestionController.java index cfa0331..026081e 100644 --- a/src/controller/QuestionController.java +++ b/src/controller/QuestionController.java @@ -6,7 +6,7 @@ import utils.FileUtil; import java.util.List; import java.util.Arrays; -public class QuestionController { +public class QuestionController extends BaseController implements QuestionService { private QuestionGenerator generator = new QuestionGenerator(); private List currentQuestions; private int currentQuestionIndex = 0; @@ -15,23 +15,39 @@ public class QuestionController { private String currentDifficulty; private int currentQuestionCount; + @Override public void startNewExam(String difficulty, int questionCount) { - currentQuestions = generator.generateQuestions(questionCount, difficulty); - currentQuestionIndex = 0; - score = 0; - userAnswers = new int[questionCount]; - Arrays.fill(userAnswers, -1); - currentDifficulty = difficulty; - currentQuestionCount = questionCount; + try { + validateStringNotEmpty(difficulty, "难度"); + if (questionCount <= 0 || questionCount > 100) { + throw new IllegalArgumentException("题目数量必须在1-100之间"); + } - // 保存试卷到本地 - FileUtil.saveCurrentExam(currentQuestions, difficulty, questionCount); + currentQuestions = generator.generateQuestions(questionCount, difficulty); + currentQuestionIndex = 0; + score = 0; + userAnswers = new int[questionCount]; + Arrays.fill(userAnswers, -1); + currentDifficulty = difficulty; + currentQuestionCount = questionCount; - System.out.println("🎯 生成新试卷: " + difficulty + " 难度, " + 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; @@ -39,25 +55,33 @@ public class QuestionController { 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++; - return currentQuestionIndex < currentQuestions.size(); + 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++) { @@ -65,21 +89,28 @@ public class QuestionController { score++; } } + logInfo("计算得分: " + score + "/" + currentQuestions.size()); return score; } + @Override public double getPercentage() { - return (double) score / currentQuestions.size() * 100; + 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]; } diff --git a/src/controller/QuestionService.java b/src/controller/QuestionService.java new file mode 100644 index 0000000..a509ca9 --- /dev/null +++ b/src/controller/QuestionService.java @@ -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(); +} diff --git a/src/controller/UserController.java b/src/controller/UserController.java index ddc09ee..e1bc1e2 100644 --- a/src/controller/UserController.java +++ b/src/controller/UserController.java @@ -5,130 +5,152 @@ import utils.FileUtil; import utils.ValidationUtil; import utils.EmailUtil; -public class UserController { +public class UserController extends BaseController implements UserService { - - public void cleanuers(){ - - FileUtil.cleanupUnregisteredUsers(); - } - /** - * 注册用户 - 发送注册码 - */ + @Override public String registerUser(String username, String email) { - // 验证用户名 - if (username == null || username.trim().isEmpty()) { - return "用户名不能为空"; - } - if (username.length() < 2 || username.length() > 20) { - return "用户名长度应为2-20个字符"; - } - - // 验证邮箱 - 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); - return "注册码已发送到您的邮箱,请查收!"; - } else { - return "邮件发送失败,请检查邮箱地址或稍后重试"; + 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) { - User user = FileUtil.loadUserByUsername(username); - - if (user == null) { - return "用户不存在或注册码未发送"; - } - - if (!user.getRegistrationCode().equals(code)) { - return "注册码不正确"; - } - - if (!password.equals(confirmPassword)) { - return "两次输入的密码不一致"; - } + 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); - if (!ValidationUtil.isValidPassword(password)) { - return "密码必须为6-10位,且包含大小写字母和数字"; + logInfo("用户完成注册: " + username); + return "注册成功!"; + } catch (Exception e) { + logError("完成注册异常: " + e.getMessage()); + return "系统错误,请稍后重试"; } - - user.setPassword(password); - user.setRegistered(true); - FileUtil.saveUser(user); - - return "注册成功!"; } - /** - * 用户登录 - 使用用户名登录 - */ + @Override public User login(String loginId, String password) { - // 判断是邮箱还是用户名 - User user; - if (loginId.contains("@")) { - // 如果是邮箱格式,按邮箱查找 - user = FileUtil.loadUserByEmail(loginId); - } else { - // 否则按用户名查找 - user = FileUtil.loadUserByUsername(loginId); + 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; } + } - // 验证用户 - if (user == null) { - 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() == null || !user.getPassword().equals(password)) { - return null; // 密码错误 - } + if (!user.getPassword().equals(oldPassword)) { + return "原密码不正确"; + } - return user; // 登录成功 - } + if (!newPassword.equals(confirmPassword)) { + return "两次输入的新密码不一致"; + } - /** - * 修改密码 - */ - public String changePassword(User user, String oldPassword, String newPassword, String confirmPassword) { - if (!user.getPassword().equals(oldPassword)) { - return "原密码不正确"; - } + if (!ValidationUtil.isValidPassword(newPassword)) { + return "新密码必须为6-10位,且包含大小写字母和数字"; + } - if (!newPassword.equals(confirmPassword)) { - return "两次输入的新密码不一致"; - } + user.setPassword(newPassword); + FileUtil.saveUser(user); - if (!ValidationUtil.isValidPassword(newPassword)) { - return "新密码必须为6-10位,且包含大小写字母和数字"; + logInfo("密码修改成功: " + user.getUsername()); + return "密码修改成功"; + } catch (Exception e) { + logError("修改密码异常: " + e.getMessage()); + return "系统错误,请稍后重试"; } - - user.setPassword(newPassword); - FileUtil.saveUser(user); - return "密码修改成功"; } + @Override + public void cleanupUnregisteredUsers() { + try { + FileUtil.cleanupUnregisteredUsers(); + logInfo("执行未注册用户清理"); + } catch (Exception e) { + logError("清理未注册用户异常: " + e.getMessage()); + } + } } \ No newline at end of file diff --git a/src/controller/UserService.java b/src/controller/UserService.java new file mode 100644 index 0000000..75f7f01 --- /dev/null +++ b/src/controller/UserService.java @@ -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(); +} \ No newline at end of file diff --git a/src/utils/EmailUtil.java b/src/utils/EmailUtil.java index a379926..99a04f5 100644 --- a/src/utils/EmailUtil.java +++ b/src/utils/EmailUtil.java @@ -34,9 +34,11 @@ public class EmailUtil { 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 @@ -44,20 +46,25 @@ public class EmailUtil { 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(); @@ -92,7 +99,6 @@ public class EmailUtil { "
" + "
" + code + "
" + "
" + - "

注册码直到系统退出前都有效,请尽快完成注册。

" + "

如果这不是您的操作,请忽略此邮件。

" + "
" + "

此为系统邮件,请勿回复

" + diff --git a/src/utils/FileUtil.java b/src/utils/FileUtil.java index 8992081..dd1cbc2 100644 --- a/src/utils/FileUtil.java +++ b/src/utils/FileUtil.java @@ -24,38 +24,43 @@ public class FileUtil { static { new File("data").mkdirs(); } + /** * 清理未注册成功的用户(未设置密码的用户) */ public static void cleanupUnregisteredUsers() { - try { Map users = loadAllUsers(); boolean hasChanges = false; + long currentTime = System.currentTimeMillis(); + // 使用迭代器安全删除 Iterator> iterator = users.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); User user = entry.getValue(); - // 删除条件:未设置密码 或 未完成注册 - if (user.getPassword() == null || user.getPassword().isEmpty() || !user.isRegistered()) { + + // 删除条件:未设置密码 或 未完成注册 且 创建时间超过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); - System.out.println("未注册用户清理完成"); + } else { System.out.println("没有需要清理的未注册用户"); } - }catch (Exception e) { + } catch (Exception e) { System.err.println("清理未注册用户时出错: " + e.getMessage()); + e.printStackTrace(); } - } + public static void saveAllUsers(Map users) { try (FileWriter writer = new FileWriter(USERS_FILE)) { gson.toJson(users, writer); @@ -63,6 +68,7 @@ public class FileUtil { System.err.println("保存用户数据失败: " + e.getMessage()); } } + // 用户相关方法保持不变... public static void saveUser(User user) { try { @@ -150,6 +156,24 @@ public class FileUtil { } } + /** + * 从本地文件加载当前试卷 + */ + 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; + } + } /** * 试卷数据包装类 diff --git a/src/view/ExamView.java b/src/view/ExamView.java index 7638fc8..1dc5109 100644 --- a/src/view/ExamView.java +++ b/src/view/ExamView.java @@ -1,6 +1,7 @@ package view; import controller.NavigationController; +import controller.NavigationService; import controller.QuestionController; import model.Question; @@ -24,6 +25,7 @@ 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; @@ -35,6 +37,7 @@ public class ExamView extends JFrame { public ExamView() { questionController = new QuestionController(); + navigationService = NavigationController.getInstance(); initializeUI(); } @@ -272,7 +275,7 @@ public class ExamView extends JFrame { dispose(); // 显示结果页面 - NavigationController.showResultView(score, total, percentage); + navigationService.showResultView(score, total, percentage); // 修改 } } } \ No newline at end of file diff --git a/src/view/LoginView.java b/src/view/LoginView.java index 47cc10d..b690740 100644 --- a/src/view/LoginView.java +++ b/src/view/LoginView.java @@ -1,7 +1,9 @@ package view; import controller.NavigationController; +import controller.NavigationService; import controller.UserController; +import controller.UserService; import model.User; import javax.swing.JFrame; @@ -20,18 +22,21 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class LoginView extends JFrame { - private JTextField usernameField; // 新增:用户名字段 + private JTextField usernameField; private JPasswordField passwordField; - private UserController userController; + private UserService userController; + private NavigationService navigationService; public LoginView() { userController = new UserController(); + navigationService = NavigationController.getInstance(); initializeUI(); cleanupUnregisteredUsers(); } + private void cleanupUnregisteredUsers() { try { - userController.cleanuers(); + userController.cleanupUnregisteredUsers(); } catch (Exception e) { System.err.println("清理未注册用户时出错: " + e.getMessage()); } @@ -52,12 +57,12 @@ public class LoginView extends JFrame { titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); mainPanel.add(titleLabel, BorderLayout.NORTH); - // Form panel - 修改为用户名和密码 + // Form panel JPanel formPanel = new JPanel(new GridLayout(3, 2, 10, 10)); - JLabel usernameLabel = new JLabel("用户名:"); // 修改:改为用户名 + JLabel usernameLabel = new JLabel("用户名:"); usernameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); - usernameField = new JTextField(); // 初始化用户名字段 + usernameField = new JTextField(); JLabel passwordLabel = new JLabel("密码:"); passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); @@ -87,7 +92,8 @@ public class LoginView extends JFrame { // Add action listeners loginButton.addActionListener(new LoginAction()); registerButton.addActionListener(e -> { - NavigationController.showRegisterView(); + + navigationService.showRegisterView(); }); // Enter key support @@ -113,12 +119,12 @@ public class LoginView extends JFrame { if (user != null) { JOptionPane.showMessageDialog(LoginView.this, "登录成功!", "成功", JOptionPane.INFORMATION_MESSAGE); - // 修改:传递用户信息到主界面 - NavigationController.showMainView(user); + + navigationService.showMainView(user); } else { JOptionPane.showMessageDialog(LoginView.this, "用户名或密码错误", "错误", JOptionPane.ERROR_MESSAGE); } } } -} +} \ No newline at end of file diff --git a/src/view/MainView.java b/src/view/MainView.java index 1d0cf04..ff853d9 100644 --- a/src/view/MainView.java +++ b/src/view/MainView.java @@ -1,6 +1,7 @@ package view; import controller.NavigationController; +import controller.NavigationService; // 新增导入 import controller.UserController; import model.User; @@ -23,9 +24,11 @@ public class MainView extends JFrame { private JComboBox difficultyComboBox; private JTextField countField; private User currentUser; // 保存当前登录用户 + private NavigationService navigationService; // 新增 public MainView(User user) { this.currentUser = user; + this.navigationService = NavigationController.getInstance(); // 新增 initializeUI(); } @@ -86,7 +89,7 @@ public class MainView extends JFrame { // Add action listeners startButton.addActionListener(new StartExamAction()); changePasswordButton.addActionListener(new ChangePasswordAction()); - logoutButton.addActionListener(e -> NavigationController.showLoginView()); + logoutButton.addActionListener(e -> navigationService.showLoginView()); // 修改 add(mainPanel); } @@ -105,7 +108,7 @@ public class MainView extends JFrame { return; } - NavigationController.showExamView(difficulty, count); + navigationService.showExamView(difficulty, count); // 修改 } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(MainView.this, "请输入有效的数字", "错误", JOptionPane.ERROR_MESSAGE); diff --git a/src/view/RegisterView.java b/src/view/RegisterView.java index 83ec84b..ffb35d0 100644 --- a/src/view/RegisterView.java +++ b/src/view/RegisterView.java @@ -1,6 +1,7 @@ package view; import controller.NavigationController; +import controller.NavigationService; import controller.UserController; import model.User; @@ -12,28 +13,33 @@ import javax.swing.JPasswordField; import javax.swing.JButton; import javax.swing.BorderFactory; import javax.swing.JOptionPane; -import java.awt.*; +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 usernameField; // 用户名输入框 private JTextField emailField; private JTextField codeField; private JPasswordField passwordField; private JPasswordField confirmPasswordField; private UserController userController; - private String currentUsername; + 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, 550); // 增加高度以容纳所有组件 + setSize(550, 500); setLocationRelativeTo(null); setResizable(false); @@ -45,8 +51,8 @@ public class RegisterView extends JFrame { titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); mainPanel.add(titleLabel, BorderLayout.NORTH); - // Form panel - JPanel formPanel = new JPanel(new GridLayout(6, 2, 10, 10)); + // Form panel - 增加用户名字段 + JPanel formPanel = new JPanel(new GridLayout(8, 2, 10, 10)); JLabel usernameLabel = new JLabel("用户名:"); usernameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); @@ -80,7 +86,7 @@ public class RegisterView extends JFrame { formPanel.add(emailLabel); formPanel.add(emailField); formPanel.add(sendCodeButton); - formPanel.add(new JLabel()); // 空标签占位 + formPanel.add(new JLabel()); formPanel.add(codeLabel); formPanel.add(codeField); formPanel.add(passwordLabel); @@ -88,31 +94,17 @@ public class RegisterView extends JFrame { formPanel.add(confirmPasswordLabel); formPanel.add(confirmPasswordField); - mainPanel.add(formPanel, BorderLayout.CENTER); - - // 创建底部面板,包含密码提示和返回按钮 - JPanel bottomPanel = new JPanel(new BorderLayout(10, 10)); + formPanel.add(registerButton); - // 密码要求提示 - JLabel hintLabel = new JLabel( - "" + - "密码要求:6-10位,必须包含大小写字母和数字
" + - "例如:Abc123、Test456" + - "", JLabel.CENTER); - hintLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); - hintLabel.setForeground(Color.GRAY); - bottomPanel.add(hintLabel, BorderLayout.CENTER); + mainPanel.add(formPanel, BorderLayout.CENTER); // Back button JButton backButton = new JButton("返回登录"); backButton.setFont(new Font("微软雅黑", Font.PLAIN, 14)); - backButton.addActionListener(e -> NavigationController.showLoginView()); - - JPanel buttonPanel = new JPanel(new FlowLayout()); - buttonPanel.add(registerButton); // 将注册按钮移到这里 - buttonPanel.add(backButton); - bottomPanel.add(buttonPanel, BorderLayout.SOUTH); + backButton.addActionListener(e -> navigationService.showLoginView()); // 修改 + JPanel bottomPanel = new JPanel(new FlowLayout()); + bottomPanel.add(backButton); mainPanel.add(bottomPanel, BorderLayout.SOUTH); // Add action listeners @@ -162,7 +154,7 @@ public class RegisterView extends JFrame { if (result.equals("注册成功!")) { JOptionPane.showMessageDialog(RegisterView.this, result, "成功", JOptionPane.INFORMATION_MESSAGE); - NavigationController.showLoginView(); + navigationService.showLoginView(); // 修改 } else { JOptionPane.showMessageDialog(RegisterView.this, result, "错误", JOptionPane.ERROR_MESSAGE); diff --git a/src/view/ResultView.java b/src/view/ResultView.java index 8987f5f..703cccb 100644 --- a/src/view/ResultView.java +++ b/src/view/ResultView.java @@ -1,6 +1,7 @@ package view; import controller.NavigationController; +import controller.NavigationService; import javax.swing.JFrame; import javax.swing.JPanel; @@ -17,8 +18,10 @@ 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(); } @@ -63,7 +66,7 @@ public class ResultView extends JFrame { mainPanel.add(buttonPanel, BorderLayout.SOUTH); // Add action listeners - continueButton.addActionListener(e -> NavigationController.showMainView()); + continueButton.addActionListener(e -> navigationService.showMainView()); // 修改 exitButton.addActionListener(e -> System.exit(0)); add(mainPanel); -- 2.34.1 From 9bdc7af65825ea00baa08708f02d0c7140b18fae Mon Sep 17 00:00:00 2001 From: wrh <16681308616@163.com> Date: Fri, 10 Oct 2025 22:50:24 +0800 Subject: [PATCH 6/6] hebing --- src/utils/EmailUtil.java | 111 ------------ src/utils/FileUtil.java | 200 -------------------- src/utils/ValidationUtil.java | 39 ---- src/view/ChangePasswordDialog.java | 131 -------------- src/view/ExamView.java | 281 ----------------------------- src/view/LoginView.java | 130 ------------- src/view/MainView.java | 126 ------------- src/view/RegisterView.java | 164 ----------------- src/view/ResultView.java | 79 -------- 9 files changed, 1261 deletions(-) delete mode 100644 src/utils/EmailUtil.java delete mode 100644 src/utils/FileUtil.java delete mode 100644 src/utils/ValidationUtil.java delete mode 100644 src/view/ChangePasswordDialog.java delete mode 100644 src/view/ExamView.java delete mode 100644 src/view/LoginView.java delete mode 100644 src/view/MainView.java delete mode 100644 src/view/RegisterView.java delete mode 100644 src/view/ResultView.java diff --git a/src/utils/EmailUtil.java b/src/utils/EmailUtil.java deleted file mode 100644 index 99a04f5..0000000 --- a/src/utils/EmailUtil.java +++ /dev/null @@ -1,111 +0,0 @@ -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 "" + - "" + - "" + - " " + - " " + - "" + - "" + - "
" + - "
" + - "

数学学习软件 - 注册验证码

" + - "
" + - "

亲爱的 " + username + ",您好!

" + - "

您正在注册数学学习软件,请使用以下验证码完成注册:

" + - "
" + - "
" + code + "
" + - "
" + - "

如果这不是您的操作,请忽略此邮件。

" + - "
" + - "

此为系统邮件,请勿回复

" + - "

数学学习软件团队

" + - "
" + - "
" + - "" + - ""; - } -} \ No newline at end of file diff --git a/src/utils/FileUtil.java b/src/utils/FileUtil.java deleted file mode 100644 index dd1cbc2..0000000 --- a/src/utils/FileUtil.java +++ /dev/null @@ -1,200 +0,0 @@ -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 users = loadAllUsers(); - boolean hasChanges = false; - long currentTime = System.currentTimeMillis(); - - // 使用迭代器安全删除 - Iterator> iterator = users.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry 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 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 users = loadAllUsers(); - users.put(user.getUsername(), user); - saveAllUsers(users); - } catch (Exception e) { - System.err.println("保存用户数据失败: " + e.getMessage()); - } - } - - public static Map loadAllUsers() { - try { - File file = new File(USERS_FILE); - if (!file.exists()) { - return new HashMap<>(); - } - - try (FileReader reader = new FileReader(file)) { - Type type = new TypeToken>(){}.getType(); - Map users = gson.fromJson(reader, type); - - // 修复现有数据中的问题 - if (users != null) { - for (Map.Entry 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 users = loadAllUsers(); - return users.get(username); - } - - public static User loadUserByEmail(String email) { - Map 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 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 questions; - private String difficulty; - private int questionCount; - private Date generateTime; - - public ExamData(List questions, String difficulty, int questionCount, Date generateTime) { - this.questions = questions; - this.difficulty = difficulty; - this.questionCount = questionCount; - this.generateTime = generateTime; - } - - // Getters - public List getQuestions() { return questions; } - public String getDifficulty() { return difficulty; } - public int getQuestionCount() { return questionCount; } - public Date getGenerateTime() { return generateTime; } - } -} \ No newline at end of file diff --git a/src/utils/ValidationUtil.java b/src/utils/ValidationUtil.java deleted file mode 100644 index 2e3cbe2..0000000 --- a/src/utils/ValidationUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -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; - } - } -} diff --git a/src/view/ChangePasswordDialog.java b/src/view/ChangePasswordDialog.java deleted file mode 100644 index 8931b77..0000000 --- a/src/view/ChangePasswordDialog.java +++ /dev/null @@ -1,131 +0,0 @@ -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( - "" + - "密码要求:6-10位,必须包含大小写字母和数字
" + - "例如:Abc123、Test456" + - "", 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); - } - } - } -} \ No newline at end of file diff --git a/src/view/ExamView.java b/src/view/ExamView.java deleted file mode 100644 index 1dc5109..0000000 --- a/src/view/ExamView.java +++ /dev/null @@ -1,281 +0,0 @@ -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); // 修改 - } - } -} \ No newline at end of file diff --git a/src/view/LoginView.java b/src/view/LoginView.java deleted file mode 100644 index b690740..0000000 --- a/src/view/LoginView.java +++ /dev/null @@ -1,130 +0,0 @@ -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); - } - } - } -} \ No newline at end of file diff --git a/src/view/MainView.java b/src/view/MainView.java deleted file mode 100644 index ff853d9..0000000 --- a/src/view/MainView.java +++ /dev/null @@ -1,126 +0,0 @@ -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 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); - - // Button panel - 添加修改密码按钮 - 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); - } - } -} \ No newline at end of file diff --git a/src/view/RegisterView.java b/src/view/RegisterView.java deleted file mode 100644 index ffb35d0..0000000 --- a/src/view/RegisterView.java +++ /dev/null @@ -1,164 +0,0 @@ -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); - } - } - } -} \ No newline at end of file diff --git a/src/view/ResultView.java b/src/view/ResultView.java deleted file mode 100644 index 703cccb..0000000 --- a/src/view/ResultView.java +++ /dev/null @@ -1,79 +0,0 @@ -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)); - } -} \ No newline at end of file -- 2.34.1