对函数进行解耦,代码规范化 #17

Merged
hnu202306060209 merged 71 commits from develop into main 4 months ago

38
.gitignore vendored

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CheckStyle-IDEA" serialisationVersion="2">
<checkstyleVersion>11.0.1</checkstyleVersion>
<scanScope>JavaOnly</scanScope>
<copyLibs>true</copyLibs>
<option name="thirdPartyClasspath" />
<option name="activeLocationIds" />
<option name="locations">
<list>
<ConfigurationLocation id="bundled-sun-checks" type="BUNDLED" scope="All" description="Sun Checks">(bundled)</ConfigurationLocation>
<ConfigurationLocation id="bundled-google-checks" type="BUNDLED" scope="All" description="Google Checks">(bundled)</ConfigurationLocation>
</list>
</option>
</component>
</project>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

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

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

Binary file not shown.

@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

@ -0,0 +1,181 @@
# 数学学习软件系统说明文档
## 概述
数学学习软件是一个基于 JavaFX 开发的桌面应用程序,旨在为不同学段(小学、初中、高中)的学生提供定制化的数学练习平台。系统支持用户注册登录、题目难度选择、答题练习和成绩统计等功能,确保生成的题目符合各学段的教学要求。
## 运行环境
### 平台要求
- **操作系统**: Windows 10/11
- **Java版本**: JDK 17 或更高版本
- **内存**: 最低 2GB RAM
- **磁盘空间**: 至少 100MB 可用空间
### 依赖项
- JavaFX 17 (已包含在项目中)
- 需要网络连接用于邮箱验证功能
## 项目结构
```
src/
├── com.wsf.mathapp/ # 前端界面和控制器
│ ├── controller/
│ │ └── SceneManager.java
│ ├── view/
│ │ ├── LoginView.java
│ │ ├── RegisterView.java
│ │ ├── MainMenuView.java
│ │ ├── LevelSelectionView.java
│ │ ├── QuestionCountView.java
│ │ ├── QuizView.java
│ │ └── ResultView.java
│ ├── service/
│ │ └── QuestionService.java
│ └── MathApplication.java
├── com.ybw.mathapp/ # 后端核心逻辑
│ ├── entity/
│ │ ├── User.java
│ │ └── QuestionWithOptions.java
│ ├── service/
│ │ ├── QuestionGenerator.java
│ │ ├── PrimarySchoolGenerator.java
│ │ ├── JuniorHighGenerator.java
│ │ ├── SeniorHighGenerator.java
│ │ ├── MultipleChoiceGenerator.java
│ │ └── AdvancedCaculate.java
│ ├── util/
│ │ ├── Login.java
│ │ ├── Register.java
│ │ ├── ChangePassword.java
│ │ ├── LoginFileUtils.java
│ │ └── EmailService.java
│ └── config/
│ └── EmailConfig.java
└── Main.java
```
## 安装和运行
### 方法一:使用 IDE 运行(推荐)
1. **安装 JDK 17**
- 下载并安装 Oracle JDK 17 或 OpenJDK 17
- 设置 JAVA_HOME 环境变量
2. **导入项目**
- 使用 IntelliJ IDEA 或 Eclipse 导入项目
- 确保配置了 JDK 17
3. **运行程序**
- 打开 `Main.java``MathApplication.java`
- 点击运行按钮启动应用程序
### 方法二:使用 JAR 文件
**直接运行 JAR 文件**
```bash
java -jar .\mathapp-1.0.jar
```
## 系统功能
### 1. 用户管理
- **用户注册**: 支持用户名、邮箱注册,通过邮箱验证码验证身份
- **用户登录**: 支持用户名或邮箱登录,密码验证
- **密码修改**: 在线修改密码,需验证原密码
- **用户信息显示**: 动态显示用户头像和用户名
### 2. 题目生成
- **小学题目**: 生成包含四则运算和括号的题目,确保计算结果为非负数
- **初中题目**: 生成包含平方或开根号运算的题目,确保每道题目都包含高级运算符
- **高中题目**: 生成包含三角函数的题目,支持复杂的数学表达式
- **选择题转换**: 将生成的题目转换为选择题形式,包含正确答案和干扰项
### 3. 答题评估
- **实时答题**: 显示题目和四个选项,支持单选
- **进度跟踪**: 显示当前题号和总题数
- **自动评分**: 计算正确率并给出相应评语
- **结果展示**: 根据得分显示不同颜色的分数和鼓励性评语
## 使用指南
### 第一步:启动应用
```bash
# 确保在项目根目录下执行
java -jar .\mathapp-1.0.jar
```
### 第二步:用户注册
1. 点击"注册账号"按钮
2. 输入用户名3-20位字母、数字
3. 输入有效邮箱地址
4. 获取并输入邮箱验证码
5. 设置密码6-10位包含大小写字母和数字
6. 确认密码并完成注册
### 第三步:选择练习
1. 登录后进入主菜单
2. 点击"开始练习"
3. 选择难度级别:小学、初中或高中
4. 输入题目数量10-30题
5. 开始答题
### 第四步:答题和查看结果
1. 阅读题目并从四个选项中选择答案
2. 点击"下一题"继续
3. 完成所有题目后查看得分和评语
4. 可选择"再次练习"或"返回主菜单"
## 配置说明
### 邮箱配置
系统使用 QQ 邮箱发送验证码,如需修改配置,编辑 `EmailConfig.java`
```java
public class EmailConfig {
public static final String SMTP_HOST = "smtp.qq.com";
public static final String SMTP_PORT = "587";
public static final String SENDER_EMAIL = "your-email@qq.com";
public static final String SENDER_PASSWORD = "your-authorization-code";
}
```
### 数据存储
- 用户信息存储在 `users.txt` 文件中
- 每个用户的答题记录保存在以用户名为名的目录中
## 故障排除
### 常见问题
1. **无法启动程序**
- 检查 JDK 版本是否为 17
- 确认 JavaFX 库路径正确
2. **邮箱验证码收不到**
- 检查网络连接
- 确认邮箱配置正确
- 查看垃圾邮件文件夹
3. **界面显示异常**
- 确保屏幕分辨率不低于 1024x768
- 检查系统字体设置
### 日志查看
程序运行日志输出到控制台,包含:
- 用户登录/注册信息
- 题目生成状态
- 界面切换记录
## 技术特性
- **模块化设计**: 易于维护和扩展
- **响应式界面**: 适配不同屏幕尺寸
- **数据验证**: 完整的输入验证机制
- **错误处理**: 友好的错误提示信息
- **性能优化**: 高效的题目生成和去重算法
## 注意事项
1. 首次使用需要注册账号
2. 小学题目不包含负数运算
3. 初中题目避免负数开根号
4. 邮箱验证码有效期为 5 分钟
5. 建议在稳定的网络环境下使用邮箱功能

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

316
mvnw vendored

@ -0,0 +1,316 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

188
mvnw.cmd vendored

@ -0,0 +1,188 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wsf</groupId>
<artifactId>MathApp</artifactId>
<version>1.0-SNAPSHOT</version>
<name>MathApp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<junit.version>5.10.0</junit.version>
<javafx.version>17.0.6</javafx.version>
<javafx.platform>win</javafx.platform>
</properties>
<dependencies>
<!-- JavaFX 基础模块 (必需) -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
<classifier>${javafx.platform}</classifier> <!-- 添加 -->
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
<classifier>${javafx.platform}</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>${javafx.version}</version>
<classifier>${javafx.platform}</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
<classifier>${javafx.platform}</classifier>
</dependency>
<!-- 其他 JavaFX 模块也加上 classifier -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>${javafx.version}</version>
<classifier>${javafx.platform}</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
<classifier>${javafx.platform}</classifier>
</dependency>
<!-- 其他依赖保持不变 -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- 第三方库 -->
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>11.1.2</version>
</dependency>
<dependency>
<groupId>com.dlsc.formsfx</groupId>
<artifactId>formsfx-core</artifactId>
<version>11.6.0</version>
<exclusions>
<exclusion>
<groupId>org.openjfx</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.synedra</groupId>
<artifactId>validatorfx</artifactId>
<version>0.4.0</version>
<exclusions>
<exclusion>
<groupId>org.openjfx</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-javafx</artifactId>
<version>12.3.1</version>
</dependency>
<dependency>
<groupId>org.kordamp.bootstrapfx</groupId>
<artifactId>bootstrapfx-core</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>eu.hansolo</groupId>
<artifactId>tilesfx</artifactId>
<version>11.48</version>
<exclusions>
<exclusion>
<groupId>org.openjfx</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.almasb</groupId>
<artifactId>fxgl</artifactId>
<version>17.3</version>
<exclusions>
<exclusion>
<groupId>org.openjfx</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<!-- 打包成 fat jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.wsf.mathapp.MathApplication</mainClass>
</transformer>
</transformers>
<!-- <filters>-->
<!-- <filter>-->
<!-- <artifact>*:*</artifact>-->
<!-- <excludes>-->
<!-- <exclude>META-INF/*.SF</exclude>-->
<!-- <exclude>META-INF/*.DSA</exclude>-->
<!-- <exclude>META-INF/*.RSA</exclude>-->
<!-- </excludes>-->
<!-- </filter>-->
<!-- </filters>-->
<!-- 关键:不要裁剪 META-INF/native -->
<minimizeJar>false</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,34 @@
package com.wsf.mathapp;
import com.wsf.mathapp.controller.SceneManager;
import javafx.application.Application;
import javafx.stage.Stage;
/**
* .
* JavaFX.
*/
public class Main extends Application {
/**
* JavaFX.
* .
*
* @param primaryStage JavaFX.
*/
@Override
public void start(Stage primaryStage) {
SceneManager sceneManager = new SceneManager(primaryStage);
sceneManager.showLoginView();
}
/**
* .
* JavaFX.
*
* @param args .
*/
public static void main(String[] args) {
launch(args);
}
}

@ -0,0 +1,9 @@
package com.wsf.mathapp;
public class MathApplication {
public static void main(String[] args) {
Main.main(args);
}
}

@ -0,0 +1,164 @@
package com.wsf.mathapp.controller;
import com.wsf.mathapp.view.LevelSelectionView;
import com.wsf.mathapp.view.LoginView;
import com.wsf.mathapp.view.MainMenuView;
import com.wsf.mathapp.view.QuestionCountView;
import com.wsf.mathapp.view.QuizView;
import com.wsf.mathapp.view.RegisterView;
import com.wsf.mathapp.view.ResultView;
import javafx.stage.Stage;
/**
* .
* .
*/
public class SceneManager {
private final Stage primaryStage;
private final LoginView loginView;
private final RegisterView registerView;
private final MainMenuView mainMenuView;
private final LevelSelectionView levelSelectionView;
private final QuestionCountView questionCountView;
private final QuizView quizView;
private final ResultView resultView;
private String currentUserName;
/**
* .
*
* @param primaryStage JavaFX.
*/
public SceneManager(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("数学学习软件");
this.primaryStage.setResizable(false);
// 初始化所有视图
this.loginView = new LoginView(this);
this.registerView = new RegisterView(this);
this.mainMenuView = new MainMenuView(this);
this.levelSelectionView = new LevelSelectionView(this);
this.questionCountView = new QuestionCountView(this);
this.quizView = new QuizView(this);
this.resultView = new ResultView(this);
}
/**
* .
* .
*/
public void showLoginView() {
System.out.println("切换到登录界面");
loginView.clearFields();
primaryStage.setScene(loginView.getScene());
primaryStage.show();
}
/**
* .
* .
*/
public void showRegisterView() {
System.out.println("切换到注册界面");
registerView.clearFields();
primaryStage.setScene(registerView.getScene());
primaryStage.show();
}
/**
* .
* .
*/
public void showMainMenuView() {
System.out.println("切换到主菜单界面");
// 在显示主菜单前更新用户名
if (mainMenuView != null) {
mainMenuView.updateUsername(currentUserName);
}
if (mainMenuView != null) {
primaryStage.setScene(mainMenuView.getScene());
}
primaryStage.show(); // 添加这行
}
/**
* .
* .
*/
public void showLevelSelectionView() {
System.out.println("切换到级别选择界面");
// 在显示级别选择界面前更新用户名
if (levelSelectionView != null) {
levelSelectionView.updateUsername(currentUserName);
}
if (levelSelectionView != null) {
primaryStage.setScene(levelSelectionView.getScene());
}
primaryStage.show(); // 添加这行
}
/**
* .
* .
*/
public void showQuestionCountView() {
System.out.println("切换到题目数量选择界面");
primaryStage.setScene(questionCountView.getScene());
primaryStage.show(); // 添加这行
}
/**
* .
*
* @param level .
* @param count .
*/
public void showQuizView(String level, int count) {
System.out.println("切换到答题界面 - 级别: " + level + ", 题目数量: " + count);
// 设置测验参数
quizView.setQuizParameters(level, count);
primaryStage.setScene(quizView.getScene());
primaryStage.show();
}
/**
* .
* @param score .
*/
public void showResultView(double score) {
System.out.println("切换到结果界面,分数: " + score);
resultView.setScore(score);
primaryStage.setScene(resultView.getScene());
primaryStage.show(); // 添加这行
}
/**
* .
*
* @return QuestionCountView .
*/
public QuestionCountView getQuestionCountView() {
return questionCountView;
}
/**
* .
*
* @param currentUserName .
*/
public void setCurrentUserName(String currentUserName) {
this.currentUserName = currentUserName;
System.out.println("设置当前用户名: " + currentUserName);
}
/**
* .
*
* @return String .
*/
public String getCurrentUserName() {
return this.currentUserName;
}
}

@ -0,0 +1,28 @@
package com.wsf.mathapp.service;
import com.ybw.mathapp.service.JuniorHighGenerator;
import com.ybw.mathapp.service.PrimarySchoolGenerator;
import com.ybw.mathapp.service.QuestionGenerator;
import com.ybw.mathapp.service.SeniorHighGenerator;
/**
* .
*/
public class QuestionService {
/**
* .
*
* @param level "小学""初中""高中".
* @return QuestionGenerator null.
*/
public static QuestionGenerator createGenerator(String level) {
return switch (level) {
case "小学" -> new PrimarySchoolGenerator();
case "初中" -> new JuniorHighGenerator();
case "高中" -> new SeniorHighGenerator();
default -> null;
};
}
}

@ -0,0 +1,310 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
/**
* .
* .
*/
public class LevelSelectionView {
private Scene scene;
private final SceneManager sceneManager;
private String currentUsername;
// 添加界面组件引用
private Label usernameLabel;
private Text avatarText;
/**
* .
*
* @param sceneManager .
*/
public LevelSelectionView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
this.currentUsername = sceneManager.getCurrentUserName();
createScene();
}
/**
* .
*/
private void createScene() {
// 创建主容器
VBox mainContainer = new VBox();
mainContainer.setPadding(new Insets(20));
// 创建顶部用户信息栏
HBox userInfoBar = createUserInfoBar();
// 创建级别选择内容区域
VBox selectionContent = createSelectionContent();
mainContainer.getChildren().addAll(userInfoBar, selectionContent);
scene = new Scene(mainContainer, 450, 550);
}
/**
* .
*
* @return HBox .
*/
private HBox createUserInfoBar() {
HBox userInfoBar = new HBox(15);
userInfoBar.setAlignment(Pos.CENTER_LEFT);
userInfoBar.setPadding(new Insets(0, 0, 30, 0));
userInfoBar.setStyle("-fx-border-color: #e0e0e0; "
+ "-fx-border-width: 0 0 1 0; -fx-padding: 0 0 15 0;");
// 用户名标签
usernameLabel = new Label(currentUsername != null ? currentUsername : "用户");
usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 16));
usernameLabel.setStyle("-fx-text-fill: #2c3e50;");
// 间隔
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
// 创建头像容器
VBox avatarContainer = createAvatarContainer();
userInfoBar.getChildren().addAll(avatarContainer, usernameLabel, spacer);
return userInfoBar;
}
/**
* .
*
* @return VBox .
*/
private VBox createAvatarContainer() {
VBox avatarContainer = new VBox();
avatarContainer.setAlignment(Pos.CENTER);
avatarContainer.setPrefSize(50, 50);
// 添加首字母文本
avatarText = new Text(getFirstLetter());
avatarText.setFill(Color.WHITE);
avatarText.setFont(Font.font("Arial", FontWeight.BOLD, 16));
// 创建圆形头像背景
Circle avatarCircle = createAvatarCircle();
// 使用StackPane将文本放在圆形中心
javafx.scene.layout.StackPane avatarStack = new javafx.scene.layout.StackPane();
avatarStack.getChildren().addAll(avatarCircle, avatarText);
avatarStack.setPrefSize(44, 44);
avatarStack.setAlignment(Pos.CENTER);
avatarContainer.getChildren().add(avatarStack);
return avatarContainer;
}
/**
* .
*
* @return Circle .
*/
private Circle createAvatarCircle() {
Circle avatarCircle = new Circle(22);
avatarCircle.setFill(Color.web("#4CAF50")); // 统一的绿色背景
avatarCircle.setStroke(Color.WHITE);
avatarCircle.setStrokeWidth(2);
// 添加阴影效果
avatarCircle.setStyle("-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 5, 0.3, 2, 2);");
return avatarCircle;
}
/**
* .
*
* @return VBox .
*/
private VBox createSelectionContent() {
VBox selectionContent = new VBox(25);
selectionContent.setPadding(new Insets(30, 20, 20, 20));
selectionContent.setAlignment(Pos.CENTER);
// 创建标题区域
VBox titleSection = createTitleSection();
// 创建级别按钮区域
VBox levelButtonsSection = createLevelButtonsSection();
// 创建返回按钮
Button backButton = createBackButton();
selectionContent.getChildren().addAll(titleSection, levelButtonsSection, backButton);
return selectionContent;
}
/**
* .
*
* @return VBox .
*/
private VBox createTitleSection() {
VBox titleSection = new VBox(5);
titleSection.setAlignment(Pos.CENTER);
Label titleLabel = new Label("选择题目级别");
titleLabel.setFont(Font.font("Arial", FontWeight.BOLD, 26));
titleLabel.setStyle("-fx-text-fill: #2c3e50;");
Label subtitleLabel = new Label("请选择适合您的学习级别");
subtitleLabel.setFont(Font.font("Arial", 14));
subtitleLabel.setStyle("-fx-text-fill: #7f8c8d; -fx-padding: 0 0 10 0;");
titleSection.getChildren().addAll(titleLabel, subtitleLabel);
return titleSection;
}
/**
* .
*
* @return VBox .
*/
private VBox createLevelButtonsSection() {
VBox levelButtonsSection = new VBox(15);
levelButtonsSection.setAlignment(Pos.CENTER);
Button primaryButton = createLevelButton("小学题目", "#4CAF50", "#45a049");
Button juniorButton = createLevelButton("初中题目", "#2196F3", "#1976D2");
Button seniorButton = createLevelButton("高中题目", "#9C27B0", "#7B1FA2");
setupLevelButtonAction(primaryButton, "小学");
setupLevelButtonAction(juniorButton, "初中");
setupLevelButtonAction(seniorButton, "高中");
levelButtonsSection.getChildren().addAll(primaryButton, juniorButton, seniorButton);
return levelButtonsSection;
}
/**
* .
*
* @param button .
* @param level .
*/
private void setupLevelButtonAction(Button button, String level) {
button.setOnAction(e -> {
sceneManager.getQuestionCountView().setLevel(level);
sceneManager.showQuestionCountView();
});
}
/**
* .
*
* @return Button .
*/
private Button createBackButton() {
Button backButton = new Button("返回主菜单");
backButton.setStyle("-fx-background-color: #95a5a6; -fx-text-fill: "
+ "white; -fx-font-size: 14px; -fx-background-radius: 8; -fx-padding: 8 20;");
backButton.setPrefSize(180, 45);
setupButtonHoverEffect(backButton, "#95a5a6", "#7f8c8d");
backButton.setOnAction(e -> sceneManager.showMainMenuView());
return backButton;
}
/**
* .
*
* @param text .
* @param color .
* @param hoverColor .
* @return Button .
*/
private Button createLevelButton(String text, String color, String hoverColor) {
Button button = new Button(text);
button.setStyle(String.format(
"-fx-background-color: %s; -fx-text-fill: "
+ "white; -fx-font-size: 16px; -fx-font-weight: bold;-fx-background-radius: 12;"
+ "-fx-padding: 12 30;-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 8, 0.3, 2, 2);",
color
));
button.setPrefSize(220, 60);
setupButtonHoverEffect(button, color, hoverColor);
return button;
}
/**
* .
*
* @param button .
* @param normalColor .
* @param hoverColor .
*/
private void setupButtonHoverEffect(Button button, String normalColor, String hoverColor) {
String normalStyle = String.format(
"-fx-background-color: %s; -fx-text-fill: white; -fx-font-size: %s;"
+ " -fx-background-radius: %s; -fx-padding: %s; -fx-effect: "
+ "dropshadow(gaussian, rgba(0,0,0,0.2), 8, 0.3, 2, 2);",
normalColor,
button.getStyle().contains("16px") ? "16px" : "14px",
button.getStyle().contains("12") ? "12" : "8",
button.getStyle().contains("12 30") ? "12 30" : "8 20"
);
String hoverStyle = String.format(
"-fx-background-color: %s; -fx-text-fill: white; -fx-font-size: %s;"
+ " -fx-background-radius: %s; -fx-padding: %s; -fx-effect:"
+ " dropshadow(gaussian, rgba(0,0,0,0.3), 10, 0.4, 3, 3);",
hoverColor,
button.getStyle().contains("16px") ? "16px" : "14px",
button.getStyle().contains("12") ? "12" : "8",
button.getStyle().contains("12 30") ? "12 30" : "8 20"
);
button.setOnMouseEntered(e -> button.setStyle(hoverStyle));
button.setOnMouseExited(e -> button.setStyle(normalStyle));
}
/**
* .
*
* @return String "U".
*/
private String getFirstLetter() {
return currentUsername != null && !currentUsername.isEmpty()
? currentUsername.substring(0, 1).toUpperCase() : "U";
}
/**
* .
*
* @param username .
*/
public void updateUsername(String username) {
this.currentUsername = username;
if (usernameLabel != null) {
usernameLabel.setText(username != null ? username : "用户");
}
if (avatarText != null) {
avatarText.setText(getFirstLetter());
}
}
/**
* .
*
* @return Scene .
*/
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,265 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import com.ybw.mathapp.util.Login;
import com.ybw.mathapp.util.LoginFileUtils;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
/**
* .
* .
*/
public class LoginView {
private Scene scene;
private final SceneManager sceneManager;
// 将UI组件声明为类的成员变量
private TextField usernameOrEmailField;
private PasswordField passwordField;
private Label statusLabel;
/**
* .
*
* @param sceneManager .
*/
public LoginView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
/**
* .
*/
private void createScene() {
VBox root = new VBox(20);
root.setPadding(new Insets(40));
root.setAlignment(Pos.CENTER);
// 创建用户名/邮箱输入框
usernameOrEmailField = createUsernameField();
// 创建密码输入框
passwordField = createPasswordField();
// 创建登录按钮
Button loginButton = createLoginButton();
// 创建注册按钮
Button registerButton = createRegisterButton();
// 创建状态标签
statusLabel = new Label();
// 设置按钮事件
setupLoginButtonAction(loginButton);
setupRegisterButtonAction(registerButton);
// 添加回车键登录支持
setupEnterKeySupport(loginButton);
// 创建界面标题
Label titleLabel = createTitleLabel();
// 创建副标题
Label subtitleLabel = createSubtitleLabel();
root.getChildren().addAll(
titleLabel, subtitleLabel, usernameOrEmailField, passwordField,
loginButton, registerButton, statusLabel
);
scene = new Scene(root, 400, 500);
}
/**
* .
*
* @return Label .
*/
private Label createTitleLabel() {
Label titleLabel = new Label("数学学习软件");
titleLabel.setFont(Font.font(24));
return titleLabel;
}
/**
* .
*
* @return Label .
*/
private Label createSubtitleLabel() {
Label subtitleLabel = new Label("用户登录");
subtitleLabel.setFont(Font.font(18));
return subtitleLabel;
}
/**
* /.
*
* @return TextField /.
*/
private TextField createUsernameField() {
TextField field = new TextField();
field.setPromptText("请输入用户名或邮箱");
field.setMaxWidth(300);
field.setPrefHeight(40);
return field;
}
/**
* .
*
* @return PasswordField .
*/
private PasswordField createPasswordField() {
PasswordField field = new PasswordField();
field.setPromptText("请输入密码");
field.setMaxWidth(300);
field.setPrefHeight(40);
return field;
}
/**
* .
*
* @return Button .
*/
private Button createLoginButton() {
Button button = new Button("登录");
button.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; -fx-font-size: 14px;");
button.setPrefSize(300, 40);
return button;
}
/**
* .
*
* @return Button .
*/
private Button createRegisterButton() {
Button button = new Button("注册账号");
button.setStyle("-fx-background-color: #2196F3; -fx-text-fill: white; -fx-font-size: 14px;");
button.setPrefSize(300, 40);
return button;
}
/**
* .
*
* @param loginButton .
*/
private void setupLoginButtonAction(Button loginButton) {
loginButton.setOnAction(e -> handleLogin());
}
/**
* .
*
* @param registerButton .
*/
private void setupRegisterButtonAction(Button registerButton) {
registerButton.setOnAction(e -> sceneManager.showRegisterView());
}
/**
* .
*
* @param loginButton .
*/
private void setupEnterKeySupport(Button loginButton) {
usernameOrEmailField.setOnAction(e -> loginButton.fire());
passwordField.setOnAction(e -> loginButton.fire());
}
/**
* .
*/
private void handleLogin() {
String usernameOrEmail = usernameOrEmailField.getText().trim();
String password = passwordField.getText();
if (usernameOrEmail.isEmpty() || password.isEmpty()) {
showError(statusLabel, "用户名/邮箱和密码不能为空!");
return;
}
// 调用后端的登录方法,支持用户名或邮箱登录
boolean success = Login.login(usernameOrEmail, password);
System.out.println(usernameOrEmail);
System.out.println(password);
if (success) {
handleLoginSuccess(usernameOrEmail);
} else {
showError(statusLabel, "用户名/邮箱或密码错误!");
}
}
/**
* .
*
* @param usernameOrEmail 使.
*/
private void handleLoginSuccess(String usernameOrEmail) {
showSuccess(statusLabel);
String username = getUsernameFromInput(usernameOrEmail);
sceneManager.setCurrentUserName(username);
sceneManager.showMainMenuView();
}
/**
* .
*
* @param usernameOrEmail .
* @return String .
*/
private String getUsernameFromInput(String usernameOrEmail) {
String foundName = LoginFileUtils.emailFindName(usernameOrEmail);
return foundName == null ? usernameOrEmail : foundName;
}
/**
* .
*/
public void clearFields() {
usernameOrEmailField.clear();
passwordField.clear();
statusLabel.setText("");
}
/**
* .
*
* @param label .
* @param message .
*/
private void showError(Label label, String message) {
label.setText(message);
label.setStyle("-fx-text-fill: red;");
}
/**
* .
*
* @param label .
*/
private void showSuccess(Label label) {
label.setText("登录成功!");
label.setStyle("-fx-text-fill: green;");
}
/**
* .
*
* @return Scene .
*/
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,461 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import com.ybw.mathapp.util.ChangePassword;
import com.ybw.mathapp.util.Login;
import com.ybw.mathapp.util.Register;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
/**
* .
* 退.
*/
public class MainMenuView {
private Scene scene;
private final SceneManager sceneManager;
private String currentUsername;
// 添加界面组件引用,用于动态更新
private Label usernameLabel;
private Text avatarText;
private Label welcomeLabel;
/**
* .
*
* @param sceneManager .
*/
public MainMenuView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
this.currentUsername = sceneManager.getCurrentUserName();
createScene();
}
/**
* .
*/
private void createScene() {
// 创建主容器
VBox mainContainer = new VBox();
mainContainer.setPadding(new Insets(20));
// 创建顶部用户信息栏
HBox userInfoBar = createUserInfoBar();
// 创建主菜单内容区域
VBox menuContent = createMenuContent();
mainContainer.getChildren().addAll(userInfoBar, menuContent);
scene = new Scene(mainContainer, 450, 550);
}
/**
* .
*
* @return HBox .
*/
private HBox createUserInfoBar() {
HBox userInfoBar = new HBox(15);
userInfoBar.setAlignment(Pos.CENTER_LEFT);
userInfoBar.setPadding(new Insets(0, 0, 30, 0));
userInfoBar.setStyle("-fx-border-color: #e0e0e0; -fx-border-width: 0 0 1 0; "
+ "-fx-padding: 0 0 15 0;");
// 用户名标签
usernameLabel = new Label(currentUsername != null ? currentUsername : "用户");
usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 16));
usernameLabel.setStyle("-fx-text-fill: #2c3e50;");
// 间隔
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
// 创建头像容器
VBox avatarContainer = createAvatarContainer();
userInfoBar.getChildren().addAll(avatarContainer, usernameLabel, spacer);
return userInfoBar;
}
/**
* .
*
* @return VBox .
*/
private VBox createAvatarContainer() {
VBox avatarContainer = new VBox();
avatarContainer.setAlignment(Pos.CENTER);
avatarContainer.setPrefSize(50, 50);
// 添加首字母文本
avatarText = new Text(getFirstLetter());
avatarText.setFill(Color.WHITE);
avatarText.setFont(Font.font("Arial", FontWeight.BOLD, 16));
// 创建圆形头像背景
Circle avatarCircle = createAvatarCircle();
// 使用StackPane将文本放在圆形中心
javafx.scene.layout.StackPane avatarStack =
new javafx.scene.layout.StackPane();
avatarStack.getChildren().addAll(avatarCircle, avatarText);
avatarStack.setPrefSize(44, 44);
avatarStack.setAlignment(Pos.CENTER);
avatarContainer.getChildren().add(avatarStack);
return avatarContainer;
}
/**
* .
*
* @return Circle .
*/
private Circle createAvatarCircle() {
Circle avatarCircle = new Circle(22);
avatarCircle.setFill(Color.web("#4CAF50"));
avatarCircle.setStroke(Color.WHITE);
avatarCircle.setStrokeWidth(2);
// 添加阴影效果
avatarCircle.setStyle("-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), "
+ "5, 0.3, 2, 2);");
return avatarCircle;
}
/**
* .
*
* @return VBox .
*/
private VBox createMenuContent() {
VBox menuContent = new VBox(25);
menuContent.setPadding(new Insets(20));
menuContent.setAlignment(Pos.CENTER);
// 创建标题区域
VBox titleSection = createTitleSection();
// 创建功能按钮区域
VBox buttonSection = createButtonSection();
menuContent.getChildren().addAll(titleSection, buttonSection);
return menuContent;
}
/**
* .
*
* @return VBox .
*/
private VBox createTitleSection() {
VBox titleSection = new VBox(10);
titleSection.setAlignment(Pos.CENTER);
Label titleLabel = new Label("数学学习软件");
titleLabel.setFont(Font.font(28));
titleLabel.setStyle("-fx-text-fill: #2c3e50;");
// 修改欢迎标签,显示用户名
welcomeLabel = new Label("欢迎,"
+ (currentUsername != null ? currentUsername : "用户") + "");
welcomeLabel.setFont(Font.font(18));
welcomeLabel.setStyle("-fx-text-fill: #7f8c8d;");
titleSection.getChildren().addAll(titleLabel, welcomeLabel);
return titleSection;
}
/**
* .
*
* @return VBox .
*/
private VBox createButtonSection() {
VBox buttonSection = new VBox(15);
buttonSection.setAlignment(Pos.CENTER);
Button startButton = createStartButton();
Button changePasswordButton = createChangePasswordButton();
Button logoutButton = createLogoutButton();
setupButtonActions(startButton, changePasswordButton, logoutButton);
buttonSection.getChildren().addAll(
startButton, changePasswordButton, logoutButton
);
return buttonSection;
}
/**
* .
*
* @return Button .
*/
private Button createStartButton() {
Button button = new Button("开始练习");
button.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; "
+ "-fx-font-size: 16px; -fx-background-radius: 10;");
button.setPrefSize(220, 55);
setupButtonHoverEffect(button, "#4CAF50", "#45a049");
return button;
}
/**
* .
*
* @return Button .
*/
private Button createChangePasswordButton() {
Button button = new Button("修改密码");
button.setStyle("-fx-background-color: #2196F3; -fx-text-fill: white; "
+ "-fx-font-size: 16px; -fx-background-radius: 10;");
button.setPrefSize(220, 55);
setupButtonHoverEffect(button, "#2196F3", "#1976D2");
return button;
}
/**
* 退.
*
* @return Button 退.
*/
private Button createLogoutButton() {
Button button = new Button("退出登录");
button.setStyle("-fx-background-color: #f44336; -fx-text-fill: white; "
+ "-fx-font-size: 16px; -fx-background-radius: 10;");
button.setPrefSize(220, 55);
setupButtonHoverEffect(button, "#f44336", "#d32f2f");
return button;
}
/**
* .
*
* @param button .
* @param normalColor .
* @param hoverColor .
*/
private void setupButtonHoverEffect(Button button, String normalColor,
String hoverColor) {
String normalStyle = String.format(
"-fx-background-color: %s; -fx-text-fill: white; -fx-font-size: 16px; "
+ "-fx-background-radius: 10;", normalColor);
String hoverStyle = String.format(
"-fx-background-color: %s; -fx-text-fill: white; -fx-font-size: 16px; "
+ "-fx-background-radius: 10;", hoverColor);
button.setOnMouseEntered(e -> button.setStyle(hoverStyle));
button.setOnMouseExited(e -> button.setStyle(normalStyle));
}
/**
* .
*
* @param startButton .
* @param changePasswordButton .
* @param logoutButton 退.
*/
private void setupButtonActions(Button startButton,
Button changePasswordButton, Button logoutButton) {
startButton.setOnAction(e -> sceneManager.showLevelSelectionView());
changePasswordButton.setOnAction(e -> showChangePasswordDialog());
logoutButton.setOnAction(e -> sceneManager.showLoginView());
}
/**
* .
*
* @return String "U".
*/
private String getFirstLetter() {
return currentUsername != null && !currentUsername.isEmpty()
? currentUsername.substring(0, 1).toUpperCase() : "U";
}
/**
* .
*
* @param username .
*/
public void updateUsername(String username) {
this.currentUsername = username;
if (usernameLabel != null) {
usernameLabel.setText(username != null ? username : "用户");
}
if (avatarText != null) {
avatarText.setText(getFirstLetter());
}
if (welcomeLabel != null) {
welcomeLabel.setText("欢迎," + (username != null ? username : "用户") + "");
}
}
/**
* .
*/
private void showChangePasswordDialog() {
javafx.scene.control.Dialog<Void> dialog =
new javafx.scene.control.Dialog<>();
dialog.setTitle("修改密码");
dialog.setHeaderText("请输入密码信息");
dialog.setGraphic(null); // 移除默认图标
// 创建表单
javafx.scene.layout.GridPane grid = createPasswordForm();
dialog.getDialogPane().setContent(grid);
// 添加按钮
dialog.getDialogPane().getButtonTypes().addAll(
javafx.scene.control.ButtonType.OK,
javafx.scene.control.ButtonType.CANCEL
);
dialog.setResultConverter(dialogButton -> {
if (dialogButton == javafx.scene.control.ButtonType.OK) {
handlePasswordChange(grid);
}
return null;
});
dialog.showAndWait();
}
/**
* .
*
* @return GridPane .
*/
private javafx.scene.layout.GridPane createPasswordForm() {
javafx.scene.layout.GridPane grid = new javafx.scene.layout.GridPane();
grid.setHgap(15);
grid.setVgap(15);
grid.setPadding(new Insets(20, 30, 10, 30));
javafx.scene.control.PasswordField oldPassword =
new javafx.scene.control.PasswordField();
oldPassword.setPromptText("请输入原密码");
oldPassword.setPrefWidth(200);
javafx.scene.control.PasswordField newPassword =
new javafx.scene.control.PasswordField();
newPassword.setPromptText("请输入新密码密码要6-10位"
+ "包含大,小写字母和数字)");
newPassword.setPrefWidth(200);
javafx.scene.control.PasswordField confirmPassword =
new javafx.scene.control.PasswordField();
confirmPassword.setPromptText("请确认新密码");
confirmPassword.setPrefWidth(200);
grid.add(new Label("原密码:"), 0, 0);
grid.add(oldPassword, 1, 0);
grid.add(new Label("新密码:"), 0, 1);
grid.add(newPassword, 1, 1);
grid.add(new Label("确认密码:"), 0, 2);
grid.add(confirmPassword, 1, 2);
return grid;
}
/**
* .
*
* @param grid .
*/
private void handlePasswordChange(javafx.scene.layout.GridPane grid) {
javafx.scene.control.PasswordField oldPassword =
(javafx.scene.control.PasswordField) grid.getChildren().get(1);
javafx.scene.control.PasswordField newPassword =
(javafx.scene.control.PasswordField) grid.getChildren().get(3);
javafx.scene.control.PasswordField confirmPassword =
(javafx.scene.control.PasswordField) grid.getChildren().get(5);
String oldPwd = oldPassword.getText();
String newPwd = newPassword.getText();
String confirmPwd = confirmPassword.getText();
if (!validatePasswordInput(oldPwd, newPwd, confirmPwd)) {
return;
}
if (!Login.login(currentUsername, oldPwd)) {
showAlert("错误", "原密码错误!");
return;
}
if (!Register.isVaildPassword(newPwd)) {
showAlert("错误", "密码要6-10位包含大小写字母和数字");
return;
}
if (!ChangePassword.changePassword(currentUsername, confirmPwd)) {
showAlert("错误", "密码修改失败");
} else {
showAlert("成功", "密码修改成功!");
}
}
/**
* .
*
* @param oldPwd .
* @param newPwd .
* @param confirmPwd .
* @return boolean .
*/
private boolean validatePasswordInput(String oldPwd, String newPwd,
String confirmPwd) {
if (oldPwd.isEmpty() || newPwd.isEmpty() || confirmPwd.isEmpty()) {
showAlert("错误", "请填写所有密码字段!");
return false;
}
if (!newPwd.equals(confirmPwd)) {
showAlert("错误", "两次输入的新密码不一致!");
return false;
}
return true;
}
/**
* .
*
* @param title .
* @param message .
*/
private void showAlert(String title, String message) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.showAndWait();
}
/**
* .
*
* @return Scene .
*/
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,219 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
/**
* .
* 10-30.
*/
public class QuestionCountView {
private Scene scene;
private final SceneManager sceneManager;
private String level;
/**
* .
*
* @param sceneManager .
*/
public QuestionCountView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
/**
* .
*/
private void createScene() {
VBox root = new VBox(20);
root.setPadding(new Insets(40));
root.setAlignment(Pos.CENTER);
// 创建标题标签
Label titleLabel = createTitleLabel();
// 创建级别显示标签
Label levelLabel = createLevelLabel();
// 创建数量输入框
TextField countField = createCountField();
// 创建开始答题按钮
Button startButton = createStartButton();
// 创建返回按钮
Button backButton = createBackButton();
// 创建状态标签
Label statusLabel = new Label();
// 设置按钮事件
setupStartButtonAction(startButton, countField, statusLabel);
setupBackButtonAction(backButton);
root.getChildren().addAll(
titleLabel, levelLabel, countField, startButton, backButton, statusLabel
);
scene = new Scene(root, 400, 400);
}
/**
* .
*
* @return Label .
*/
private Label createTitleLabel() {
Label titleLabel = new Label("选择题目数量");
titleLabel.setFont(Font.font(20));
return titleLabel;
}
/**
* .
*
* @return Label .
*/
private Label createLevelLabel() {
Label levelLabel = new Label();
levelLabel.setFont(Font.font(16));
return levelLabel;
}
/**
* .
*
* @return TextField .
*/
private TextField createCountField() {
TextField countField = new TextField();
countField.setPromptText("请输入题目数量 (10-30)");
countField.setMaxWidth(250);
countField.setPrefHeight(40);
return countField;
}
/**
* .
*
* @return Button .
*/
private Button createStartButton() {
Button startButton = new Button("开始答题");
startButton.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; "
+ "-fx-font-size: 14px;");
startButton.setPrefSize(250, 45);
return startButton;
}
/**
* .
*
* @return Button .
*/
private Button createBackButton() {
Button backButton = new Button("返回");
backButton.setStyle("-fx-background-color: #757575; -fx-text-fill: white;");
backButton.setPrefSize(250, 40);
return backButton;
}
/**
* .
*
* @param startButton .
* @param countField .
* @param statusLabel .
*/
private void setupStartButtonAction(Button startButton, TextField countField,
Label statusLabel) {
startButton.setOnAction(e -> handleStartQuiz(countField, statusLabel));
}
/**
* .
*
* @param backButton .
*/
private void setupBackButtonAction(Button backButton) {
backButton.setOnAction(e -> sceneManager.showLevelSelectionView());
}
/**
* .
*
* @param countField .
* @param statusLabel .
*/
private void handleStartQuiz(TextField countField, Label statusLabel) {
try {
String countText = countField.getText().trim();
if (countText.isEmpty()) {
showError(statusLabel, "请输入题目数量!");
return;
}
int count = Integer.parseInt(countText);
if (count < 10 || count > 30) {
showError(statusLabel, "题目数量必须在10-30之间");
return;
}
// 直接调用 showQuizView 并传递参数
sceneManager.showQuizView(level, count);
} catch (NumberFormatException ex) {
showError(statusLabel, "请输入有效的数字!");
}
}
/**
* .
*
* @param level .
*/
public void setLevel(String level) {
this.level = level;
updateLevelDisplay();
}
/**
* .
*/
private void updateLevelDisplay() {
if (scene != null) {
VBox root = (VBox) scene.getRoot();
Label levelLabel = (Label) root.getChildren().get(1);
levelLabel.setText("当前级别: " + level);
levelLabel.setStyle("-fx-text-fill: #2196F3;");
}
}
/**
* .
*
* @param label .
* @param message .
*/
private void showError(Label label, String message) {
label.setText(message);
label.setStyle("-fx-text-fill: red;");
}
/**
* .
*
* @return Scene .
*/
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,338 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import com.wsf.mathapp.service.QuestionService;
import com.ybw.mathapp.entity.QuestionWithOptions;
import com.ybw.mathapp.service.MultipleChoiceGenerator;
import com.ybw.mathapp.service.QuestionGenerator;
import java.util.List;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
/**
* .
* .
*/
public class QuizView {
private Scene scene;
private final SceneManager sceneManager;
private String currentLevel;
private int questionCount;
private List<QuestionWithOptions> questions;
private int correctAnswers = 0;
private Label questionLabel;
private ToggleGroup optionsGroup;
private VBox optionsContainer;
private Button nextButton;
private Label progressLabel;
private Label questionNumberLabel;
/**
* .
*
* @param sceneManager .
*/
public QuizView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
/**
* .
*/
private void createScene() {
VBox root = new VBox(20);
root.setPadding(new Insets(30));
root.setAlignment(Pos.TOP_CENTER);
// 创建界面组件
progressLabel = createProgressLabel();
questionNumberLabel = createQuestionNumberLabel();
questionLabel = createQuestionLabel();
optionsContainer = createOptionsContainer();
nextButton = createNextButton();
root.getChildren().addAll(progressLabel, questionNumberLabel,
questionLabel, optionsContainer, nextButton);
scene = new Scene(root, 600, 500);
}
/**
* .
*
* @return Label .
*/
private Label createProgressLabel() {
Label label = new Label();
label.setFont(Font.font(14));
return label;
}
/**
* .
*
* @return Label .
*/
private Label createQuestionNumberLabel() {
Label label = new Label();
label.setFont(Font.font(16));
label.setStyle("-fx-text-fill: #666;");
return label;
}
/**
* .
*
* @return Label .
*/
private Label createQuestionLabel() {
Label label = new Label();
label.setFont(Font.font(18));
label.setWrapText(true);
label.setStyle("-fx-padding: 10 0 20 0;");
return label;
}
/**
* .
*
* @return VBox .
*/
private VBox createOptionsContainer() {
VBox container = new VBox(15);
container.setAlignment(Pos.CENTER_LEFT);
container.setPadding(new Insets(10));
return container;
}
/**
* .
*
* @return Button .
*/
private Button createNextButton() {
Button button = new Button("下一题");
button.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; "
+ "-fx-font-size: 14px;");
button.setPrefSize(150, 40);
button.setDisable(true);
return button;
}
/**
* .
*
* @param level .
* @param count .
*/
public void setQuizParameters(String level, int count) {
System.out.println("设置测验参数 - 级别: " + level + ", 题目数量: " + count);
this.currentLevel = level;
this.questionCount = count;
this.correctAnswers = 0;
// 生成题目
generateQuestions();
// 检查是否成功生成题目
if (questions == null || questions.isEmpty()) {
System.out.println("题目生成失败,显示空状态");
showEmptyState();
return;
}
System.out.println("成功生成 " + questions.size() + " 道题目,开始显示第一题");
showQuestion(0);
}
/**
* .
*/
private void generateQuestions() {
try {
System.out.println("开始生成题目,级别: " + currentLevel + ", 数量: " + questionCount);
QuestionGenerator questionGenerator = QuestionService.createGenerator(currentLevel);
if (questionGenerator == null) {
System.err.println("题目生成器创建失败,级别: " + currentLevel);
questions = java.util.Collections.emptyList();
return;
}
System.out.println("题目生成器创建成功: "
+ questionGenerator.getClass().getSimpleName());
MultipleChoiceGenerator multipleChoiceGenerator = new MultipleChoiceGenerator(
questionGenerator, currentLevel);
questions = multipleChoiceGenerator.generateMultipleChoiceQuestions(questionCount);
System.out.println("题目生成完成,数量: "
+ (questions != null ? questions.size() : "null"));
} catch (Exception e) {
System.err.println("生成题目时出现异常: " + e.getMessage());
questions = java.util.Collections.emptyList();
}
}
/**
* .
*/
private void showEmptyState() {
VBox root = (VBox) scene.getRoot();
root.getChildren().clear();
Label emptyLabel = new Label("无法生成题目,请返回重试");
emptyLabel.setFont(Font.font(16));
Button backButton = new Button("返回");
backButton.setOnAction(e -> sceneManager.showLevelSelectionView());
root.getChildren().addAll(emptyLabel, backButton);
}
/**
* .
*
* @param index .
*/
private void showQuestion(int index) {
if (index >= questions.size()) {
// 所有题目已回答,显示结果
double score = (double) correctAnswers / questions.size();
sceneManager.showResultView(score);
return;
}
QuestionWithOptions question = questions.get(index);
updateQuestionDisplay(index, question);
setupOptions(question);
setupNextButton(index, question);
}
/**
* .
*
* @param index .
* @param question .
*/
private void updateQuestionDisplay(int index, QuestionWithOptions question) {
// 更新进度和题号
progressLabel.setText("进度: " + (index + 1) + "/" + questions.size());
questionNumberLabel.setText("第 " + (index + 1) + " 题");
// 设置题目内容
String questionText = formatQuestionText(question.getQuestionText());
questionLabel.setText(questionText + " = ?");
}
/**
* .
*
* @param questionText .
* @return String .
*/
private String formatQuestionText(String questionText) {
// 移除末尾的 "="
if (questionText.endsWith(" =")) {
questionText = questionText.substring(0, questionText.length() - 2);
}
return questionText;
}
/**
* .
*
* @param question .
*/
private void setupOptions(QuestionWithOptions question) {
// 清空选项容器
optionsContainer.getChildren().clear();
optionsGroup = new ToggleGroup();
// 添加选项
List<String> options = question.getOptions();
for (int i = 0; i < options.size(); i++) {
RadioButton radioButton = createOptionRadioButton(i, options.get(i));
optionsContainer.getChildren().add(radioButton);
}
}
/**
* .
*
* @param index .
* @param optionText .
* @return RadioButton .
*/
private RadioButton createOptionRadioButton(int index, String optionText) {
RadioButton radioButton = new RadioButton((char) ('A' + index) + ". " + optionText);
radioButton.setToggleGroup(optionsGroup);
radioButton.setFont(Font.font(14));
radioButton.setWrapText(true);
radioButton.setPrefWidth(500);
return radioButton;
}
/**
* .
*
* @param index .
* @param question .
*/
private void setupNextButton(int index, QuestionWithOptions question) {
nextButton.setDisable(true);
nextButton.setText(index == questions.size() - 1 ? "提交答案" : "下一题");
nextButton.setOnAction(e -> {
checkAnswer(question);
showQuestion(index + 1);
});
// 启用下一题按钮当选择了一个选项时
setupOptionSelectionListener();
}
/**
* .
*/
private void setupOptionSelectionListener() {
optionsGroup.selectedToggleProperty().addListener((obs, oldVal, newVal) ->
nextButton.setDisable(newVal == null));
}
/**
* .
*
* @param question .
*/
private void checkAnswer(QuestionWithOptions question) {
RadioButton selectedRadioButton = (RadioButton) optionsGroup.getSelectedToggle();
if (selectedRadioButton != null) {
int selectedIndex = optionsContainer.getChildren().indexOf(selectedRadioButton);
if (selectedIndex == question.getCorrectAnswerIndex()) {
correctAnswers++;
}
}
}
/**
* .
*
* @return Scene .
*/
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,454 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import com.ybw.mathapp.util.EmailService;
import com.ybw.mathapp.util.LoginFileUtils;
import com.ybw.mathapp.util.Register;
import java.util.regex.Pattern;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
/**
* .
* .
*/
public class RegisterView {
private Scene scene;
private final SceneManager sceneManager;
// 将UI组件声明为类的成员变量
private TextField usernameField;
private TextField emailField;
private TextField codeField;
private PasswordField passwordField;
private PasswordField confirmPasswordField;
private Label statusLabel;
private Button sendCodeButton;
/**
* .
*
* @param sceneManager .
*/
public RegisterView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
/**
* .
*/
private void createScene() {
VBox root = new VBox(15);
root.setPadding(new Insets(30));
root.setAlignment(Pos.CENTER);
// 创建界面组件
usernameField = createUsernameField();
emailField = createEmailField();
sendCodeButton = createSendCodeButton();
codeField = createCodeField();
passwordField = createPasswordField();
confirmPasswordField = createConfirmPasswordField();
Button registerButton = createRegisterButton();
statusLabel = new Label();
Button backButton = createBackButton();
// 设置按钮事件
setupSendCodeButtonAction();
setupRegisterButtonAction(registerButton);
setupBackButtonAction(backButton);
Label titleLabel = createTitleLabel();
root.getChildren().addAll(
titleLabel,
usernameField,
emailField,
sendCodeButton,
codeField,
passwordField,
confirmPasswordField,
registerButton,
backButton,
statusLabel
);
scene = new Scene(root, 400, 550);
}
/**
* .
*
* @return Label .
*/
private Label createTitleLabel() {
Label titleLabel = new Label("用户注册");
titleLabel.setFont(Font.font(20));
return titleLabel;
}
/**
* .
*
* @return TextField .
*/
private TextField createUsernameField() {
TextField field = new TextField();
field.setPromptText("请输入用户名(3-20位字母、数字、下划线)");
field.setMaxWidth(300);
field.setPrefHeight(35);
return field;
}
/**
* .
*
* @return TextField .
*/
private TextField createEmailField() {
TextField field = new TextField();
field.setPromptText("请输入邮箱");
field.setMaxWidth(300);
field.setPrefHeight(35);
return field;
}
/**
* .
*
* @return Button .
*/
private Button createSendCodeButton() {
Button button = new Button("发送验证码");
button.setStyle("-fx-background-color: #FF9800; -fx-text-fill: white;");
button.setPrefSize(120, 35);
return button;
}
/**
* .
*
* @return TextField .
*/
private TextField createCodeField() {
TextField field = new TextField();
field.setPromptText("请输入验证码");
field.setMaxWidth(300);
field.setPrefHeight(35);
return field;
}
/**
* .
*
* @return PasswordField .
*/
private PasswordField createPasswordField() {
PasswordField field = new PasswordField();
field.setPromptText("请输入密码(6-10位含大小写字母和数字)");
field.setMaxWidth(300);
field.setPrefHeight(35);
return field;
}
/**
* .
*
* @return PasswordField .
*/
private PasswordField createConfirmPasswordField() {
PasswordField field = new PasswordField();
field.setPromptText("请再次输入密码");
field.setMaxWidth(300);
field.setPrefHeight(35);
return field;
}
/**
* .
*
* @return Button .
*/
private Button createRegisterButton() {
Button button = new Button("注册");
button.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; "
+ "-fx-font-size: 14px;");
button.setPrefSize(300, 40);
return button;
}
/**
* .
*
* @return Button .
*/
private Button createBackButton() {
Button button = new Button("返回登录");
button.setStyle("-fx-background-color: #757575; -fx-text-fill: white;");
button.setPrefSize(300, 35);
return button;
}
/**
* .
*/
private void setupSendCodeButtonAction() {
sendCodeButton.setOnAction(e -> handleSendVerificationCode());
}
/**
* .
*
* @param registerButton .
*/
private void setupRegisterButtonAction(Button registerButton) {
registerButton.setOnAction(e -> handleRegistration());
}
/**
* .
*
* @param backButton .
*/
private void setupBackButtonAction(Button backButton) {
backButton.setOnAction(e -> sceneManager.showLoginView());
}
/**
* .
*/
private void handleSendVerificationCode() {
String username = usernameField.getText().trim();
String email = emailField.getText().trim();
if (!validateUsernameForCode(username)) {
return;
}
if (!validateEmailForCode(email)) {
return;
}
// 生成并发送验证码
boolean sent = EmailService.sendCode(email);
if (sent) {
showSuccess(statusLabel, "验证码已发送到您的邮箱!");
// 禁用发送按钮60秒
sendCodeButton.setDisable(true);
startCountdown(sendCodeButton);
} else {
showError(statusLabel, "发送验证码失败,请稍后重试!");
}
}
/**
* .
*
* @param username .
* @return boolean .
*/
private boolean validateUsernameForCode(String username) {
if (isInvalidUsername(username)) {
showError(statusLabel, "用户名只包含英文字母和数字)");
return false;
}
if (LoginFileUtils.isNameRegistered(username)) {
showError(statusLabel, "用户名已存在,请选择其他用户名!");
return false;
}
return true;
}
/**
* .
*
* @param email .
* @return boolean .
*/
private boolean validateEmailForCode(String email) {
if (!EmailService.isValidEmail(email)) {
showError(statusLabel, "请输入有效的邮箱地址!");
return false;
}
if (LoginFileUtils.isEmailRegistered(email)) {
showError(statusLabel, "该邮箱已注册,请直接登录!");
return false;
}
return true;
}
/**
* .
*/
private void handleRegistration() {
String username = usernameField.getText().trim();
String email = emailField.getText().trim();
String code = codeField.getText().trim();
String password = passwordField.getText();
String confirmPassword = confirmPasswordField.getText();
if (!validateRegistrationInput(username, email, code, password, confirmPassword)) {
return;
}
// 验证验证码
if (!EmailService.verifyCode(email, code)) {
showError(statusLabel, "验证码错误或已过期!");
return;
}
if (!Register.isVaildPassword(password)) {
showError(statusLabel, "密码要6-10位包含大小写字母和数字");
return;
}
if (!Register.isEqualPassword(password, confirmPassword)) {
showError(statusLabel, "两次密码不一致");
return;
}
if (Register.register(username, email, password)) {
showSuccess(statusLabel, "注册成功!用户名: " + username);
sceneManager.setCurrentUserName(username);
sceneManager.showMainMenuView();
} else {
showError(statusLabel, "注册失败,请检查信息!");
}
}
/**
* .
*
* @param username .
* @param email .
* @param code .
* @param password .
* @param confirmPassword .
* @return boolean .
*/
private boolean validateRegistrationInput(String username, String email,
String code, String password, String confirmPassword) {
if (isInvalidUsername(username)) {
showError(statusLabel, "用户名只包含英文字母和数字");
return false;
}
if (LoginFileUtils.isNameRegistered(username)) {
showError(statusLabel, "用户名已存在,请选择其他用户名!");
return false;
}
if (!EmailService.isValidEmail(email)) {
showError(statusLabel, "请输入有效的邮箱地址!");
return false;
}
if (LoginFileUtils.isEmailRegistered(email)) {
showError(statusLabel, "该邮箱已注册,请直接登录!");
return false;
}
if (username.isEmpty() || email.isEmpty() || code.isEmpty()
|| password.isEmpty() || confirmPassword.isEmpty()) {
showError(statusLabel, "请填写所有字段!");
return false;
}
return true;
}
/**
* .
*/
public void clearFields() {
usernameField.clear();
emailField.clear();
codeField.clear();
passwordField.clear();
confirmPasswordField.clear();
statusLabel.setText("");
// 重置发送验证码按钮状态
sendCodeButton.setText("发送验证码");
sendCodeButton.setDisable(false);
}
/**
* .
*
* @param button .
*/
private void startCountdown(Button button) {
new Thread(() -> {
try {
for (int i = 60; i > 0; i--) {
int finalI = i;
javafx.application.Platform.runLater(() -> button.setText(finalI + "秒后重发"));
Thread.sleep(1000);
}
javafx.application.Platform.runLater(() -> {
button.setText("发送验证码");
button.setDisable(false);
});
} catch (InterruptedException ex) {
// ex.printStackTrace();
}
}).start();
}
/**
* .
*
* @param label .
* @param message .
*/
private void showError(Label label, String message) {
label.setText(message);
label.setStyle("-fx-text-fill: red;");
}
/**
* .
*
* @param label .
* @param message .
*/
private void showSuccess(Label label, String message) {
label.setText(message);
label.setStyle("-fx-text-fill: green;");
}
/**
* .
*
* @param username .
* @return boolean .
*/
public static boolean isInvalidUsername(String username) {
if (username == null || username.trim().isEmpty()) {
return true;
}
// 用户名规则:只包含英文字母和数字,不限制长度
String usernameRegex = "^[a-zA-Z0-9]+$";
return !Pattern.matches(usernameRegex, username.trim());
}
/**
* .
*
* @return Scene .
*/
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,278 @@
package com.wsf.mathapp.view;
import com.wsf.mathapp.controller.SceneManager;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
/**
* .
* .
*/
public class ResultView {
private Scene scene;
private final SceneManager sceneManager;
private double score;
/**
* .
*
* @param sceneManager .
*/
public ResultView(SceneManager sceneManager) {
this.sceneManager = sceneManager;
createScene();
}
/**
* .
*/
private void createScene() {
// 创建主容器
VBox mainContainer = new VBox();
mainContainer.setPadding(new Insets(20));
// 创建顶部用户信息栏
HBox userInfoBar = createUserInfoBar();
// 创建结果内容区域
VBox resultContent = createResultContent();
mainContainer.getChildren().addAll(userInfoBar, resultContent);
scene = new Scene(mainContainer, 500, 550);
}
/**
* .
*
* @return HBox .
*/
private HBox createUserInfoBar() {
HBox userInfoBar = new HBox(10);
userInfoBar.setAlignment(Pos.CENTER_LEFT);
userInfoBar.setPadding(new Insets(0, 0, 20, 0));
return userInfoBar;
}
/**
* .
*
* @return VBox .
*/
private VBox createResultContent() {
VBox resultContent = new VBox(30);
resultContent.setPadding(new Insets(20));
resultContent.setAlignment(Pos.CENTER);
// 创建标题区域
Label titleLabel = createTitleLabel();
// 创建分数显示区域
VBox scoreSection = createScoreSection();
// 创建按钮区域
VBox buttonSection = createButtonSection();
resultContent.getChildren().addAll(titleLabel, scoreSection, buttonSection);
return resultContent;
}
/**
* .
*
* @return Label .
*/
private Label createTitleLabel() {
Label titleLabel = new Label("答题完成");
titleLabel.setFont(Font.font(24));
return titleLabel;
}
/**
* .
*
* @return VBox .
*/
private VBox createScoreSection() {
VBox scoreSection = new VBox(15);
scoreSection.setAlignment(Pos.CENTER);
Label scoreLabel = new Label();
scoreLabel.setFont(Font.font(48));
Label messageLabel = new Label();
messageLabel.setFont(Font.font(18));
scoreSection.getChildren().addAll(scoreLabel, messageLabel);
return scoreSection;
}
/**
* .
*
* @return VBox .
*/
private VBox createButtonSection() {
VBox buttonSection = new VBox(15);
buttonSection.setAlignment(Pos.CENTER);
Button restartButton = createRestartButton();
Button mainMenuButton = createMainMenuButton();
Button exitButton = createExitButton();
setupButtonActions(restartButton, mainMenuButton, exitButton);
buttonSection.getChildren().addAll(restartButton, mainMenuButton, exitButton);
return buttonSection;
}
/**
* .
*
* @return Button .
*/
private Button createRestartButton() {
Button button = new Button("再次练习");
button.setStyle("-fx-background-color: #4CAF50; -fx-text-fill: white; "
+ "-fx-font-size: 16px;");
button.setPrefSize(200, 50);
return button;
}
/**
* .
*
* @return Button .
*/
private Button createMainMenuButton() {
Button button = new Button("返回主菜单");
button.setStyle("-fx-background-color: #2196F3; -fx-text-fill: white; "
+ "-fx-font-size: 16px;");
button.setPrefSize(200, 50);
return button;
}
/**
* 退.
*
* @return Button 退.
*/
private Button createExitButton() {
Button button = new Button("退出系统");
button.setStyle("-fx-background-color: #757575; -fx-text-fill: white; "
+ "-fx-font-size: 16px;");
button.setPrefSize(200, 50);
return button;
}
/**
* .
*
* @param restartButton .
* @param mainMenuButton .
* @param exitButton 退.
*/
private void setupButtonActions(Button restartButton, Button mainMenuButton,
Button exitButton) {
restartButton.setOnAction(e -> sceneManager.showLevelSelectionView());
mainMenuButton.setOnAction(e -> sceneManager.showMainMenuView());
exitButton.setOnAction(e -> System.exit(0));
}
/**
* .
*
* @param score 0-1.
*/
public void setScore(double score) {
this.score = score;
updateScoreDisplay();
}
/**
* .
*/
private void updateScoreDisplay() {
if (scene != null) {
VBox mainContainer = (VBox) scene.getRoot();
VBox resultContent = (VBox) mainContainer.getChildren().get(1);
VBox scoreSection = (VBox) resultContent.getChildren().get(1);
Label scoreLabel = (Label) scoreSection.getChildren().get(0);
Label messageLabel = (Label) scoreSection.getChildren().get(1);
updateScoreLabel(scoreLabel);
updateMessageLabel(messageLabel);
}
}
/**
* .
*
* @param scoreLabel .
*/
private void updateScoreLabel(Label scoreLabel) {
int percentage = (int) (score * 100);
scoreLabel.setText(percentage + "分");
applyScoreColor(scoreLabel);
}
/**
* .
*
* @param messageLabel .
*/
private void updateMessageLabel(Label messageLabel) {
messageLabel.setText(getScoreMessage());
}
/**
* .
*
* @param scoreLabel .
*/
private void applyScoreColor(Label scoreLabel) {
if (score >= 0.9) {
scoreLabel.setStyle("-fx-text-fill: #4CAF50;");
} else if (score >= 0.7) {
scoreLabel.setStyle("-fx-text-fill: #FF9800;");
} else if (score >= 0.6) {
scoreLabel.setStyle("-fx-text-fill: #FFC107;");
} else {
scoreLabel.setStyle("-fx-text-fill: #f44336;");
}
}
/**
* .
*
* @return String .
*/
private String getScoreMessage() {
if (score >= 0.9) {
return "优秀!表现非常出色!";
} else if (score >= 0.7) {
return "良好!继续加油!";
} else if (score >= 0.6) {
return "及格!还有进步空间!";
} else {
return "需要多加练习!";
}
}
/**
* .
*
* @return Scene .
*/
public Scene getScene() {
return scene;
}
}

@ -0,0 +1,13 @@
package com.ybw.mathapp.config;
public class EmailConfig {
// 发件人邮箱配置以QQ邮箱为例
public static final String SMTP_HOST = "smtp.qq.com";
public static final String SMTP_PORT = "587";
public static final String SENDER_EMAIL = "1798231811@qq.com"; // 替换为你的邮箱
public static final String SENDER_PASSWORD = "dzmfirotgnlceeae"; // 替换为你的授权码
public static final String EMAIL_SUBJECT = "【用户注册】验证码";
public static final int CODE_EXPIRY_MINUTES = 5;
}

@ -0,0 +1,119 @@
package com.ybw.mathapp.entity;
import java.util.List;
/**
*
*
* @author
* @since 2025
*/
public class QuestionWithOptions {
/**
*
*/
private String questionText;
/**
*
*/
private List<String> options;
/**
* (0-based)
*/
private int correctAnswerIndex;
/**
*
*
* @param questionText
* @param options
* @param correctAnswerIndex (0-based)
* @throws IllegalArgumentException options null
*/
public QuestionWithOptions(String questionText, List<String> options, int correctAnswerIndex) {
if (options == null) {
throw new IllegalArgumentException("选项列表不能为 null");
}
if (correctAnswerIndex < 0 || correctAnswerIndex >= options.size()) {
throw new IllegalArgumentException("正确答案索引超出选项范围");
}
this.questionText = questionText;
this.options = options;
this.correctAnswerIndex = correctAnswerIndex;
}
/**
*
*
* @return
*/
public String getQuestionText() {
return questionText;
}
/**
*
*
* @param questionText
*/
public void setQuestionText(String questionText) {
this.questionText = questionText;
}
/**
*
*
* @return
*/
public List<String> getOptions() {
return options;
}
/**
*
*
* @param options
* @throws IllegalArgumentException options null
*/
public void setOptions(List<String> options) {
if (options == null) {
throw new IllegalArgumentException("选项列表不能为 null");
}
this.options = options;
}
/**
*
*
* @return (0-based)
*/
public int getCorrectAnswerIndex() {
return correctAnswerIndex;
}
/**
*
*
* @param correctAnswerIndex (0-based)
* @throws IllegalArgumentException
*/
public void setCorrectAnswerIndex(int correctAnswerIndex) {
if (correctAnswerIndex < 0 || correctAnswerIndex >= options.size()) {
throw new IllegalArgumentException("正确答案索引超出选项范围");
}
this.correctAnswerIndex = correctAnswerIndex;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Question: ").append(questionText).append("\n");
for (int i = 0; i < options.size(); i++) {
sb.append("Option ").append((char) ('A' + i)).append(": ").append(options.get(i))
.append("\n");
}
sb.append("Correct Answer: ").append((char) ('A' + correctAnswerIndex));
return sb.toString();
}
}

@ -0,0 +1,134 @@
package com.ybw.mathapp.entity;
/**
*
*
* <p>
*
*
* @author
* @version 1.0
* @since 2025
*/
public class User {
/**
*
*/
private final String name;
/**
*
*/
private final String email;
/**
*
*/
private final String password;
/**
*
*/
private String level;
/**
*
*
* @param name
* @param email
* @param password
* @throws IllegalArgumentException nameemail password null
*/
public User(String name, String email, String password) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
if (email == null || email.trim().isEmpty()) {
throw new IllegalArgumentException("邮箱不能为空");
}
if (password == null || password.trim().isEmpty()) {
throw new IllegalArgumentException("密码不能为空");
}
this.name = name;
this.email = email;
this.password = password;
}
/**
*
*
* <p> "name,email,password"
*
* @param line
* @return null
*/
public static User fromString(String line) {
if (line == null || line.trim().isEmpty()) {
return null;
}
String[] parts = line.split(",", 3); // 最多分割成3部分
if (parts.length == 3) {
return new User(parts[0].trim(), parts[1].trim(), parts[2].trim());
}
return null;
}
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @return
*/
public String getPassword() {
return password;
}
/**
*
*
* @return
*/
public String getLevel() {
return level;
}
/**
*
*
* @param newLevel "小学""初中""高中"
*/
public void setLevel(String newLevel) {
level = newLevel;
}
/**
*
*
* @return
*/
public String getEmail() {
return email;
}
/**
*
*
* <p> "name,email,password"
*
* @return
*/
@Override
public String toString() {
return name + "," + email + "," + password;
}
}

@ -0,0 +1,248 @@
package com.ybw.mathapp.service;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/**
* (sin, cos, tan)
*
* <p>
* <ol>
* <li> {@code ( )} </li>
* <li> (, , sin, cos, tan) </li>
* <li></li>
* </ol>
*
* <p>:
* <ul>
* <li>{@code "3 + 开根号 16 平方"} {@code "3 + (sqrt(16))^2"} = 19</li>
* <li>{@code "开根号 ( 4 + 5 ) 平方"} {@code "(sqrt(4+5))^2"} = 9</li>
* <li>{@code "sin 30 + 5"} {@code "sin(30) + 5"} (30)</li>
* </ul>
*
* <p>: ArithmeticException
*
* @author
* @since 2025
*/
public class AdvancedCaculate {
private static final Set<String> BASIC_OPERATORS = new HashSet<>(
Arrays.asList("+", "-", "*", "/"));
private static final Set<String> ADVANCED_OPERATORS = new HashSet<>(
Arrays.asList("平方", "开根号", "sin", "cos", "tan"));
private static final Map<String, Integer> PRECEDENCE = new HashMap<>();
static {
PRECEDENCE.put("+", 1);
PRECEDENCE.put("-", 1);
PRECEDENCE.put("*", 2);
PRECEDENCE.put("/", 2);
// 高级运算符优先级更高
PRECEDENCE.put("平方", 3); // 后置
PRECEDENCE.put("开根号", 3); // 前置
PRECEDENCE.put("sin", 3); // 前置
PRECEDENCE.put("cos", 3); // 前置
PRECEDENCE.put("tan", 3); // 前置
}
/**
*
*
* <p>: ["3", "+", "开根号", "16", "平方"]
*
* @param expressionTokens
* @return
* @throws IllegalArgumentException
* @throws ArithmeticException
*/
public static double calculate(List<String> expressionTokens) {
Stack<Double> numberStack = new Stack<>();
Stack<String> operatorStack = new Stack<>();
for (String token : expressionTokens) {
if (isNumeric(token)) {
numberStack.push(Double.parseDouble(token));
} else if (token.equals("(")) {
operatorStack.push(token);
} else if (token.equals(")")) {
handleClosingParenthesis(numberStack, operatorStack);
} else if (ADVANCED_OPERATORS.contains(token)) {
handleAdvancedOperator(token, numberStack, operatorStack);
} else if (BASIC_OPERATORS.contains(token)) {
handleBasicOperator(token, numberStack, operatorStack);
} else {
throw new IllegalArgumentException("Unknown token: " + token);
}
}
// 处理栈中剩余的所有运算符
while (!operatorStack.isEmpty()) {
String op = operatorStack.pop();
if (op.equals("(") || op.equals(")")) {
throw new IllegalArgumentException("Mismatched parentheses in expression.");
}
processOperator(numberStack, op);
}
if (numberStack.size() != 1 || !operatorStack.isEmpty()) {
throw new IllegalArgumentException(
"Invalid expression: " + String.join(" ", expressionTokens));
}
return numberStack.pop();
}
/**
* ')'
*/
private static void handleClosingParenthesis(Stack<Double> numberStack,
Stack<String> operatorStack) {
while (!operatorStack.isEmpty() && !operatorStack.peek().equals("(")) {
processOperator(numberStack, operatorStack.pop());
}
if (!operatorStack.isEmpty()) { // Pop the "("
operatorStack.pop();
} else {
throw new IllegalArgumentException("Mismatched parentheses in expression.");
}
}
/**
* , sin, cos, tan,
*/
private static void handleAdvancedOperator(String token, Stack<Double> numberStack,
Stack<String> operatorStack) {
if ("平方".equals(token)) {
if (numberStack.isEmpty()) {
throw new IllegalArgumentException("Invalid expression: '平方' lacks an operand.");
}
double operand = numberStack.pop();
numberStack.push(Math.pow(operand, 2));
} else { // "开根号", "sin", "cos", "tan" 是前置运算符
operatorStack.push(token);
}
}
/**
* +, -, *, /
*/
private static void handleBasicOperator(String token, Stack<Double> numberStack,
Stack<String> operatorStack) {
// 处理四则运算符,遵循优先级
while (!operatorStack.isEmpty()
&& !operatorStack.peek().equals("(")
&& PRECEDENCE.get(token) <= PRECEDENCE.getOrDefault(operatorStack.peek(), 0)) {
processOperator(numberStack, operatorStack.pop());
}
operatorStack.push(token);
}
/**
*
*
* @param numberStack
* @param operator
* @throws IllegalArgumentException
* @throws ArithmeticException
*/
private static void processOperator(Stack<Double> numberStack, String operator) {
if (numberStack.isEmpty()) {
throw new IllegalArgumentException(
"Invalid expression: operator '" + operator + "' lacks operand(s).");
}
if (ADVANCED_OPERATORS.contains(operator)) {
processAdvancedOperator(numberStack, operator);
} else if (BASIC_OPERATORS.contains(operator)) {
processBasicOperator(numberStack, operator);
} else {
throw new IllegalArgumentException("Unexpected operator in process: " + operator);
}
}
/**
* , sin, cos, tan
*/
private static void processAdvancedOperator(Stack<Double> numberStack, String operator) {
double operand = numberStack.pop();
double result;
switch (operator) {
case "开根号":
if (operand < 0) {
// 抛出异常让调用者MultipleChoiceGenerator处理
throw new ArithmeticException("Cannot take square root of negative number: " + operand);
}
result = Math.sqrt(operand);
break;
case "sin":
result = Math.sin(Math.toRadians(operand)); // 假设输入是度数
break;
case "cos":
result = Math.cos(Math.toRadians(operand));
break;
case "tan":
// tan(90 + n*180) 会趋向无穷,这里不特别处理,让其返回 Infinity 或 -Infinity
result = Math.tan(Math.toRadians(operand));
break;
default:
throw new IllegalArgumentException("Unknown advanced operator: " + operator);
}
numberStack.push(result);
}
/**
* +, -, *, /
*/
private static void processBasicOperator(Stack<Double> numberStack, String operator) {
if (numberStack.size() < 2) {
throw new IllegalArgumentException(
"Invalid expression: operator '" + operator + "' lacks operand(s).");
}
double b = numberStack.pop();
double a = numberStack.pop();
double result;
switch (operator) {
case "+":
result = a + b;
break;
case "-":
result = a - b;
break;
case "*":
result = a * b;
break;
case "/":
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
result = a / b;
break;
default:
throw new IllegalArgumentException("Unknown operator: " + operator);
}
numberStack.push(result);
}
/**
*
*
* @param str
* @return true false
*/
public static boolean isNumeric(String str) {
if (str == null || str.isEmpty()) {
return false;
}
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

@ -0,0 +1,214 @@
package com.ybw.mathapp.service;
import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
*
*
* <p>
*
*
* @author
* @version 1.1
* @since 2025
*/
public class JuniorHighGenerator implements QuestionGenerator {
/**
* "平方""开根号"
*/
private static final String[] ADVANCED_OPS = {"平方", "开根号"};
/**
*
*/
private static final String[] OPERATORS = {"+", "-", "*", "/"};
/**
*
*/
private final Random random = new Random();
@Override
public List<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
*
*
* <p>
*
*
* @return
*/
private String generateSingleQuestion() {
List<String> parts = new ArrayList<>();
int operandCount = random.nextInt(5) + 1;
parts = generateBase(operandCount, parts);
boolean hasAdvancedOp = false;
if (operandCount == 1) {
// 对于单个操作数,直接添加高级运算符
if ("平方".equals(ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)])) {
parts.add("平方");
} else {
// 为单个操作数的开根号添加括号
parts.set(0, "开根号 ( " + parts.get(0) + " )");
}
hasAdvancedOp = true;
} else {
// 遍历所有可能的操作数位置 (索引为偶数)
// 修复:循环条件确保最后一个操作数也能被检查
for (int i = 0; i < parts.size(); i += 2) { // 只检查操作数索引 (0, 2, 4, ...)
// 该位置要为操作数且随机添加括号
if (isNumeric(parts.get(i)) && random.nextBoolean()) {
// 随机选择高级运算符
if ("开根号".equals(ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)])) {
parts = generateRoot(parts, i);
} else { // 平方运算
parts = generateSquare(parts, i);
}
hasAdvancedOp = true;
break; // 添加成功后退出循环
}
}
}
// 启动保底强制加入一个高级运算符
if (!hasAdvancedOp) {
parts = forceAddAdvancedOp(parts);
}
return String.join(" ", parts) + " =";
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
public List<String> generateBase(int operandCount, List<String> parts) {
for (int i = 0; i < operandCount; i++) {
int num = random.nextInt(100) + 1;
parts.add(String.valueOf(num));
if (i < operandCount - 1) {
parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
}
}
return parts;
}
/**
*
*
* <p>使
*
*
* @param parts
* @return
*/
public List<String> forceAddAdvancedOp(List<String> parts) {
String advancedOp = ADVANCED_OPS[random.nextInt(ADVANCED_OPS.length)];
if ("平方".equals(advancedOp)) {
// 对整个表达式进行平方
parts.add(0, "("); // 在开头添加左括号
parts.add(") 平方"); // 在末尾添加右括号和"平方"
} else { // 开根号
// 对整个表达式进行开根号
parts.add(0, "开根号 ( "); // 在开头添加"开根号 ( "
parts.add(" )"); // 在末尾添加" )"
}
return parts;
}
/**
*
*
* <p>
*
*
* @param parts
* @param i ()
* @return
*/
public List<String> generateRoot(List<String> parts, int i) {
if (random.nextBoolean()) {
// 对单个操作数进行开根号
parts.set(i, "开根号 ( " + parts.get(i) + " )");
} else {
// 对子表达式进行开根号
parts.set(i, "开根号 ( " + parts.get(i)); // 在起始操作数前添加左括号和"开根号 ("
int endIndex = findMatchingEndIndex(parts, i); // 查找匹配的结束操作数索引
String currentEnd = parts.get(endIndex);
parts.set(endIndex, currentEnd + " )"); // 在结束操作数后添加右括号
}
return parts;
}
/**
*
*
* <p>
*
* @param parts
* @param i ()
* @return
*/
public List<String> generateSquare(List<String> parts, int i) {
parts.set(i, "(" + parts.get(i)); // 在起始操作数前添加左括号
int endIndex = findMatchingEndIndex(parts, i); // 查找匹配的结束操作数索引
String currentEnd = parts.get(endIndex);
parts.set(endIndex, currentEnd + " ) 平方"); // 在结束操作数后添加右括号和"平方"
return parts;
}
/**
* i () i+2
*
*
* @param parts
* @param i
* @return
*/
private int findMatchingEndIndex(List<String> parts, int i) {
// 获取最后一个操作数的索引
int lastOperandIndex = parts.size() - 1;
if (lastOperandIndex % 2 != 0) {
lastOperandIndex--; // 如果列表长度是偶数最后一个元素是运算符需要减1得到最后一个操作数索引
}
// 确保范围是有效的操作数索引
if (i >= lastOperandIndex) {
return lastOperandIndex; // 如果i已经是最后一个或超出返回最后一个
}
// 在 [i+2, lastOperandIndex] 范围内选择一个操作数索引 (步长为2)
// 确保至少包含一个运算符所以从i+2开始
List<Integer> possibleEndIndices = new ArrayList<>();
for (int idx = i + 2; idx <= lastOperandIndex; idx += 2) {
possibleEndIndices.add(idx);
}
if (!possibleEndIndices.isEmpty()) {
// 随机选择一个可能的结束索引
return possibleEndIndices.get(random.nextInt(possibleEndIndices.size()));
} else {
// 理论上不应该到达这里,如果到达则返回起始索引
return i;
}
}
}

@ -0,0 +1,252 @@
package com.ybw.mathapp.service;
import com.ybw.mathapp.entity.QuestionWithOptions;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
*
*
* <p>
*
* <ul>
* <li> {@link AdvancedCaculate} </li>
* <li></li>
* </ul>
*
* @author
* @since 2025
*/
public class MultipleChoiceGenerator {
/**
*
*/
private static final DecimalFormat df = new DecimalFormat("#0.00");
/**
*
*/
private final QuestionGenerator baseGenerator;
/**
*
*/
private final Random random = new Random();
/**
*
*/
private final String level;
/**
*
*
* @param baseGenerator
* @param level ("小学", "初中", "高中")
*/
public MultipleChoiceGenerator(QuestionGenerator baseGenerator, String level) {
this.baseGenerator = baseGenerator;
this.level = level;
}
/**
*
*
* @param count
* @return
*/
public List<QuestionWithOptions> generateMultipleChoiceQuestions(int count) {
List<QuestionWithOptions> mcQuestions = new ArrayList<>();
Set<String> seenQuestionTexts = new HashSet<>(); // 用于存储已生成的题干,保证唯一性
while (mcQuestions.size() < count) {
String baseQuestion = generateUniqueBaseQuestion(seenQuestionTexts);
if (baseQuestion == null) {
// 如果无法生成不重复的基础题目,可能基础生成器的可能组合已用尽
break; // 退出循环
}
QuestionWithOptions mcq = generateSingleMcq(baseQuestion);
if (mcq != null) {
mcQuestions.add(mcq);
seenQuestionTexts.add(baseQuestion); // 添加成功生成的题干
}
// 如果 generateSingleMCQ 返回 null说明计算或生成选项失败循环会继续尝试下一个基础题目
}
return mcQuestions;
}
/**
*
*
* @param seenQuestionTexts
* @return null
*/
private String generateUniqueBaseQuestion(Set<String> seenQuestionTexts) {
int attempts = 0;
int maxAttempts = 1000; // 防止无限循环,如果生成太多重复题则退出
while (attempts < maxAttempts) {
List<String> baseQuestionList = baseGenerator.generateQuestions(1);
String baseQuestion = baseQuestionList.get(0);
if (!seenQuestionTexts.contains(baseQuestion)) {
return baseQuestion;
}
attempts++;
}
return null; // 达到最大尝试次数仍未找到唯一题目
}
/**
*
*
* @param baseQuestion "3 + 5 = ?"
* @return null
*/
private QuestionWithOptions generateSingleMcq(String baseQuestion) {
try {
// 从基础题干中提取表达式部分,例如 "3 + 5 = ?" -> "3 + 5"
String expression = baseQuestion.substring(0, baseQuestion.lastIndexOf(" =")).trim();
List<String> tokens = tokenizeExpression(expression);
// 计算正确答案
double correctAnswer = AdvancedCaculate.calculate(tokens);
// 生成选项列表
List<String> options = generateOptions(correctAnswer);
if (options == null) {
// 无法生成足够的有效选项
return null;
}
// 随机打乱选项顺序
Collections.shuffle(options);
// 找到正确答案在打乱后列表中的索引
int correctIndex = options.indexOf(df.format(correctAnswer));
return new QuestionWithOptions(baseQuestion, options, correctIndex);
} catch (ArithmeticException | IllegalArgumentException e) {
// 计算或表达式格式错误,跳过此题
// System.out.println("计算或表达式错误,跳过题目: " + baseQuestion + ", Error: " + e.getMessage());
return null; // 返回 null 表示生成失败
}
}
/**
* ( + )
*
* <p>
*
* @param correctAnswer
* @return null
*/
private List<String> generateOptions(double correctAnswer) {
Set<Double> wrongAnswers = new HashSet<>();
int attempts = 0;
int maxAttempts = 100; // 防止无限循环
int numWrongOptions = 3; // 假设总共4个选项需要3个错误答案
while (wrongAnswers.size() < numWrongOptions && attempts < maxAttempts) {
int offset = random.nextInt(20) + 1; // 生成 1-20 的偏移量
if (random.nextBoolean()) {
offset = -offset; // 随机正负
}
double wrongAnswer = correctAnswer + offset;
// 确保错误答案与正确答案不同,并且对于小学题不为负数
if (Math.abs(df.format(wrongAnswer).compareTo(df.format(correctAnswer))) != 0) {
if (!level.equals("小学") || wrongAnswer >= 0) {
wrongAnswers.add(wrongAnswer);
}
}
attempts++;
}
if (wrongAnswers.size() < numWrongOptions) {
return null; // 无法生成足够选项
}
// 将正确答案和错误答案合并
List<Double> allAnswers = new ArrayList<>();
allAnswers.add(correctAnswer);
allAnswers.addAll(wrongAnswers);
// 格式化所有答案为字符串
List<String> options = new ArrayList<>();
for (Double ans : allAnswers) {
options.add(df.format(ans));
}
return options;
}
// ... (其他类成员和方法保持不变)
// --- 表达式分词逻辑 ---
// 将 "3 + 开根号 ( 4 ) 平方 - sin 30" 分割成 ["3", "+", "开根号", "(", "4", ")", "平方", "-", "sin", "30"]
/**
*
*
* @param expression
* @return
*/
private List<String> tokenizeExpression(String expression) {
List<String> tokens = new ArrayList<>();
int i = 0;
while (i < expression.length()) {
String token = findNextToken(expression, i);
if (token != null) {
tokens.add(token);
i += token.length();
} else {
i++;
} /* else {
// 如果找不到匹配的 token可能是单个字符或未知格式
tokens.add(String.valueOf(expression.charAt(i)));
i++;
}
*/
}
return tokens;
}
/**
* token
*
* @param expression
* @param startPos
* @return token null
*/
private String findNextToken(String expression, int startPos) {
char c = expression.charAt(startPos);
if (Character.isWhitespace(c)) {
return null; // 空格由调用者处理
}
if (c == '(' || c == ')') {
return String.valueOf(c);
}
if (Character.isDigit(c) || c == '.') {
// 查找连续的数字或小数点
int j = startPos;
while (j < expression.length() && (Character.isDigit(expression.charAt(j))
|| expression.charAt(j) == '.')) {
j++;
}
return expression.substring(startPos, j);
}
String[] advancedOps = {"平方", "开根号", "sin", "cos", "tan"};
// 检查多字符运算符
for (String op : advancedOps) {
if (expression.startsWith(op, startPos)) {
return op;
}
}
// 如果不是多字符运算符,则认为是单字符运算符 (+, -, *, / 等)
return String.valueOf(c);
}
}

@ -0,0 +1,106 @@
package com.ybw.mathapp.service;
import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
*
*
* <p>
*
*
* @author
* @version 1.0
* @since 2025
*/
public class PrimarySchoolGenerator implements QuestionGenerator {
/** 运算符数组,包含四则运算符号。 */
private static final String[] OPERATORS = {"+", "-", "*", "/"};
/** 随机数生成器,用于生成随机题目。 */
private final Random random = new Random();
@Override
public List<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
* .
*
* <p>2-5
*
*
* @return
*/
private String generateSingleQuestion() {
int operandCount = random.nextInt(4) + 2; // 2-5个操作数
List<String> parts = new ArrayList<>();
while (true) {
// 生成基础操作
parts = generateBase(operandCount, parts);
// 简单添加括号逻辑:随机加一个括号
if (operandCount > 2 && random.nextBoolean()) {
// 遍历查找左括号的合理位置
for (int i = 0; i < parts.size() - 2; i++) {
// 该位置要为操作数且随机添加括号
if (isNumeric(parts.get(i)) && random.nextBoolean()) {
parts.add(i, "(");
i++;
// 为避免随机数上限出现0此处要单独判断一下左括号正好括住倒数第二个操作数的情况
if (i == parts.size() - 3) {
parts.add(")");
} else {
while (true) {
int i2 = random.nextInt(parts.size() - 3 - i) + 2;
if (isNumeric(parts.get(i + i2))) {
parts.add(i + i2 + 1, ")");
break;
}
}
}
break;
}
}
}
try {
if (AdvancedCaculate.calculate(parts) >= 0) {
return String.join(" ", parts) + " =";
} else {
parts.clear();
}
} catch (ArithmeticException | IllegalArgumentException e) {
parts.clear();
}
}
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
public List<String> generateBase(int operandCount, List<String> parts) {
for (int i = 0; i < operandCount; i++) {
int num = random.nextInt(100) + 1;
parts.add(String.valueOf(num));
if (i < operandCount - 1) {
parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
}
}
return parts;
}
}

@ -0,0 +1,29 @@
package com.ybw.mathapp.service;
import java.util.List;
/**
*
*
* <p>
*
*
*
* @author
* @version 1.0
* @since 2025
*/
public interface QuestionGenerator {
/**
*
*
* <p>
*
*
* @param count
* @return
*/
List<String> generateQuestions(int count);
}

@ -0,0 +1,127 @@
package com.ybw.mathapp.service;
import static com.ybw.mathapp.service.AdvancedCaculate.isNumeric;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
*
*
* <p>sincostan
*
*
* @author
* @version 1.0
* @since 2025
*/
public class SeniorHighGenerator implements QuestionGenerator {
/** 三角函数运算符数组,包含"sin"、"cos"和"tan"。 */
private static final String[] TRIG_FUNCS = {"sin", "cos", "tan"};
/** 基本运算符数组,包含四则运算符号。 */
private static final String[] OPERATORS = {"+", "-", "*", "/"};
/** 随机数生成器,用于生成随机题目。 */
private final Random random = new Random();
@Override
public List<String> generateQuestions(int count) {
List<String> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
String question = generateSingleQuestion();
questions.add(question);
}
return questions;
}
/**
*
*
* <p>
*
*
* @return
*/
private String generateSingleQuestion() {
List<String> parts = new ArrayList<>();
int operandCount = random.nextInt(5) + 1;
parts = generateBase(operandCount, parts);
String advancedOp;
if (operandCount == 1) {
advancedOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
parts.set(0, advancedOp + " " + parts.get(0));
} else {
// 遍历查找左括号的合理位置
for (int i = 0; i < parts.size(); i++) {
// 最后一次循环保底生成高中三角函数
if (i == parts.size() - 1) {
advancedOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
parts.set(i, advancedOp + " " + parts.get(i));
} else if (isNumeric(parts.get(i)) && random.nextBoolean()) { // 随机数看是否为操作数且随即进入生成程序
// 进入随机生成tan\sin\cos的程序
parts = generateTrig(parts, i);
break;
}
}
}
return String.join(" ", parts) + " =";
}
/**
*
*
* <p>
*
* @param operandCount
* @param parts
* @return
*/
// 产生基本操作
public List<String> generateBase(int operandCount, List<String> parts) {
for (int i = 0; i < operandCount; i++) {
int num = random.nextInt(100) + 1;
parts.add(String.valueOf(num));
if (i < operandCount - 1) {
parts.add(OPERATORS[random.nextInt(OPERATORS.length)]);
}
}
return parts;
}
/**
*
*
* <p>
*
*
* @param parts
* @param i
* @return
*/
// 产生三角函数运算符
public List<String> generateTrig(List<String> parts, int i) {
String trigOp = TRIG_FUNCS[random.nextInt(TRIG_FUNCS.length)];
if (random.nextBoolean()) {
parts.set(i, trigOp + " " + parts.get(i));
} else {
parts.set(i, trigOp + " ( " + parts.get(i));
// 为避免随机数上限出现0此处要单独判断一下左括号正好括住倒数第二个操作数的情况
if (i == parts.size() - 3) {
parts.set(parts.size() - 1, parts.get(parts.size() - 1) + " )");
} else {
while (true) {
int i2 = random.nextInt(parts.size() - 3 - i) + 2;
if (isNumeric(parts.get(i + i2))) {
parts.set(i + i2, parts.get(i + i2) + " )");
break;
}
}
}
}
return parts;
}
}

@ -0,0 +1,139 @@
package com.ybw.mathapp.util;
import static com.ybw.mathapp.util.LoginFileUtils.USER_FILE;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
*
*
* <p>
*
*
* @author
* @since 2025
*/
public class ChangePassword {
/**
*
*/
private static final List<String> lines = new ArrayList<>();
/**
*
*/
private static String userLine = null;
/**
* 0
*/
private static int userLineNumber = -1;
/**
*
*
* <p>
*
*
* @param name
* @param newPassword
* @return true false
*/
public static boolean changePassword(String name, String newPassword) {
File file = new File(USER_FILE);
if (!file.exists()) {
System.out.println("用户文件不存在: " + USER_FILE);
return false;
}
// 1. 读取文件,查找用户
lines.clear(); // 清空上一次的缓存
userLine = null;
userLineNumber = -1;
if (!findUserLine(name, file)) {
return false;
}
if (userLine == null || userLineNumber == -1) {
// 用户未找到
System.out.println("用户 '" + name + "' 不存在,修改失败。");
return false;
}
// 2. 更新找到的用户行中的密码
String[] parts = userLine.split(",");
if (parts.length != 3) {
System.err.println("用户文件中用户 '" + name + "' 的数据格式不正确,无法修改密码。");
return false;
}
parts[2] = newPassword; // 假设密码是第三个字段
String updatedLine = String.join(",", parts);
lines.set(userLineNumber, updatedLine); // 替换列表中的旧行
// 3. 将更新后的内容写回文件
if (!writeBack(lines, file)) {
return false;
}
return true;
}
/**
*
*
* @param lines
* @param file
* @return true false
*/
private static boolean writeBack(List<String> lines, File file) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
System.err.println("写入文件时出错: " + e.getMessage());
return false; // 如果写回失败,认为修改未成功
}
return true;
}
/**
*
*
* <p> {@code lines}
* {@code userLine} {@code userLineNumber}
*
* @param name
* @param file
* @return true false
*/
private static boolean findUserLine(String name, File file) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
int currentLineNum = 0;
while ((line = reader.readLine()) != null) {
lines.add(line);
String[] parts = line.split(",");
// 假设格式为: username,email,password
if (parts.length >= 3 && parts[0].trim().equals(name.trim())) {
userLine = line; // 找到用户行
userLineNumber = currentLineNum;
// break; // 找到后可以退出循环,但为了读取所有行到 lines不在此处 break
}
currentLineNum++;
}
} catch (IOException e) {
System.err.println("读取文件时出错: " + e.getMessage());
return false;
}
return true;
}
}

@ -0,0 +1,250 @@
package com.ybw.mathapp.util;
import com.ybw.mathapp.config.EmailConfig;
import jakarta.mail.Authenticator;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.PasswordAuthentication;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
/**
*
*
* <p>
* {@link EmailConfig}
*
* @author
* @since 2025
*/
public class EmailService {
/**
*
*/
private static final Map<String, VerificationCodeInfo> verificationCodes = new HashMap<>();
/**
* 6
*
* @return 6
*/
public static String generateVerificationCode() {
Random random = new Random();
int code = 100000 + random.nextInt(900000);
return String.valueOf(code);
}
/**
*
*
* <p>使 {@link EmailConfig} SMTP
*
*
* @param recipientEmail
* @param code
* @return true false
*/
public static boolean sendVerificationCode(String recipientEmail, String code) {
try {
// 创建邮件会话
Properties props = new Properties();
props.put("mail.smtp.host", EmailConfig.SMTP_HOST);
props.put("mail.smtp.port", EmailConfig.SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.ssl.protocols", "TLSv1.2");
// 创建认证器
Authenticator auth = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
EmailConfig.SENDER_EMAIL,
EmailConfig.SENDER_PASSWORD
);
}
};
Session session = Session.getInstance(props, auth);
// 创建邮件消息
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(EmailConfig.SENDER_EMAIL));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(recipientEmail));
message.setSubject(EmailConfig.EMAIL_SUBJECT);
// 创建邮件内容
String emailContent = createEmailContent(code);
message.setContent(emailContent, "text/html; charset=utf-8");
// 发送邮件
Transport.send(message);
// 存储验证码信息
verificationCodes.put(recipientEmail,
new VerificationCodeInfo(code, System.currentTimeMillis()));
System.out.println("验证码已发送到邮箱: " + recipientEmail);
return true;
} catch (MessagingException e) {
System.err.println("发送邮件失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* HTML
*
* @param code
* @return HTML
*/
private static String createEmailContent(String code) {
return "<!DOCTYPE html>"
+ "<html>"
+ "<head>"
+ "<meta charset='UTF-8'>"
+ "<style>"
+ "body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }"
+ ".container { max-width: 600px; margin: 0 auto; padding: 20px; }"
+ ".header { background-color: #4CAF50; color: white; padding: 20px; text-align: center; }"
+ ".content { padding: 20px; background-color: #f9f9f9; margin: 20px 0; }"
+ ".code { font-size: 24px; font-weight: bold; color: #4CAF50; text-align: center; "
+ "padding: 15px; background-color: white; border: 2px dashed #4CAF50; margin: 20px 0; }"
+ ".footer { text-align: center; color: #666; font-size: 12px; }"
+ "</style>"
+ "</head>"
+ "<body>"
+ "<div class='container'>"
+ "<div class='header'>"
+ "<h2>用户注册验证码</h2>"
+ "</div>"
+ "<div class='content'>"
+ "<p>您好!</p>"
+ "<p>您正在注册账户,验证码如下:</p>"
+ "<div class='code'>" + code + "</div>"
+ "<p>验证码有效期为 " + EmailConfig.CODE_EXPIRY_MINUTES + " 分钟,请勿泄露给他人。</p>"
+ "<p>如果这不是您本人的操作,请忽略此邮件。</p>"
+ "</div>"
+ "<div class='footer'>"
+ "<p>此邮件为系统自动发送,请勿回复。</p>"
+ "</div>"
+ "</div>"
+ "</body>"
+ "</html>";
}
/**
*
*
* @param email
* @param inputCode
* @return true false
*/
public static boolean verifyCode(String email, String inputCode) {
VerificationCodeInfo codeInfo = verificationCodes.get(email);
if (codeInfo == null) {
return false;
}
// 检查验证码是否过期
long currentTime = System.currentTimeMillis();
if (currentTime - codeInfo.timestamp > EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
verificationCodes.remove(email);
return false;
}
return codeInfo.code.equals(inputCode);
}
/**
*
*/
public static void cleanupExpiredCodes() {
long currentTime = System.currentTimeMillis();
Iterator<Entry<String, VerificationCodeInfo>> iterator =
verificationCodes.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, VerificationCodeInfo> entry = iterator.next();
if (currentTime - entry.getValue().timestamp
> EmailConfig.CODE_EXPIRY_MINUTES * 60 * 1000) {
iterator.remove();
}
}
}
/**
*
*
* @param email
* @return true false
*/
public static boolean isValidEmail(String email) {
if (email == null || email.isEmpty()) {
System.out.println("邮箱地址不能为空!");
return false;
}
if (!(email.contains("@") && email.contains("."))) {
System.out.println("邮箱格式不正确!");
return false;
}
return true;
}
/**
*
*
* <p>
*
* @param email
* @return true false
*/
public static boolean sendCode(String email) {
// 生成验证码
String verificationCode = EmailService.generateVerificationCode();
// 尝试发送邮件
if (!EmailService.sendVerificationCode(email, verificationCode)) {
// 如果发送失败sendVerificationCode 已经打印了错误信息
return false;
}
return true;
}
/**
*
*/
private static class VerificationCodeInfo {
/**
*
*/
String code;
/**
*
*/
long timestamp;
/**
*
*
* @param code
* @param timestamp
*/
VerificationCodeInfo(String code, long timestamp) {
this.code = code;
this.timestamp = timestamp;
}
}
}

@ -0,0 +1,32 @@
package com.ybw.mathapp.util;
/**
*
*
* <p>
*
* @author
* @since 2025
*/
public class Login {
/**
* 使
*
* <p> {@link LoginFileUtils#validateUser(String, String)}
*
*
* @param name
* @param password
* @return true false
*/
public static boolean login(String name, String password) {
if (LoginFileUtils.validateUser(name, password)) {
System.out.println("登录成功!欢迎回来," + name);
return true;
} else {
System.out.println("用户名或密码错误!");
return false;
}
}
}

@ -0,0 +1,155 @@
package com.ybw.mathapp.util;
import com.ybw.mathapp.entity.User;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
*
*
* <p> ({@code users.txt})
*
*
* @author
* @since 2025
*/
public class LoginFileUtils {
/**
*
*/
public static final String USER_FILE = "users.txt";
/**
*
*
* <p>
*
* @return
*/
public static List<User> readUsers() {
List<User> users = new ArrayList<>();
File file = new File(USER_FILE);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
System.err.println("创建用户文件失败: " + e.getMessage());
}
return users;
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
User user = User.fromString(line);
if (user != null) {
users.add(user);
}
}
} catch (IOException e) {
System.err.println("读取用户文件失败: " + e.getMessage());
}
return users;
}
/**
*
*
* @param user
*/
public static void saveUser(User user) {
try (PrintWriter writer = new PrintWriter(new FileWriter(USER_FILE, true))) {
writer.println(user.toString());
} catch (IOException e) {
System.err.println("保存用户信息失败: " + e.getMessage());
}
}
/**
*
*
* <p>
*
* @param email
* @return true false
*/
public static boolean isEmailRegistered(String email) {
List<User> users = readUsers();
for (User user : users) {
if (user.getEmail().equalsIgnoreCase(email)) {
return true;
}
}
return false;
}
/**
*
*
* <p>
*
* @param name
* @return true false
*/
public static boolean isNameRegistered(String name) {
List<User> users = readUsers();
for (User user : users) {
if (user.getName().equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
/**
*
*
* <p>使/
*
* @param emailOrName
* @param password
* @return true false
*/
public static boolean validateUser(String emailOrName, String password) {
List<User> users = readUsers();
for (User user : users) {
if ((user.getEmail().equalsIgnoreCase(emailOrName)
|| user.getName().equalsIgnoreCase(emailOrName))
&& user.getPassword().equals(password)) {
return true;
}
}
return false;
}
/**
*
*
* <p>
*
* @param email
* @return null
*/
public static String emailFindName(String email) {
List<User> users = readUsers();
for (User user : users) {
if (user.getEmail().equalsIgnoreCase(email)) {
return user.getName();
}
}
return null;
}
}

@ -0,0 +1,70 @@
package com.ybw.mathapp.util;
import com.ybw.mathapp.entity.User;
import java.util.regex.Pattern;
/**
*
*
* <p>
*
* @author
* @since 2025
*/
public class Register {
/**
*
*
* <p>
*
* @param name
* @param email
* @param password1
* @return true true使
*/
public static boolean register(String name, String email, String password1) {
User user = new User(name, email, password1);
LoginFileUtils.saveUser(user);
System.out.println("注册成功!您可以使用邮箱和密码登录了。");
return true;
}
/**
*
*
* <p>
* <ul>
* <li> 6 10 </li>
* <li></li>
* <li></li>
* </ul>
*
* @param password1
* @return true false
*/
public static boolean isVaildPassword(String password1) {
// 使用正则表达式验证长度6-10只包含字母数字且包含大小写字母和数字
String regex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,10}$";
if (!Pattern.matches(regex, password1)) {
return false;
}
return true;
}
/**
*
*
* @param password1
* @param password2
* @return true false
*/
public static boolean isEqualPassword(String password1, String password2) {
if (!password1.equals(password2)) {
System.out.println("两次输入的密码不一致!");
return false;
}
return true;
}
}
Loading…
Cancel
Save