Merge develop into main #1

main
王奕辉 2 months ago
commit b2e439ba77

@ -10,4 +10,9 @@
</profile>
</annotationProcessing>
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="exam" options="-parameters" />
</option>
</component>
</project>

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

@ -2,5 +2,6 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MapOrSetKeyShouldOverrideHashCodeEquals" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</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>

@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -33,9 +33,9 @@ cd spring-boot-online-exam
+ 支持单选题、多选题、判断题
+ 支持学生(student)、教师(teacher)、管理员(admin)三种角色
+ 学生:参加考试和查看我的考试
+ 教师:学生的所有权限+创建/编辑题目+创建/编辑考试
+ 管理员:教师的所有权限+管理用户
+ 学生:参加考试和查看我的考试
+ 教师:学生的所有权限+创建/编辑题目+创建/编辑考试
+ 管理员:教师的所有权限+管理用户
### 2.3 软件架构
@ -55,35 +55,35 @@ cd spring-boot-online-exam
+ 3.启动后端
> 打开`spring-boot-online-exam/backend/exam`这个Maven项目可以在IDE里启动或者执行`mvn install`生成jar包启动
+ 4.启动前端
+ 进入到前端代码路径 `cd spring-boot-online-exam/frontend/exam/`
+ 安装依赖 `npm install`
+ 启动前端 `npm run serve`
+ 进入到前端代码路径 `cd spring-boot-online-exam/frontend/exam/`
+ 安装依赖 `npm install`
+ 启动前端 `npm run serve`
+ 5.部署完毕,查看效果
> 打开 http://localhost:8000 或者 http://本机ip:8000 即可查看演示效果
## 3.功能图示
+ 1.管理题目
+ 1.1 题目列表
> ![题目查看](doc/images/question_list.png)
+ 1.2 题目创建
> ![题目创建](doc/images/question_create.png)
+ 1.3 题目更新
> ![题目更新](doc/images/question_update.png)
+ 1.1 题目列表
> ![题目查看](doc/images/question_list.png)
+ 1.2 题目创建
> ![题目创建](doc/images/question_create.png)
+ 1.3 题目更新
> ![题目更新](doc/images/question_update.png)
+ 2.考试管理
+ 2.1 考试列表
> ![考试查看](doc/images/exam_list.png)
+ 2.2 考试创建
> ![考试创建](doc/images/exam_create.png)
+ 2.3 考试更新(`还有点小bug开发中`)
> ![考试更新](doc/images/exam_update.png)
+ 2.1 考试列表
> ![考试查看](doc/images/exam_list.png)
+ 2.2 考试创建
> ![考试创建](doc/images/exam_create.png)
+ 2.3 考试更新(`还有点小bug开发中`)
> ![考试更新](doc/images/exam_update.png)
+ 3.我的考试
+ 3.1 参加考试
> 在"考试列表"模块点击自己想参加的考试卡片即可
> ![参加考试1](doc/images/exam_join.png)
> ![参加考试2](doc/images/exam_join2.png)
+ 3.2 考试记录查看
> ![考试记录查看](doc/images/exam_detail.png)
+ 3.1 参加考试
> 在"考试列表"模块点击自己想参加的考试卡片即可
> ![参加考试1](doc/images/exam_join.png)
> ![参加考试2](doc/images/exam_join2.png)
+ 3.2 考试记录查看
> ![考试记录查看](doc/images/exam_detail.png)
## 4.参与贡献

@ -1,12 +1,17 @@
// 指定当前类所在的包路径这是Java中组织类的一种方式有助于类的管理和访问控制。
package lsgwr.exam;
// 导入Spring Boot的启动类和应用配置自动装配的注解类。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// @SpringBootApplication是一个方便的注解它包括了@Configuration@EnableAutoConfiguration和@ComponentScan注解。
// 它告诉Spring Boot基于当前类所在包及其子包下的组件来启动自动配置和组件扫描。
@SpringBootApplication
public class ExamApplication {
public static void main(String[] args) {
// main方法是Java应用程序的入口点。当运行这个类时JVM会调用这个方法。
public static void main(String[] args)
{
// SpringApplication.run方法启动Spring应用传入ExamApplication.class当前启动类和main方法的参数args。
// 这个方法会执行一系列的操作包括启动Spring容器加载应用上下文自动配置等。
SpringApplication.run(ExamApplication.class, args);
}
}

@ -1,9 +1,6 @@
/***********************************************************
* @Description :
* @author : 广(Laing Shan Guang)
* @date : 2019-05-28 08:04
* @email : liangshanguang2@gmail.com
***********************************************************/
/**
* @Description : REST,HTTP,,
*/
package lsgwr.exam.controller;
import lsgwr.exam.entity.Exam;
@ -22,221 +19,350 @@ import java.util.List;
@RestController
@Api(tags = "Exam APIs")
//将此控制器中的所有端点映射到/api/exam URL路径
@RequestMapping("/api/exam")
public class ExamController {
@Autowired
@Autowired //注入一个ExamService实例用于处理与考试和问题相关的业务逻辑
private ExamService examService;
/**
* @Description: 使examService.getQuestionAll(),ResultVO,
* &#064;ApiOperationAPI,SwaggerUI
*/
@GetMapping("/question/all")
@ApiOperation("获取所有问题的列表")
ResultVO<List<QuestionVo>> getQuestionAll() {
// 获取全部问题列表。
ResultVO<List<QuestionVo>> getQuestionAll() {
// 定义返回值。
ResultVO<List<QuestionVo>> resultVO;
try {
// 调用examService获取全部问题列表。
List<QuestionVo> questionAll = examService.getQuestionAll();
// 返回成功结果。
resultVO = new ResultVO<>(0, "获取全部问题列表成功", questionAll);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回失败结果。
resultVO = new ResultVO<>(-1, "获取全部问题列表失败", null);
}
// 返回结果。
return resultVO;
}
@PostMapping("/question/update")
@ApiOperation("更新问题")
ResultVO<QuestionVo> questionUpdate(@RequestBody QuestionVo questionVo) {
// 完成问题的更新
// 完成问题的更新
System.out.println(questionVo);
try {
// 调用examService的updateQuestion方法更新问题。
QuestionVo questionVoResult = examService.updateQuestion(questionVo);
// 返回更新成功的结果。
return new ResultVO<>(0, "更新问题成功", questionVoResult);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回更新失败的结果。
return new ResultVO<>(-1, "更新问题失败", null);
}
}
/**
*
* @Description: QuestionCreateSimplifyVoQuestionCreateVoID使examService.questionCreate()
* @param questionCreateSimplifyVo QuestionCreateSimplifyVo
* @param request HttpServletRequestID
* @return ResultVO<String>
*/
@PostMapping("/question/create")
@ApiOperation("创建问题")
ResultVO<String> questionCreate(@RequestBody QuestionCreateSimplifyVo questionCreateSimplifyVo, HttpServletRequest request) {
// 创建一个QuestionCreateVo对象。
QuestionCreateVo questionCreateVo = new QuestionCreateVo();
// 把能拷贝过来的属性都拷贝过来
// 把能拷贝过来的属性都拷贝过来
BeanUtils.copyProperties(questionCreateSimplifyVo, questionCreateVo);
// 设置创建者信息
// 设置创建者信息
String userId = (String) request.getAttribute("user_id");
questionCreateVo.setQuestionCreatorId(userId);
System.out.println(questionCreateVo);
try {
// 调用examService的questionCreate方法创建问题。
examService.questionCreate(questionCreateVo);
// 返回问题创建成功的ResultVO。
return new ResultVO<>(0, "问题创建成功", null);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回创建问题失败的ResultVO。
return new ResultVO<>(-1, "创建问题失败", null);
}
}
/**
* @Description: ,使examService.getSelections()ResultVO
* @return ResultVO<QuestionAllVo>
*/
@GetMapping("/question/selection")
@ApiOperation("获取问题分类的相关选项")
ResultVO<QuestionSelectionVo> getSelections() {
// 获取问题分类选项。
ResultVO<QuestionSelectionVo> getSelections() {
// 调用examService的getSelections方法获取问题分类选项。
QuestionSelectionVo questionSelectionVo = examService.getSelections();
// 如果获取成功。
if (questionSelectionVo != null) {
// 返回成功的结果。
return new ResultVO<>(0, "获取问题分类选项成功", questionSelectionVo);
} else {
// 否则返回失败的结果。
return new ResultVO<>(-1, "获取问题分类选项失败", null);
}
}
/**
* @Description: 使examService.getQuestionDetail(id),ResultVO,
* @param id id
* @return ResultVO<QuestionDetailVo>
*/
@GetMapping("/question/detail/{id}")
@ApiOperation("根据问题的id获取问题的详细信息")
ResultVO<QuestionDetailVo> getQuestionDetail(@PathVariable String id) {
// 根据问题id获取问题的详细信息
// 根据问题id获取问题的详细信息
System.out.println(id);
ResultVO<QuestionDetailVo> resultVO;
try {
// 调用examService的getQuestionDetail方法根据问题id获取问题的详细信息。
QuestionDetailVo questionDetailVo = examService.getQuestionDetail(id);
// 如果获取成功则返回ResultVO对象状态码为0提示信息为"获取问题详情成功"数据为questionDetailVo。
resultVO = new ResultVO<>(0, "获取问题详情成功", questionDetailVo);
} catch (Exception e) {
// 如果获取失败则打印异常信息并返回ResultVO对象状态码为-1提示信息为"获取问题详情失败"数据为null。
e.printStackTrace();
// 创建一个表示获取失败的ResultVO对象状态码设置为 -1表示获取操作失败消息设置为"获取问题详情失败"数据部分设置为null
resultVO = new ResultVO<>(-1, "获取问题详情失败", null);
}
// 返回ResultVO对象。
return resultVO;
}
/**
* @Description: 使examService.getExamAll(),ResultVO,
* @return ResultVO<QuestionDetailVo>
*/
@GetMapping("/all")
@ApiOperation("获取全部考试的列表")
ResultVO<List<ExamVo>> getExamAll() {
// 需要拼接前端需要的考试列表对象
// 需要拼接前端需要的考试列表对象
ResultVO<List<ExamVo>> resultVO;
try {
// 调用examService的getExamAll方法获取全部考试的列表。
List<ExamVo> examVos = examService.getExamAll();
// 将获取到的考试列表封装到ResultVO对象中并返回。
resultVO = new ResultVO<>(0, "获取全部考试的列表成功", examVos);
} catch (Exception e) {
// 捕获异常,并打印异常信息。
e.printStackTrace();
// 将异常信息封装到ResultVO对象中并返回。
resultVO = new ResultVO<>(-1, "获取全部考试的列表失败", null);
}
return resultVO;
}
/**
* @Description: 使examService.getExamQuestionTypeList(),ResultVO,
* @return ResultVO<ExamQuestionTypeVo>
*/
@GetMapping("/question/type/list")
@ApiOperation("获取问题列表,按照单选、多选和判断题分类返回")
ResultVO<ExamQuestionTypeVo> getExamQuestionTypeList() {
// 获取问题的分类列表
// 获取问题的分类列表
ResultVO<ExamQuestionTypeVo> resultVO;
try {
// 调用examService的getExamQuestionType方法获取问题分类列表。
ExamQuestionTypeVo examQuestionTypeVo = examService.getExamQuestionType();
// 如果获取成功,则返回成功的结果。
resultVO = new ResultVO<>(0, "获取问题列表成功", examQuestionTypeVo);
} catch (Exception e) {
// 如果获取失败,则打印异常信息,并返回失败的结果。
e.printStackTrace();
resultVO = new ResultVO<>(-1, "获取问题列表失败", null);
}
// 返回结果。
return resultVO;
}
/**
* @Description: 使examService.createExam(),ResultVO,
* @param examCreateVo
* @param request id
* @return ResultVO<Exam>
*/
@PostMapping("/create")
@ApiOperation("创建考试")
ResultVO<Exam> createExam(@RequestBody ExamCreateVo examCreateVo, HttpServletRequest request) {
// 从前端传参数过来,在这里完成考试的入库
// 定义一个ResultVO类型的变量用于存放最终要返回给客户端的结果对象其泛型参数指定为Exam表示包含新创建考试相关信息的结果封装
ResultVO<Exam> resultVO;
// 从HttpServletRequest对象中获取用户ID信息通常这个用户ID是在请求处理的前置环节比如拦截器中设置到请求属性中的用于标识创建这个考试的用户是谁
String userId = (String) request.getAttribute("user_id");
try {
// 调用ExamService的create方法将创建考试的视图对象examCreateVo和获取到的用户ID传递进去由服务层实现将考试记录数据保存到数据库等具体的创建逻辑操作返回创建好的Exam对象代表新创建的考试信息
Exam exam = examService.create(examCreateVo, userId);
// 如果创建成功创建一个表示成功的ResultVO对象状态码设置为0表示创建操作成功消息设置为"创建考试成功"并将创建好的考试对象放入ResultVO对象中返回给客户端
resultVO = new ResultVO<>(0, "创建考试成功", exam);
} catch (Exception e) {
e.printStackTrace();
// 如果在创建过程中出现异常创建一个表示创建失败的ResultVO对象状态码设置为 -1表示创建操作失败消息设置为"创建考试失败"数据部分设置为null
resultVO = new ResultVO<>(-1, "创建考试失败", null);
}
return resultVO;
}
/**
* @Description: 使examService.updateExam(),ResultVO,
* @param examVo
* @param request id
* @return
*/
@PostMapping("/update")
@ApiOperation("更新考试")
ResultVO<Exam> updateExam(@RequestBody ExamVo examVo, HttpServletRequest request) {
// 从前端传参数过来,在这里完成考试的入库
// 从前端传参数过来,在这里完成考试的入库
ResultVO<Exam> resultVO;
// 获取当前用户id。
String userId = (String) request.getAttribute("user_id");
try {
// 调用service层更新考试。
Exam exam = examService.update(examVo, userId);
// 返回更新成功的resultVO。
resultVO = new ResultVO<>(0, "更新考试成功", exam);
} catch (Exception e) {
// 打印异常信息。
e.printStackTrace();
// 返回更新失败的resultVO。
resultVO = new ResultVO<>(-1, "更新考试失败", null);
}
return resultVO;
}
/**
* @Description:
* @return ResultVO<List<ExamCardVo>>
*/
@GetMapping("/card/list")
@ApiOperation("获取考试列表,适配前端卡片列表")
ResultVO<List<ExamCardVo>> getExamCardList() {
// 获取考试列表卡片
// 获取考试列表卡片
ResultVO<List<ExamCardVo>> resultVO;
try {
// 调用examService的getExamCardList方法获取考试列表卡片。
List<ExamCardVo> examCardVoList = examService.getExamCardList();
// 如果获取数据成功创建一个表示成功的ResultVO对象状态码设置为0表示获取考试列表卡片成功消息设置为"获取考试列表卡片成功"并将获取到的考试卡片列表数据放入ResultVO对象中以便返回给客户端进行展示。
resultVO = new ResultVO<>(0, "获取考试列表卡片成功", examCardVoList);
} catch (Exception e) {
// 如果获取失败,则打印异常信息,并返回失败的结果。
e.printStackTrace();
// 创建一个表示获取失败的ResultVO对象状态码设置为 -1表示获取考试列表卡片失败消息可根据业务实际情况设置为相应的提示语数据部分设置为null因为没有成功获取到有效的考试卡片列表数据。
resultVO = new ResultVO<>(-1, "获取考试列表卡片失败", null);
}
return resultVO;
}
/**
* @Description: id
* @param id id
* @return ResultVO<ExamDetailVo>
*/
@GetMapping("/detail/{id}")
@ApiOperation("根据考试的id获取考试详情")
ResultVO<ExamDetailVo> getExamDetail(@PathVariable String id) {
// 根据id获取考试详情
// 根据id获取考试详情先定义一个用于存放最终要返回给客户端的ResultVO<ExamDetailVo>类型的结果对象,后续根据查询数据的情况进行赋值操作。
ResultVO<ExamDetailVo> resultVO;
try {
// 调用ExamService的getExamDetail方法将接收到的考卷唯一标识符id传递进去由服务层实现从数据库或者其他数据源根据该ID查询对应考卷详细信息的逻辑返回一个ExamDetailVo对象代表该考卷的所有详细信息。
ExamDetailVo examDetail = examService.getExamDetail(id);
// 如果获取详细信息成功创建一个表示成功的ResultVO对象状态码设置为0表示获取考试详情成功消息设置为"获取考试详情成功"并将获取到的考卷详细信息对象放入ResultVO对象中以便返回给客户端进行展示。
resultVO = new ResultVO<>(0, "获取考试详情成功", examDetail);
} catch (Exception e) {
// 如果在获取考卷详细信息的过程中出现异常创建一个表示获取失败的ResultVO对象状态码设置为 -1表示获取考试详情失败消息可根据业务实际情况设置为相应的提示语数据部分设置为null因为没有成功获取到有效的考卷详细信息。
resultVO = new ResultVO<>(-1, "获取考试详情失败", null);
}
return resultVO;
}
/**
* @Description: 使examService.finishExam,ResultVO,
* @param examId id
* @param answersMap
* @param request id
* @return
*/
@PostMapping("/finish/{examId}")
@ApiOperation("根据用户提交的答案对指定id的考试判分")
ResultVO<ExamRecord> finishExam(@PathVariable String examId, @RequestBody HashMap<String, List<String>> answersMap, HttpServletRequest request) {
// 定义一个用于存放结果的ResultVO<ExamRecord>类型的数据结构,后续根据评分操作的成功与否以及获取到的相关数据进行赋值,用于最终返回给客户端展示评分结果。
ResultVO<ExamRecord> resultVO;
try {
// 拦截器里设置上的用户id
// 拦截器里设置上的用户id从HttpServletRequest对象中获取用户ID属性值该用户ID用于标识当前提交答案并进行评分的用户是后续业务逻辑处理如记录答题记录归属、判断是否有权限答题等的重要依据。
String userId = (String) request.getAttribute("user_id");
// 下面根据用户提交的信息进行判分,返回用户的得分情况
// 下面根据用户提交的信息进行判分返回用户的得分情况调用ExamService的judge方法传入获取到的用户ID、考试唯一标识符examId以及用户提交的答案集合answersMap由服务层实现具体的评分逻辑比如对比答案、计算得分等操作并返回一个ExamRecord对象包含了评分后的成绩记录等详细信息。
ExamRecord examRecord = examService.judge(userId, examId, answersMap);
resultVO = new ResultVO<>(0, "考卷提交成功", examRecord);
// 封装成绩记录到最终结果中创建一个表示评分成功的ResultVO对象状态码设置为0表示考卷提交评分成功消息设置为"考卷提交成功"并将包含成绩记录的ExamRecord对象放入ResultVO对象中以便返回给客户端展示评分结果。
resultVO = new ResultVO<>(0, "考卷提交成功", examRecord);// 封装成绩记录到最终结果中
} catch (Exception e) {
// 如果在评分过程中出现异常,打印异常堆栈信息,方便开发人员排查问题,查看是答案解析出错还是其他业务逻辑环节出现的错误导致评分失败。
e.printStackTrace();
// 创建一个表示评分失败的ResultVO对象状态码设置为 -1表示考卷提交评分失败消息可根据业务实际情况设置为相应的提示语数据部分设置为null因为没有成功获取到有效的评分成绩记录。
resultVO = new ResultVO<>(-1, "考卷提交失败", null);
}
return resultVO;
}
/**
* @Description: 使examService.getExamRecordList,ResultVO,
* @param request id
* @return ResultVO<List<ExamRecordVo>> ResultVOResultVO
*/
@GetMapping("/record/list")
@ApiOperation("获取当前用户的考试记录")
ResultVO<List<ExamRecordVo>> getExamRecordList(HttpServletRequest request) {
// 定义一个用于存放结果的ResultVO<List<ExamRecordVo>>类型的数据结构,后续根据查询用户考试记录操作的成功与否以及获取到的相关数据进行赋值,用于最终返回给客户端展示查询结果。
ResultVO<List<ExamRecordVo>> resultVO;
try {
// 拦截器里设置上的用户id
// 拦截器里设置上的用户id从HttpServletRequest对象中获取用户ID属性值该用户ID用于明确要查询其考试记录的目标用户是服务层准确获取对应数据的关键依据。
String userId = (String) request.getAttribute("user_id");
// 下面根据用户账号拿到他(她所有的考试信息)注意要用VO封装下
// 下面根据用户账号拿到他所有的考试信息注意要用VO封装下调用ExamService的getExamRecordList方法传入获取到的用户ID由服务层实现从数据库或者其他数据源获取该用户所有历史考试记录数据的逻辑并将其整理封装成List<ExamRecordVo>类型的列表返回每个ExamRecordVo对象包含了如时间、得分等详细信息。
List<ExamRecordVo> examRecordVoList = examService.getExamRecordList(userId);
resultVO = new ResultVO<>(0, "获取考试记录成功", examRecordVoList);
// 封装查询得到的信息到最终结果中创建一个表示获取成功的ResultVO对象状态码设置为0表示获取考试记录成功消息设置为"获取考试记录成功"并将获取到的用户考试记录列表数据放入ResultVO对象中以便返回给客户端展示历史考试记录情况。
resultVO = new ResultVO<>(0, "获取考试记录成功", examRecordVoList);//封装查询得到的信息到最终结果中;
} catch (Exception e) {
// 如果在获取用户考试记录数据的过程中出现异常,打印异常堆栈信息,方便开发人员排查问题,查看是数据源查询出错还是数据封装等环节出现的错误导致获取失败。
e.printStackTrace();
// 创建一个表示获取失败的ResultVO对象状态码设置为 -1表示获取考试记录失败消息可根据业务实际情况设置为相应的提示语数据部分设置为null因为没有成功获取到有效的用户考试记录数据。
resultVO = new ResultVO<>(-1, "获取考试记录失败", null);
}
return resultVO;
return resultVO;//返回封装好的 数据结构
}
/**
* @Description: 使examService.getExamRecordDetail,ResultVO,id
* @param recordId id
* @return ResultVO<RecordDetailVo> ResultVOResultVO
*/
@GetMapping("/record/detail/{recordId}")
@ApiOperation("根据考试记录id获取考试记录详情")
ResultVO<RecordDetailVo> getExamRecordDetail(@PathVariable String recordId) {
// 定义一个用于存放结果的ResultVO<RecordDetailVo>类型的数据结构,后续根据查询测验详细情况操作的成功与否以及获取到的相关数据进行赋值,用于最终返回给客户端展示查询结果。
ResultVO<RecordDetailVo> resultVO;
try {
// 调用ExamService的getRecordDetail方法将接收到的测验记录唯一标识符recordId传递进去由服务层实现从数据库或者其他数据源根据该ID查询对应测验详细信息的逻辑返回一个RecordDetailVo对象代表该次测验的所有详细信息。
RecordDetailVo recordDetailVo = examService.getRecordDetail(recordId);
resultVO = new ResultVO<>(0, "获取考试记录详情成功", recordDetailVo);
// 封装查询得到的信息到最终结果中创建一个表示获取成功的ResultVO对象状态码设置为0表示获取考试记录详情成功消息设置为"获取考试记录详情成功"并将获取到的测验详细信息对象放入ResultVO对象中以便返回给客户端展示该次测验的详细情况。
resultVO = new ResultVO<>(0, "获取考试记录详情成功", recordDetailVo);//封装查询得到的信息到最终结果中;
} catch (Exception e) {
// 如果在获取测验详细信息的过程中出现异常,打印异常堆栈信息,方便开发人员排查问题,查看是数据源查询出错还是数据解析等环节出现的错误导致获取失败。
e.printStackTrace();
// 创建一个ResultVO对象表示操作失败
// 状态码设置为-1表示获取考试记录详情失败
// 消息设置为"获取考试记录详情失败"
// 由于查询失败详细信息对象设置为null
resultVO = new ResultVO<>(-1, "获取考试记录详情失败", null);
}
// 返回封装好的ResultVO对象给客户端
// 客户端可以根据状态码和消息判断操作是否成功,并根据详细信息对象获取测验的详细记录信息
return resultVO;
}
}

@ -42,37 +42,49 @@ public class UploadDownloadController {
@ApiOperation("单文件上传,支持同时传入参数")
@PostMapping("/api/upload/singleAndparas")
public String uploadFileSingle(@RequestParam("dir") String dir, @RequestParam("file") MultipartFile uploadfile) {
// 单文件上传。
public String uploadFileSingle(@RequestParam("dir") String dir, @RequestParam("file") MultipartFile uploadfile) {
// 调用FileTransUtil工具类中的uploadFile方法将上传的文件和目录作为参数传入。
return FileTransUtil.uploadFile(uploadfile, dir);
}
// 单文件上传支持同时传入参数Model。
@ApiOperation("单文件上传,支持同时传入参数,Model")
@PostMapping("/upload/single/model")
public String singleUploadFileModel(@ModelAttribute("model") UploadModel2 model) {
// 调用FileTransUtil工具类中的uploadFile方法将上传的文件和目录作为参数传入。
return FileTransUtil.uploadFile(model.getFile(), model.getDir());
}
// 多文件上传,支持同时传入参数。
@ApiOperation("多文件上传,支持同时传入参数")
@PostMapping("upload/multiAndparas")
public String uploadFileMulti(@RequestParam("dir") String dir, @RequestParam("files") MultipartFile[] uploadfiles) {
// 调用FileTransUtil工具类中的uploadFiles方法将上传的文件数组和目录作为参数传入。
return FileTransUtil.uploadFiles(uploadfiles, dir);
}
// 多文件上传,支持同时传入参数。
@ApiOperation("多文件上传,支持同时传入参数")
@PostMapping(value = "/upload/multi/model")
public String multiUploadFileModel(@ModelAttribute(("model")) UploadModel model) {
// 调用FileTransUtil工具类中的uploadFiles方法将上传的文件数组和目录作为参数传入。
return FileTransUtil.uploadFiles(model.getFiles(), model.getDir());
}
// Get下载文件
@ApiOperation("Get下载文件")
@GetMapping(value = "/download/get")
public ResponseEntity<InputStreamResource> downloadFileGet(@RequestParam String filePath) throws IOException {
// 调用FileTransUtil工具类中的downloadFile方法将文件路径作为参数传入。
return FileTransUtil.downloadFile(filePath);
}
// Post下载文件
@ApiOperation("Post下载文件")
@PostMapping(value = "/download/post")
public ResponseEntity<InputStreamResource> downloadFilePost(@RequestBody DownloadQo downloadQo) throws IOException {
// 调用FileTransUtil工具类中的downloadFile方法将文件路径作为参数传入。
return FileTransUtil.downloadFile(downloadQo.getPath());
}
}

@ -62,18 +62,27 @@ public class UserController {
@GetMapping("/user-info")
@ApiOperation("获取用户信息")
// 根据请求获取用户信息
ResultVO<UserVo> getUserInfo(HttpServletRequest request) {
// 从请求中获取用户ID
String userId = (String) request.getAttribute("user_id");
// 根据用户ID获取用户信息
UserVo userVo = userService.getUserInfo(userId);
// 返回用户信息
return new ResultVO<>(ResultEnum.GET_INFO_SUCCESS.getCode(), ResultEnum.GET_INFO_SUCCESS.getMessage(), userVo);
}
@GetMapping("/info")
@ApiOperation("获取用户的详细信息,包括个人信息页面和操作权限")
// 获取用户信息的接口
ResultVO<UserInfoVo> getInfo(HttpServletRequest request) {
// 打印进入接口的日志
System.out.println("进入/user/info的获取用户信息的接口");
// 获取用户ID
String userId = (String) request.getAttribute("user_id");
// 调用userService获取用户信息
UserInfoVo userInfoVo = userService.getInfo(userId);
// 返回结果
return new ResultVO<>(ResultEnum.GET_INFO_SUCCESS.getCode(), ResultEnum.GET_INFO_SUCCESS.getMessage(), userInfoVo);
}

@ -11,17 +11,29 @@ import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
*
* ID
*/
@Data
@Entity
public class Action {
/**
* ID
*/
@Id
@GeneratedValue
private Integer actionId;
/**
*
*/
private String actionName;
/**
*
*/
private String actionDescription;
/**
*
*/
private Boolean defaultCheck;
}

@ -4,51 +4,60 @@
* @date : 2019/5/14 07:42
* @email : liangshanguang2@gmail.com
***********************************************************/
// 定义包名,用于组织类文件,避免命名冲突
package lsgwr.exam.entity;
// 导入Jackson库的JsonFormat注解用于JSON序列化时自定义日期格式
import com.fasterxml.jackson.annotation.JsonFormat;
// 导入Lombok库的Data注解用于自动生成getter、setter、equals、hashCode和toString方法
import lombok.Data;
// 导入Hibernate的DynamicUpdate注解用于在实体更新时只更新发生变化的字段
import org.hibernate.annotations.DynamicUpdate;
// 导入JPA的Entity注解用于声明该类是一个JPA实体类
import javax.persistence.Entity;
// 导入JPA的Id注解用于声明该类中的某个字段作为主键
import javax.persistence.Id;
// 导入Java的Date类用于表示日期和时间
import java.util.Date;
@Entity
@Data
@DynamicUpdate
public class Exam {
// 使用JPA的@Id注解声明该字段为主键
@Id
private String examId;
private String examName;
private String examAvatar;
private String examDescription;
private String examQuestionIds;
private String examQuestionIdsRadio;
private String examQuestionIdsCheck;
private String examQuestionIdsJudge;
private Integer examScore;
private Integer examScoreRadio;
private Integer examScoreCheck;
private Integer examScoreJudge;
private String examCreatorId;
private Integer examTimeLimit;
private String examId;// 考试ID唯一标识一场考试
private String examName;// 考试名称
private String examAvatar; // 考试头像或图标
private String examDescription;// 考试描述或简介
private String examQuestionIds;// 存储所有问题ID的字符串可能是逗号分隔的ID列表
private String examQuestionIdsRadio;// 存储所有单选题ID的字符串
private String examQuestionIdsCheck;// 存储所有多选题ID的字符串
private String examQuestionIdsJudge;// 存储所有判断题ID的字符串
private Integer examScore;// 考试总分
private Integer examScoreRadio;// 单选题总分
private Integer examScoreCheck;// 多选题总分
private Integer examScoreJudge;// 判断题总分
private String examCreatorId;// 创建者ID标识谁创建了这场考试
private Integer examTimeLimit;// 考试时间限制,单位可能是分钟
// 考试开始时间使用Jackson的@JsonFormat注解自定义日期格式
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date examStartDate;
// 考试结束时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date examEndDate;
/**
* , Java
* Java
* 使Jackson@JsonFormat便
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* Java
* @DynamicUpdate
* Java
* 使Hibernate@DynamicUpdate
* 使Jackson@JsonFormat便
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;

@ -7,42 +7,44 @@
package lsgwr.exam.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;// 引入Jackson库用于日期格式化的注解
import lombok.Data;// 引入Lombok库用于简化Java实体类的编写如自动生成getter和setter方法
import javax.persistence.Entity;
import javax.persistence.Entity;// 引入JPA注解用于标识这是一个实体类并映射到数据库中的一张表
// 引入JPA注解用于标识实体类的主键字段
import javax.persistence.Id;
import java.util.Date;
// 使用@Data注解自动生成getter和setter方法以及toString、equals和hashCode方法
@Data
// 使用@Entity注解标识这是一个JPA实体类
@Entity
public class ExamRecord {
/**
*
*
*/
@Id
private String examRecordId;
/**
* id
* ID
*/
private String examId;
/**
* (_-),
* 线_线-
*/
private String answerOptionIds;
/**
* userid
* IDID
*/
private String examJoinerId;
/**
*
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date examJoinDate;
/**
* ()
*
*/
private Integer examTimeCost;
/**
@ -50,7 +52,7 @@ public class ExamRecord {
*/
private Integer examJoinScore;
/**
*
*
*/
private Integer examResultLevel;
}
}

@ -7,18 +7,22 @@
package lsgwr.exam.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.Data;// 引入Lombok库中的@Data注解用于自动生成getter、setter、toString、equals和hashCode方法
import javax.persistence.Entity;// 引入JPAJava Persistence API中的@Entity注解标识这是一个实体类与数据库中的表相对应
import javax.persistence.GeneratedValue;// 引入JPA中的@GeneratedValue注解用于指定主键的生成策略这里未明确指定策略将使用默认策略
import javax.persistence.Id;// 引入JPA中的@Id注解标识实体类的主键字段
// 使用@Data注解自动生成getter、setter等方法
@Data
// 使用@Entity注解标识这是一个JPA实体类
@Entity
public class ExamRecordLevel {
// 考试记录等级的ID是主键通过@Id注解标识并通过@GeneratedValue注解指定主键生成策略
@Id
@GeneratedValue
private Integer examRecordLevelId;
// 考试记录等级的名称,用于标识等级,如“优秀”、“良好”等
private String examRecordLevelName;
// 考试记录等级的描述,用于对等级进行更详细的说明
private String examRecordLevelDescription;
}
}

@ -14,21 +14,54 @@ import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;
/**
*
* getter/setter
*/
@Data
@Entity
@DynamicUpdate
public class Question {
/**
* id
*/
@Id
private String questionId;
/**
*
*/
private String questionName;
/**
*
*/
private Integer questionScore;
/**
* id
*/
private String questionCreatorId;
/**
* id
*/
private Integer questionLevelId;
/**
* id
*/
private Integer questionTypeId;
/**
* id
*/
private Integer questionCategoryId;
/**
*
*/
private String questionDescription;
/**
* idA,B,C,D
*/
private String questionOptionIds;
/**
* idA
*/
private String questionAnswerOptionIds;
/**
* , Java

@ -13,19 +13,27 @@ import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
*
*/
@Data
@Entity
public class QuestionCategory {
/**
* id
*/
@Id
@GeneratedValue
@JsonProperty("id")
private Integer questionCategoryId;
/**
*
*/
@JsonProperty("name")
private String questionCategoryName;
/**
*
*/
@JsonProperty("description")
private String questionCategoryDescription;
}

@ -14,17 +14,27 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
*
*/
@Entity
@Data
public class QuestionLevel {
/**
* ID
*/
@Id
@GeneratedValue
@JsonProperty("id")
private Integer questionLevelId;
/**
*
*/
@JsonProperty("name")
private String questionLevelName;
/**
*
*/
@JsonProperty("description")
private String questionLevelDescription;
}

@ -11,12 +11,23 @@ import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
*
*/
@Data
@Entity
public class QuestionOption {
/**
* ABCD
*/
@Id
private String questionOptionId;
/**
*
*/
private String questionOptionContent;
/**
*
*/
private String questionOptionDescription;
}

@ -14,17 +14,28 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* QuestionType
* ID
*/
@Data
@Entity
public class QuestionType {
/**
* ID
*/
@Id
@GeneratedValue
@JsonProperty("id")
private Integer questionTypeId;
/**
*
*/
@JsonProperty("name")
private String questionTypeName;
/**
*
*/
@JsonProperty("description")
private String questionTypeDescription;
}

@ -13,14 +13,31 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
*
* ID
*/
@Data
@Entity
public class Role {
/**
* ID
* @Id
*/
@Id
@GeneratedValue
private Integer roleId;
/**
*
*/
private String roleName;
/**
*
*/
private String roleDescription;
/**
*
*/
private String roleDetail;
/**
* 访(-)

@ -14,20 +14,49 @@ import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;
/**
*
*/
@Data
@Entity
@DynamicUpdate
public class User {
/**
* id
*/
@Id
private String userId;
/**
*
*/
private String userUsername;
/**
*
*/
private String userNickname;
/**
*
*/
private String userPassword;
/**
* id
*/
private Integer userRoleId;
/**
*
*/
private String userAvatar;
/**
*
*/
private String userDescription;
/**
*
*/
private String userEmail;
/**
*
*/
private String userPhone;
/**
* , Java

@ -3,18 +3,18 @@ package lsgwr.exam.enums;
import lombok.Getter;
/**
*
* @author liangshanguang
* @Description:
*/
@Getter
public enum LoginTypeEnum {
/**
* 12
* @Description:
* 12
*/
USERNAME(1, "用户名"),
EMAIL(2, "邮箱");
// 构造函数用于初始化code和description
LoginTypeEnum(Integer type, String name) {
this.type = type;
this.name = name;

@ -8,17 +8,16 @@ package lsgwr.exam.enums;
import lombok.Getter;
/**
* @Description :
*/
@Getter
public enum QuestionEnum {
/**
*
*/
RADIO(1, "单选题"),
CHECK(2, "多选题"),
JUDGE(3, "判断题");
// 构造函数用于初始化id和role
QuestionEnum(Integer id, String role) {
this.id = id;
this.role = role;

@ -2,6 +2,9 @@ package lsgwr.exam.enums;
import lombok.Getter;
/**
* @Description :
*/
@Getter
public enum ResultEnum {
// 下面是本项目用到的所有错误码
@ -17,6 +20,11 @@ public enum ResultEnum {
ORDER_UPDATE_ERR(15, "考试更新异常"),
ORDER_DETAIL_EMPTY(16, "用户详情为空");
/**
* @Description: codemessage
* @param code
* @param message
*/
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;

@ -8,6 +8,9 @@ package lsgwr.exam.enums;
import lombok.Getter;
/**
* @Description:
*/
@Getter
public enum RoleEnum {
@ -18,7 +21,11 @@ public enum RoleEnum {
TEACHER(2, "教师"),
STUDENT(3, "学生");
/**
* @Description: codemessage
* @param id id
* @param role
*/
RoleEnum(Integer id, String role) {
this.id = id;
this.role = role;

@ -11,15 +11,18 @@ import lombok.Getter;
@Getter
public class ExamException extends RuntimeException {
// 定义异常码
private Integer code;
// 构造函数,传入枚举类型
public ExamException(ResultEnum resultEnum) {
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
// 构造函数,传入异常码和异常信息
public ExamException( Integer code, String message) {
super(message);
this.code = code;
}
}
}

@ -13,5 +13,8 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
public class DownloadQo {
/**
*
*/
String path;
}

@ -9,5 +9,6 @@ package lsgwr.exam.repository;
import lsgwr.exam.entity.ExamRecordLevel;
import org.springframework.data.jpa.repository.JpaRepository;
// ExamRecordLevelRepository接口继承JpaRepository接口用于操作ExamRecordLevel实体类
public interface ExamRecordLevelRepository extends JpaRepository<ExamRecordLevel, Integer> {
}
}

@ -1,9 +1,3 @@
/***********************************************************
* @Description :
* @author : 广(Laing Shan Guang)
* @date : 2019-05-14 08:23
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.repository;
import lsgwr.exam.entity.ExamRecord;
@ -19,4 +13,4 @@ public interface ExamRecordRepository extends JpaRepository<ExamRecord, String>
* @return
*/
List<ExamRecord> findByExamJoinerIdOrderByExamJoinDateDesc(String userId);
}
}

@ -12,7 +12,9 @@ import org.springframework.data.jpa.repository.Query;
import java.util.List;
// ExamRepository接口继承JpaRepository接口用于操作Exam实体类
public interface ExamRepository extends JpaRepository<Exam, String> {
// 使用JPQL查询语句查询Exam实体类按照updateTime降序排列
@Query("select e from Exam e order by e.updateTime desc")
List<Exam> findAll();
}

@ -8,6 +8,8 @@ package lsgwr.exam.repository;
import lsgwr.exam.entity.Page;
import org.springframework.data.jpa.repository.JpaRepository;
/**
*
*/
public interface PageRepository extends JpaRepository<Page, Integer> {
}

@ -8,6 +8,8 @@ package lsgwr.exam.repository;
import lsgwr.exam.entity.QuestionCategory;
import org.springframework.data.jpa.repository.JpaRepository;
/**
*
*/
public interface QuestionCategoryRepository extends JpaRepository<QuestionCategory, Integer> {
}

@ -8,6 +8,8 @@ package lsgwr.exam.repository;
import lsgwr.exam.entity.QuestionLevel;
import org.springframework.data.jpa.repository.JpaRepository;
/**
*
*/
public interface QuestionLevelRepository extends JpaRepository<QuestionLevel, Integer> {
}

@ -8,6 +8,8 @@ package lsgwr.exam.repository;
import lsgwr.exam.entity.QuestionOption;
import org.springframework.data.jpa.repository.JpaRepository;
/**
*
*/
public interface QuestionOptionRepository extends JpaRepository<QuestionOption, String> {
}

@ -11,7 +11,9 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
/**
*
*/
public interface QuestionRepository extends JpaRepository<Question, String> {
List<Question> findByQuestionTypeId(Integer id);
@Query("select q from Question q order by q.updateTime desc")

@ -8,6 +8,8 @@ package lsgwr.exam.repository;
import lsgwr.exam.entity.QuestionType;
import org.springframework.data.jpa.repository.JpaRepository;
/**
*
*/
public interface QuestionTypeRepository extends JpaRepository<QuestionType, Integer> {
}

@ -9,5 +9,8 @@ package lsgwr.exam.repository;
import lsgwr.exam.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
/**
*
*/
public interface RoleRepository extends JpaRepository<Role, Integer> {
}

@ -8,7 +8,9 @@ package lsgwr.exam.repository;
import lsgwr.exam.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
/**
*
*/
public interface UserRepository extends JpaRepository<User, String> {
/**
*

@ -4,15 +4,15 @@
* @date : 2019-05-28 08:05
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.service;
package lsgwr.exam.service;// 定义了接口所属的包名
import lsgwr.exam.entity.Exam;
import lsgwr.exam.entity.ExamRecord;
import lsgwr.exam.vo.*;
import java.util.HashMap;
import java.util.List;
import lsgwr.exam.entity.Exam;// 导入了实体类Exam可能包含考试的基本信息
import lsgwr.exam.entity.ExamRecord;// 导入了实体类ExamRecord可能包含考试记录的详细信息
import lsgwr.exam.vo.*;// 导入了VO对象VO是值对象用于在不同层之间传递数据
import java.util.HashMap;// 导入了HashMap用于存储键值对
import java.util.List;// 导入了List用于存储一系列对象
// 定义了一个名为ExamService的接口
public interface ExamService {
/**
*

@ -10,8 +10,8 @@ import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import lsgwr.exam.entity.*;
import lsgwr.exam.enums.QuestionEnum;
import lsgwr.exam.service.ExamService;
import lsgwr.exam.repository.*;
import lsgwr.exam.service.ExamService;
import lsgwr.exam.vo.*;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@ -40,9 +40,9 @@ public class ExamServiceImpl implements ExamService {
private final QuestionOptionRepository questionOptionRepository;
public ExamServiceImpl(QuestionRepository questionRepository, UserRepository userRepository, QuestionLevelRepository questionLevelRepository, QuestionTypeRepository questionTypeRepository, QuestionCategoryRepository questionCategoryRepository, QuestionOptionRepository questionOptionRepository, ExamRepository examRepository, ExamRecordRepository examRecordRepository) {
this.questionRepository = questionRepository;
this.userRepository = userRepository;
this.questionLevelRepository = questionLevelRepository;
this.questionRepository = questionRepository;//
this.userRepository = userRepository;//
this.questionLevelRepository = questionLevelRepository;//
this.questionTypeRepository = questionTypeRepository;
this.questionCategoryRepository = questionCategoryRepository;
this.questionOptionRepository = questionOptionRepository;
@ -57,9 +57,9 @@ public class ExamServiceImpl implements ExamService {
}
private List<QuestionVo> getQuestionVos(List<Question> questionList) {
// 需要自定义的question列表
// 需要自定义的question列表
List<QuestionVo> questionVoList = new ArrayList<>();
// 循环完成每个属性的定制
// 循环完成每个属性的定制
for (Question question : questionList) {
QuestionVo questionVo = getQuestionVo(question);
questionVoList.add(questionVo);
@ -69,9 +69,9 @@ public class ExamServiceImpl implements ExamService {
private QuestionVo getQuestionVo(Question question) {
QuestionVo questionVo = new QuestionVo();
// 先复制能复制的属性
// 先复制能复制的属性
BeanUtils.copyProperties(question, questionVo);
// 设置问题的创建者
// 设置问题的创建者
questionVo.setQuestionCreator(
Objects.requireNonNull(
userRepository.findById(
@ -79,7 +79,7 @@ public class ExamServiceImpl implements ExamService {
).orElse(null)
).getUserUsername());
// 设置问题的难度
// 设置问题的难度
questionVo.setQuestionLevel(
Objects.requireNonNull(
questionLevelRepository.findById(
@ -87,7 +87,7 @@ public class ExamServiceImpl implements ExamService {
).orElse(null)
).getQuestionLevelDescription());
// 设置题目的类别,比如单选、多选、判断等
// 设置题目的类别,比如单选、多选、判断等
questionVo.setQuestionType(
Objects.requireNonNull(
questionTypeRepository.findById(
@ -95,7 +95,7 @@ public class ExamServiceImpl implements ExamService {
).orElse(null)
).getQuestionTypeDescription());
// 设置题目分类,比如数学、语文、英语、生活、人文等
// 设置题目分类,比如数学、语文、英语、生活、人文等
questionVo.setQuestionCategory(
Objects.requireNonNull(
questionCategoryRepository.findById(
@ -104,20 +104,20 @@ public class ExamServiceImpl implements ExamService {
).getQuestionCategoryName()
);
// 选项的自定义Vo列表
// 选项的自定义Vo列表
List<QuestionOptionVo> optionVoList = new ArrayList<>();
// 获得所有的选项列表
// 获得所有的选项列表
List<QuestionOption> optionList = questionOptionRepository.findAllById(
Arrays.asList(question.getQuestionOptionIds().split("-"))
);
// 获取所有的答案列表optionList中每个option的isAnswer选项
// 获取所有的答案列表optionList中每个option的isAnswer选项
List<QuestionOption> answerList = questionOptionRepository.findAllById(
Arrays.asList(question.getQuestionAnswerOptionIds().split("-"))
);
// 根据选项和答案的id相同设置optionVo的isAnswer属性
// 根据选项和答案的id相同设置optionVo的isAnswer属性
for (QuestionOption option : optionList) {
QuestionOptionVo optionVo = new QuestionOptionVo();
BeanUtils.copyProperties(option, optionVo);
@ -129,14 +129,14 @@ public class ExamServiceImpl implements ExamService {
optionVoList.add(optionVo);
}
// 设置题目的所有选项
// 设置题目的所有选项
questionVo.setQuestionOptionVoList(optionVoList);
return questionVo;
}
@Override
public QuestionVo updateQuestion(QuestionVo questionVo) {
// 1.把需要的属性都设置好
// 1.把需要的属性都设置好
StringBuilder questionAnswerOptionIds = new StringBuilder();
List<QuestionOption> questionOptionList = new ArrayList<>();
List<QuestionOptionVo> questionOptionVoList = questionVo.getQuestionOptionVoList();
@ -148,26 +148,26 @@ public class ExamServiceImpl implements ExamService {
questionOptionList.add(questionOption);
if (questionOptionVo.getAnswer()) {
if (i != size - 1) {
// 把更新后的答案的id加上去,记得用-连到一起
// 把更新后的答案的id加上去,记得用-连到一起
questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId()).append("-");
} else {
// 最后一个不需要用-连接
// 最后一个不需要用-连接
questionAnswerOptionIds.append(questionOptionVo.getQuestionOptionId());
}
}
}
// 1.更新问题
// 1.更新问题
Question question = questionRepository.findById(questionVo.getQuestionId()).orElse(null);
assert question != null;
BeanUtils.copyProperties(questionVo, question);
question.setQuestionAnswerOptionIds(questionAnswerOptionIds.toString());
questionRepository.save(question);
// 2.更新所有的option
// 2.更新所有的option
questionOptionRepository.saveAll(questionOptionList);
// 返回更新后的问题,方便前端局部刷新
// 返回更新后的问题,方便前端局部刷新
return getQuestionVo(question);
}
@ -175,58 +175,64 @@ public class ExamServiceImpl implements ExamService {
public void questionCreate(QuestionCreateVo questionCreateVo) {
// 问题创建
Question question = new Question();
// 把能复制的属性都复制过来
// 把能复制的属性都复制过来
BeanUtils.copyProperties(questionCreateVo, question);
// 设置下questionOptionIds和questionAnswerOptionIds需要自己用Hutool生成下
// 设置下questionOptionIds和questionAnswerOptionIds需要自己用Hutool生成下
List<QuestionOption> questionOptionList = new ArrayList<>();
List<QuestionOptionCreateVo> questionOptionCreateVoList = questionCreateVo.getQuestionOptionCreateVoList();
for (QuestionOptionCreateVo questionOptionCreateVo : questionOptionCreateVoList) {
QuestionOption questionOption = new QuestionOption();
// 设置选项的的内容
// 设置选项的的内容
questionOption.setQuestionOptionContent(questionOptionCreateVo.getQuestionOptionContent());
// 设置选项的id
// 设置选项的id
questionOption.setQuestionOptionId(IdUtil.simpleUUID());
questionOptionList.add(questionOption);
}
// 把选项都存起来然后才能用于下面设置Question的questionOptionIds和questionAnswerOptionIds
// 把选项都存起来然后才能用于下面设置Question的questionOptionIds和questionAnswerOptionIds
questionOptionRepository.saveAll(questionOptionList);
String questionOptionIds = "";
String questionAnswerOptionIds = "";
// 经过上面的saveAll方法所有的option的主键id都已经持久化了
// 经过上面的saveAll方法所有的option的主键id都已经持久化了
for (int i = 0; i < questionOptionCreateVoList.size(); i++) {
// 获取指定选项
// 获取指定选项
QuestionOptionCreateVo questionOptionCreateVo = questionOptionCreateVoList.get(i);
// 获取保存后的指定对象
// 获取保存后的指定对象
QuestionOption questionOption = questionOptionList.get(i);
questionOptionIds += questionOption.getQuestionOptionId() + "-";
if (questionOptionCreateVo.getAnswer()) {
// 如果是答案的话
// 如果是答案的话
questionAnswerOptionIds += questionOption.getQuestionOptionId() + "-";
}
}
// 把字符串最后面的"-"给去掉
// 把字符串最后面的"-"给去掉
questionAnswerOptionIds = replaceLastSeparator(questionAnswerOptionIds);
questionOptionIds = replaceLastSeparator(questionOptionIds);
// 设置选项id组成的字符串
// 设置选项id组成的字符串
question.setQuestionOptionIds(questionOptionIds);
// 设置答案选项id组成的字符串
// 设置答案选项id组成的字符串
question.setQuestionAnswerOptionIds(questionAnswerOptionIds);
// 自己生成问题的id
// 自己生成问题的id
question.setQuestionId(IdUtil.simpleUUID());
// 先把创建时间和更新时间每次都取当前时间吧
// 先把创建时间和更新时间每次都取当前时间吧
question.setCreateTime(new Date());
question.setUpdateTime(new Date());
// 保存问题到数据库
// 保存问题到数据库
questionRepository.save(question);
}
@Override
// 重写父类方法,获取问题选择列表
public QuestionSelectionVo getSelections() {
// 创建问题选择对象
QuestionSelectionVo questionSelectionVo = new QuestionSelectionVo();
// 设置问题类别列表
questionSelectionVo.setQuestionCategoryList(questionCategoryRepository.findAll());
// 设置问题等级列表
questionSelectionVo.setQuestionLevelList(questionLevelRepository.findAll());
// 设置问题类型列表
questionSelectionVo.setQuestionTypeList(questionTypeRepository.findAll());
// 返回问题选择对象
return questionSelectionVo;
}
@ -245,10 +251,15 @@ public class ExamServiceImpl implements ExamService {
@Override
public QuestionDetailVo getQuestionDetail(String id) {
// 根据id查询问题
Question question = questionRepository.findById(id).orElse(null);
// 创建问题详情对象
QuestionDetailVo questionDetailVo = new QuestionDetailVo();
// 设置问题id
questionDetailVo.setId(id);
// 设置问题名称
questionDetailVo.setName(question.getQuestionName());
// 设置问题描述
questionDetailVo.setDescription(question.getQuestionDescription());
// 问题类型,单选题/多选题/判断题
questionDetailVo.setType(
@ -258,10 +269,10 @@ public class ExamServiceImpl implements ExamService {
).orElse(null)
).getQuestionTypeDescription()
);
// 获取当前问题的选项
// 获取当前问题的选项
String optionIdsStr = trimMiddleLine(question.getQuestionOptionIds());
String[] optionIds = optionIdsStr.split("-");
// 获取选项列表
// 获取选项列表
List<QuestionOption> optionList = questionOptionRepository.findAllById(Arrays.asList(optionIds));
questionDetailVo.setOptions(optionList);
return questionDetailVo;
@ -274,14 +285,14 @@ public class ExamServiceImpl implements ExamService {
}
private List<ExamVo> getExamVos(List<Exam> examList) {
// 需要自定义的exam列表
// 需要自定义的exam列表
List<ExamVo> examVoList = new ArrayList<>();
// 循环完成每个属性的定制
// 循环完成每个属性的定制
for (Exam exam : examList) {
ExamVo examVo = new ExamVo();
// 先尽量复制能复制的所有属性
// 先尽量复制能复制的所有属性
BeanUtils.copyProperties(exam, examVo);
// 设置问题的创建者
// 设置问题的创建者
examVo.setExamCreator(
Objects.requireNonNull(
userRepository.findById(
@ -290,7 +301,7 @@ public class ExamServiceImpl implements ExamService {
).getUserUsername()
);
// 获取所有单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
// 获取所有单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
List<ExamQuestionSelectVo> radioQuestionVoList = new ArrayList<>();
List<Question> radioQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsRadio().split("-"))
@ -298,12 +309,12 @@ public class ExamServiceImpl implements ExamService {
for (Question question : radioQuestionList) {
ExamQuestionSelectVo radioQuestionVo = new ExamQuestionSelectVo();
BeanUtils.copyProperties(question, radioQuestionVo);
radioQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
radioQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
radioQuestionVoList.add(radioQuestionVo);
}
examVo.setExamQuestionSelectVoRadioList(radioQuestionVoList);
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上
List<ExamQuestionSelectVo> checkQuestionVoList = new ArrayList<>();
List<Question> checkQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsCheck().split("-"))
@ -311,12 +322,12 @@ public class ExamServiceImpl implements ExamService {
for (Question question : checkQuestionList) {
ExamQuestionSelectVo checkQuestionVo = new ExamQuestionSelectVo();
BeanUtils.copyProperties(question, checkQuestionVo);
checkQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
checkQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
checkQuestionVoList.add(checkQuestionVo);
}
examVo.setExamQuestionSelectVoCheckList(checkQuestionVoList);
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上
List<ExamQuestionSelectVo> judgeQuestionVoList = new ArrayList<>();
List<Question> judgeQuestionList = questionRepository.findAllById(
Arrays.asList(exam.getExamQuestionIdsJudge().split("-"))
@ -324,7 +335,7 @@ public class ExamServiceImpl implements ExamService {
for (Question question : judgeQuestionList) {
ExamQuestionSelectVo judgeQuestionVo = new ExamQuestionSelectVo();
BeanUtils.copyProperties(question, judgeQuestionVo);
judgeQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
judgeQuestionVo.setChecked(true); // 考试中的问题肯定被选中的
judgeQuestionVoList.add(judgeQuestionVo);
}
examVo.setExamQuestionSelectVoJudgeList(judgeQuestionVoList);
@ -340,7 +351,7 @@ public class ExamServiceImpl implements ExamService {
@Override
public ExamQuestionTypeVo getExamQuestionType() {
ExamQuestionTypeVo examQuestionTypeVo = new ExamQuestionTypeVo();
// 获取所有单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
// 获取所有单选题列表并赋值到ExamVo的属性ExamQuestionSelectVoRadioList上
List<ExamQuestionSelectVo> radioQuestionVoList = new ArrayList<>();
List<Question> radioQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.RADIO.getId());
for (Question question : radioQuestionList) {
@ -350,7 +361,7 @@ public class ExamServiceImpl implements ExamService {
}
examQuestionTypeVo.setExamQuestionSelectVoRadioList(radioQuestionVoList);
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoCheckList上
List<ExamQuestionSelectVo> checkQuestionVoList = new ArrayList<>();
List<Question> checkQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.CHECK.getId());
for (Question question : checkQuestionList) {
@ -360,7 +371,7 @@ public class ExamServiceImpl implements ExamService {
}
examQuestionTypeVo.setExamQuestionSelectVoCheckList(checkQuestionVoList);
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上
// 获取所有多选题列表并赋值到ExamVo的属性ExamQuestionSelectVoJudgeList上
List<ExamQuestionSelectVo> judgeQuestionVoList = new ArrayList<>();
List<Question> judgeQuestionList = questionRepository.findByQuestionTypeId(QuestionEnum.JUDGE.getId());
for (Question question : judgeQuestionList) {
@ -374,7 +385,7 @@ public class ExamServiceImpl implements ExamService {
@Override
public Exam create(ExamCreateVo examCreateVo, String userId) {
// 在线考试系统创建
// 在线考试系统创建
Exam exam = new Exam();
BeanUtils.copyProperties(examCreateVo, exam);
exam.setExamId(IdUtil.simpleUUID());
@ -382,35 +393,53 @@ public class ExamServiceImpl implements ExamService {
exam.setCreateTime(new Date());
exam.setUpdateTime(new Date());
// Todo:这两个日志后面是要在前端传入的,这里暂时定为当前日期
exam.setExamStartDate(new Date());
exam.setExamEndDate(new Date());
String radioIdsStr = "";
String checkIdsStr = "";
String judgeIdsStr = "";
List<ExamQuestionSelectVo> radios = examCreateVo.getRadios();
List<ExamQuestionSelectVo> checks = examCreateVo.getChecks();
List<ExamQuestionSelectVo> judges = examCreateVo.getJudges();
int radioCnt = 0, checkCnt = 0, judgeCnt = 0;
for (ExamQuestionSelectVo radio : radios) {
if (radio.getChecked()) {
radioIdsStr += radio.getQuestionId() + "-";
radioCnt++;
}
}
radioIdsStr = replaceLastSeparator(radioIdsStr);
for (ExamQuestionSelectVo check : checks) {
if (check.getChecked()) {
checkIdsStr += check.getQuestionId() + "-";
checkCnt++;
}
}
checkIdsStr = replaceLastSeparator(checkIdsStr);
for (ExamQuestionSelectVo judge : judges) {
if (judge.getChecked()) {
judgeIdsStr += judge.getQuestionId() + "-";
judgeCnt++;
}
}
// 设置考试开始时间和结束时间
exam.setExamStartDate(new Date());
exam.setExamEndDate(new Date());
// 初始化选择题、判断题和单选题的id字符串
String radioIdsStr = "";
String checkIdsStr = "";
String judgeIdsStr = "";
// 获取选择题、判断题和单选题的列表
List<ExamQuestionSelectVo> radios = examCreateVo.getRadios();
List<ExamQuestionSelectVo> checks = examCreateVo.getChecks();
List<ExamQuestionSelectVo> judges = examCreateVo.getJudges();
// 初始化选择题、判断题和单选题的计数器
int radioCnt = 0, checkCnt = 0, judgeCnt = 0;
// 遍历单选题列表
for (ExamQuestionSelectVo radio : radios) {
// 如果单选题被选中
if (radio.getChecked()) {
// 将单选题的id添加到字符串中
radioIdsStr += radio.getQuestionId() + "-";
// 单选题计数器加1
radioCnt++;
}
}
// 去掉最后一个分隔符
radioIdsStr = replaceLastSeparator(radioIdsStr);
// 遍历判断题列表
for (ExamQuestionSelectVo check : checks) {
// 如果判断题被选中
if (check.getChecked()) {
// 将判断题的id添加到字符串中
checkIdsStr += check.getQuestionId() + "-";
// 判断题计数器加1
checkCnt++;
}
}
// 去掉最后一个分隔符
checkIdsStr = replaceLastSeparator(checkIdsStr);
// 遍历选择题列表
for (ExamQuestionSelectVo judge : judges) {
// 如果选择题被选中
if (judge.getChecked()) {
// 将选择题的id添加到字符串中
judgeIdsStr += judge.getQuestionId() + "-";
// 选择题计数器加1
judgeCnt++;
}
}
judgeIdsStr = replaceLastSeparator(judgeIdsStr);
exam.setExamQuestionIds(radioIdsStr + "-" + checkIdsStr + "-" + judgeIdsStr);
// 设置各个题目的id
@ -427,49 +456,64 @@ public class ExamServiceImpl implements ExamService {
@Override
public Exam update(ExamVo examVo, String userId) {
// 创建一个新的Exam对象
Exam exam = new Exam();
// 将examVo中的属性值复制到exam对象中
BeanUtils.copyProperties(examVo, exam);
exam.setExamCreatorId(userId); // 考试的更新人为最新的创建人
exam.setUpdateTime(new Date()); // 考试的更新日期要记录下
// 设置考试更新人为最新的创建人
exam.setExamCreatorId(userId);
// 记录考试更新日期
exam.setUpdateTime(new Date());
// 初始化选择题、判断题、单选题的id字符串
String radioIdsStr = "";
String checkIdsStr = "";
String judgeIdsStr = "";
String judgeIdsStr = "";//djfndiosbchjdshjiowceduikmn
// 获取选择题、判断题、单选题的列表
List<ExamQuestionSelectVo> radios = examVo.getExamQuestionSelectVoRadioList();
List<ExamQuestionSelectVo> checks = examVo.getExamQuestionSelectVoCheckList();
List<ExamQuestionSelectVo> judges = examVo.getExamQuestionSelectVoJudgeList();
// 初始化选择题、判断题、单选题的数量
int radioCnt = 0, checkCnt = 0, judgeCnt = 0;
// 遍历选择题列表将选中的题目id添加到字符串中并统计选择题数量
for (ExamQuestionSelectVo radio : radios) {
if (radio.getChecked()) {
radioIdsStr += radio.getQuestionId() + "-";
radioCnt++;
}
}
// 去除最后一个分隔符
radioIdsStr = replaceLastSeparator(radioIdsStr);
// 遍历判断题列表将选中的题目id添加到字符串中并统计判断题数量
for (ExamQuestionSelectVo check : checks) {
if (check.getChecked()) {
checkIdsStr += check.getQuestionId() + "-";
checkCnt++;
}
}
// 去除最后一个分隔符
checkIdsStr = replaceLastSeparator(checkIdsStr);
// 遍历单选题列表将选中的题目id添加到字符串中并统计单选题数量
for (ExamQuestionSelectVo judge : judges) {
if (judge.getChecked()) {
judgeIdsStr += judge.getQuestionId() + "-";
judgeCnt++;
}
}
// 去除最后一个分隔符。
judgeIdsStr = replaceLastSeparator(judgeIdsStr);
// 设置各个题目的id。
exam.setExamQuestionIds(radioIdsStr + "-" + checkIdsStr + "-" + judgeIdsStr);
// 设置各个题目的id
exam.setExamQuestionIdsRadio(radioIdsStr);
exam.setExamQuestionIdsCheck(checkIdsStr);
exam.setExamQuestionIdsJudge(judgeIdsStr);
// 计算总分数
// 计算总分数
int examScore = radioCnt * exam.getExamScoreRadio() + checkCnt * exam.getExamScoreCheck() + judgeCnt * exam.getExamScoreJudge();
exam.setExamScore(examScore);
// 保存exam对象。
examRepository.save(exam);
// 返回exam对象。
return exam;
}
@ -499,53 +543,53 @@ public class ExamServiceImpl implements ExamService {
@Override
public ExamRecord judge(String userId, String examId, HashMap<String, List<String>> answersMap) {
// 开始考试判分啦~~~
// 1.首先获取考试对象和选项数组
// 开始考试判分啦~~~
// 1.首先获取考试对象和选项数组
ExamDetailVo examDetailVo = getExamDetail(examId);
Exam exam = examDetailVo.getExam();
// 2.然后获取该考试下所有的题目信息
// 2.然后获取该考试下所有的题目信息
List<String> questionIds = new ArrayList<>();
// 2.1 题目id的数组
// 2.1 题目id的数组
List<String> radioIdList = Arrays.asList(examDetailVo.getRadioIds());
List<String> checkIdList = Arrays.asList(examDetailVo.getCheckIds());
List<String> judgeIdList = Arrays.asList(examDetailVo.getJudgeIds());
questionIds.addAll(radioIdList);
questionIds.addAll(checkIdList);
questionIds.addAll(judgeIdList);
// 2.2 每种题目的分数
// 2.2 每种题目的分数
int radioScore = exam.getExamScoreRadio();
int checkScore = exam.getExamScoreCheck();
int judgeScore = exam.getExamScoreJudge();
// 2.3 根据问题id的数组拿到所有的问题对象供下面步骤用
// 2.3 根据问题id的数组拿到所有的问题对象供下面步骤用
List<Question> questionList = questionRepository.findAllById(questionIds);
Map<String, Question> questionMap = new HashMap<>();
for (Question question : questionList) {
questionMap.put(question.getQuestionId(), question);
}
// 3.根据正确答案和用户作答信息进行判分
// 3.根据正确答案和用户作答信息进行判分
Set<String> questionIdsAnswer = answersMap.keySet();
// 存储当前考试每个题目的得分情况
// 存储当前考试每个题目的得分情况
Map<String, Integer> judgeMap = new HashMap<>();
// 考生作答地每个题目的选项(题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔),用于查看考试详情
// 例子题目1的id_作答选项1-作答选项2&题目2的id_作答选项1&题目3_作答选项1-作答选项2-作答选项3
// 考生作答地每个题目的选项(题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔),用于查看考试详情
// 例子题目1的id_作答选项1-作答选项2&题目2的id_作答选项1&题目3_作答选项1-作答选项2-作答选项3
StringBuilder answerOptionIdsSb = new StringBuilder();
// 用户此次考试的总分
// 用户此次考试的总分
int totalScore = 0;
for (String questionId : questionIdsAnswer) {
// 获取用户作答地这个题的答案信息
// 获取用户作答地这个题的答案信息
Question question = questionMap.get(questionId);
// 获取答案选项
// 获取答案选项
String questionAnswerOptionIds = replaceLastSeparator(question.getQuestionAnswerOptionIds());
List<String> questionAnswerOptionIdList = Arrays.asList(questionAnswerOptionIds.split("-"));
Collections.sort(questionAnswerOptionIdList);
String answerStr = listConcat(questionAnswerOptionIdList);
// 获取用户作答
// 获取用户作答
List<String> questionUserOptionIdList = answersMap.get(questionId);
Collections.sort(questionUserOptionIdList);
String userStr = listConcat(questionUserOptionIdList);
// 判断questionAnswerOptionIds和answersMap里面的答案是否相等
// 判断questionAnswerOptionIds和answersMap里面的答案是否相等
if (answerStr.equals(userStr)) {
// 说明题目作答正确,下面根据题型给分
// 说明题目作答正确,下面根据题型给分
int score = 0;
if (radioIdList.contains(questionId)) {
score = radioScore;
@ -556,22 +600,22 @@ public class ExamServiceImpl implements ExamService {
if (judgeIdList.contains(questionId)) {
score = judgeScore;
}
// 累计本次考试得分
// 累计本次考试得分
totalScore += score;
// True代表题目答对
// True代表题目答对
answerOptionIdsSb.append(questionId + "@True_" + userStr + "$");
judgeMap.put(questionId, score);
} else {
// 说明题目作答错误,直接判零分,False代表题目答错
// 说明题目作答错误,直接判零分,False代表题目答错
answerOptionIdsSb.append(questionId + "@False_" + userStr + "$");
judgeMap.put(questionId, 0);
}
}
// 4.计算得分记录本次考试结果存到ExamRecord中
// 4.计算得分记录本次考试结果存到ExamRecord中
ExamRecord examRecord = new ExamRecord();
examRecord.setExamRecordId(IdUtil.simpleUUID());
examRecord.setExamId(examId);
// 注意去掉最后可能有的&_-
// 注意去掉最后可能有的&_-
examRecord.setAnswerOptionIds(replaceLastSeparator(answerOptionIdsSb.toString()));
examRecord.setExamJoinerId(userId);
examRecord.setExamJoinDate(new Date());
@ -582,58 +626,60 @@ public class ExamServiceImpl implements ExamService {
@Override
public List<ExamRecordVo> getExamRecordList(String userId) {
// 获取指定用户下的考试记录列表
// 获取指定用户下的考试记录列表
List<ExamRecord> examRecordList = examRecordRepository.findByExamJoinerIdOrderByExamJoinDateDesc(userId);
List<ExamRecordVo> examRecordVoList = new ArrayList<>();
for (ExamRecord examRecord : examRecordList) {
ExamRecordVo examRecordVo = new ExamRecordVo();
// 根据考试记录中的考试ID获取考试信息
Exam exam = examRepository.findById(examRecord.getExamId()).orElse(null);
examRecordVo.setExam(exam);
// 根据用户ID获取用户信息
User user = userRepository.findById(userId).orElse(null);
examRecordVo.setUser(user);
// 设置考试记录信息
examRecordVo.setExamRecord(examRecord);
examRecordVoList.add(examRecordVo);
}
return examRecordVoList;
}
@Override
public RecordDetailVo getRecordDetail(String recordId) {
// 获取考试详情的封装对象
// 获取考试详情的封装对象
ExamRecord record = examRecordRepository.findById(recordId).orElse(null);
RecordDetailVo recordDetailVo = new RecordDetailVo();
recordDetailVo.setExamRecord(record);
// 用户的答案,需要解析
// 用户的答案,需要解析
HashMap<String, List<String>> answersMap = new HashMap<>();
HashMap<String, String> resultsMap = new HashMap<>();
assert record != null;
String answersStr = record.getAnswerOptionIds();
// $分隔题目,因为$在正则中有特殊用途(行尾),所以需要括起来
// $分隔题目,因为$在正则中有特殊用途(行尾),所以需要括起来
String[] questionArr = answersStr.split("[$]");
for (String questionStr : questionArr) {
System.out.println(questionStr);
// 区分开题目标题和选项
// 区分开题目标题和选项
String[] questionTitleResultAndOption = questionStr.split("_");
String[] questionTitleAndResult = questionTitleResultAndOption[0].split("@");
String[] questionOptions = questionTitleResultAndOption[1].split("-");
// 题目:答案选项
// 题目:答案选项
answersMap.put(questionTitleAndResult[0], Arrays.asList(questionOptions));
// 题目True / False
// 题目True / False
resultsMap.put(questionTitleAndResult[0], questionTitleAndResult[1]);
}
recordDetailVo.setAnswersMap(answersMap);
recordDetailVo.setResultsMap(resultsMap);
// 下面再计算正确答案的map
// 下面再计算正确答案的map
ExamDetailVo examDetailVo = getExamDetail(record.getExamId());
List<String> questionIdList = new ArrayList<>();
questionIdList.addAll(Arrays.asList(examDetailVo.getRadioIds()));
questionIdList.addAll(Arrays.asList(examDetailVo.getCheckIds()));
questionIdList.addAll(Arrays.asList(examDetailVo.getJudgeIds()));
// 获取所有的问题对象
// 获取所有的问题对象
List<Question> questionList = questionRepository.findAllById(questionIdList);
HashMap<String, List<String>> answersRightMap = new HashMap<>();
for (Question question : questionList) {
// 记得去掉最后可能出现的特殊字符
// 记得去掉最后可能出现的特殊字符
String questionAnswerOptionIdsStr = replaceLastSeparator(question.getQuestionAnswerOptionIds());
String[] questionAnswerOptionIds = questionAnswerOptionIdsStr.split("-");
answersRightMap.put(question.getQuestionId(), Arrays.asList(questionAnswerOptionIds));
@ -641,7 +687,6 @@ public class ExamServiceImpl implements ExamService {
recordDetailVo.setAnswersRightMap(answersRightMap);
return recordDetailVo;
}
/**
* -
*
@ -650,7 +695,7 @@ public class ExamServiceImpl implements ExamService {
*/
private String replaceLastSeparator(String str) {
String lastChar = str.substring(str.length() - 1);
// 题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔
// 题目和题目之间用$分隔,题目有多个选项地话用-分隔,题目和选项之间用_分隔
if ("-".equals(lastChar) || "_".equals(lastChar) || "$".equals(lastChar)) {
str = StrUtil.sub(str, 0, str.length() - 1);
}

@ -47,7 +47,12 @@ public class UserServiceImpl implements UserService {
@Autowired
ActionRepository actionRepository;
/**
*
*
* @param registerDTO
* @return null
*/
@Override
public User register(RegisterDTO registerDTO) {
try {
@ -106,7 +111,12 @@ public class UserServiceImpl implements UserService {
}
return null;
}
/**
*
*
* @param userId id
* @return
*/
@Override
public UserVo getUserInfo(String userId) {
User user = userRepository.findById(userId).orElse(null);
@ -115,7 +125,12 @@ public class UserServiceImpl implements UserService {
BeanUtils.copyProperties(user, userVo);
return userVo;
}
/**
*
*
* @param userId id
* @return
*/
@Override
public UserInfoVo getInfo(String userId) {
User user = userRepository.findById(userId).orElse(null);

@ -7,22 +7,32 @@
package lsgwr.exam.utils;
import lsgwr.exam.vo.ResultVO;
/**
*
*/
public class ResultVOUtil {
/**
*
*/
public static ResultVO success(Integer code, String msg, Object object) {
return new ResultVO(code, msg, object);
}
/**
*
*/
public static ResultVO success(Object object) {
return new ResultVO(0, "成功", object);
}
/**
*
*/
public static ResultVO success() {
return new ResultVO(0, "成功", null);
}
/**
*
*/
public static ResultVO error(Integer code, String msg) {
return new ResultVO(code, msg, null);
}

@ -8,15 +8,24 @@ package lsgwr.exam.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* Action
*/
@Data
public class ActionVo {
/**
*
*/
@JsonProperty("action")
private String actionName;
/**
*
*/
@JsonProperty("describe")
private String actionDescription;
/**
*
*/
@JsonProperty("defaultCheck")
private Boolean defaultCheck;
}

@ -8,22 +8,39 @@ package lsgwr.exam.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @Description: ,便JSON
* API,JSON
* Lombok@Datagettersetter
* Jackson@JsonPropertyJSON使
*/
@Data
public class ExamCardVo {
@JsonProperty("id")
private String examId;
/**
*
*/
@JsonProperty("title")
private String examName;
/**
*
*/
@JsonProperty("avatar")
private String examAvatar;
/**
*
*/
@JsonProperty("content")
private String examDescription;
/**
*
*/
@JsonProperty("score")
private Integer examScore;
private Integer examScore;// 考试的分数
/**
*
*/
@JsonProperty("elapse")
private Integer examTimeLimit;
private Integer examTimeLimit;// 考试的时间限制,以分钟为单位
}

@ -5,60 +5,63 @@
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.vo;
// 引入Jackson库中的注解用于JSON序列化和反序列化时自定义属性名
import com.fasterxml.jackson.annotation.JsonProperty;
// 引入Lombok库中的@Data注解它会自动为你的类的字段生成getter和setter方法以及toString、equals和hashCode方法
import lombok.Data;
import java.util.List;
// 使用@Data注解来自动生成getter、setter等方法
@Data
public class ExamCreateVo {
// 考试名称,通过@JsonProperty注解指定JSON字段名为"name"
@JsonProperty("name")
private String examName;
// 考试头像或图标,通过@JsonProperty注解指定JSON字段名为"avatar"
@JsonProperty("avatar")
private String examAvatar;
// 考试描述,通过@JsonProperty注解指定JSON字段名为"desc"
@JsonProperty("desc")
private String examDescription;
/**
*
* @JsonPropertyJSON"elapse"
*
*/
@JsonProperty("elapse")
private Integer examTimeLimit;
/**
*
* 使List<ExamQuestionSelectVo>
* ExamQuestionSelectVoVO
*/
private List<ExamQuestionSelectVo> radios;
/**
*
* 使List<ExamQuestionSelectVo>
*/
private List<ExamQuestionSelectVo> checks;
/**
*
* 使List<ExamQuestionSelectVo>
*/
private List<ExamQuestionSelectVo> judges;
/**
*
* @JsonPropertyJSON"radioScore"
*/
@JsonProperty("radioScore")
private Integer examScoreRadio;
/**
*
* @JsonPropertyJSON"checkScore"
*/
@JsonProperty("checkScore")
private Integer examScoreCheck;
/**
*
* @JsonPropertyJSON"judgeScore"
*/
@JsonProperty("judgeScore")
private Integer examScoreJudge;

@ -5,29 +5,42 @@
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.vo;
// 引入Exam实体类它可能包含了考试的详细信息如考试ID、名称、描述等
import lsgwr.exam.entity.Exam;
// 引入Lombok库的@Data注解用于自动生成getter、setter等方法
import lombok.Data;
// 使用@Data注解来自动生成这个类的getter、setter等方法
@Data
public class ExamDetailVo {
/**
*
*
* Exam
* ID
* ExamDetailVoExam
*/
private Exam exam;
/**
* id
* id
* ID
*
* ID
*/
private String[] radioIds;
/**
* id
* id
* idID
*
*
*/
private String[] checkIds;
/**
* id
* id
* ID
*
* ID
*/
private String[] judgeIds;

@ -4,37 +4,40 @@
* @date : 2019-06-22 17:00
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.vo;
package lsgwr.exam.vo;// 定义包名,用于组织类文件,避免命名冲突
// 导入Jackson库的JsonProperty注解用于JSON序列化时自定义字段名
import com.fasterxml.jackson.annotation.JsonProperty;
// 导入Lombok库的Data注解用于自动生成getter、setter、equals、hashCode和toString方法
import lombok.Data;
// 导入Java的List接口用于存储ExamVo对象的集合
import java.util.List;
// 使用Lombok的@Data注解自动为该类生成getter、setter等方法
@Data
public class ExamPageVo {
/**
*
*
*/
private Integer pageSize;
/**
* 1
* 110
* 1
*/
private Integer pageNo;
/**
*
*
*/
private Long totalCount;
/**
*
*
*/
private Integer totalPage;
/**
*
* ExamVoExamVo
* @JsonProperty("data")JSONexamVoListJSON"data"
*/
@JsonProperty("data")
private List<ExamVo> examVoList;

@ -4,22 +4,32 @@
* @date : 2019-06-17 23:10
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.vo;
package lsgwr.exam.vo;// 定义包名,用于组织类文件,避免命名冲突
// 导入Jackson库的JsonProperty注解用于JSON序列化时自定义字段名
import com.fasterxml.jackson.annotation.JsonProperty;
// 导入Lombok库的Data注解用于自动生成getter、setter、equals、hashCode和toString方法
import lombok.Data;
// 使用Lombok的@Data注解自动为该类生成getter、setter等方法
@Data
public class ExamQuestionSelectVo {
/**
* JSON"id"
*
*/
@JsonProperty("id")
private String questionId;
/**
* JSON"name"
*
*/
@JsonProperty("name")
private String questionName;
/**
* .falsetrue
*
* false
* true
*
* JSON"checked"
*/
@JsonProperty("checked")
private Boolean checked = false;

@ -4,21 +4,35 @@
* @date : 2019-06-23 11:00
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.vo;
package lsgwr.exam.vo;// 定义包名,用于组织类文件,避免命名冲突
// 导入Jackson库的JsonProperty注解用于JSON序列化时自定义字段名
import com.fasterxml.jackson.annotation.JsonProperty;
// 导入Lombok库的Data注解用于自动生成getter、setter、equals、hashCode和toString方法
import lombok.Data;
// 导入Java的List接口用于存储对象的集合
import java.util.List;
// 使用Lombok的@Data注解自动为该类生成getter、setter等方法
@Data
public class ExamQuestionTypeVo {
/**
* ExamQuestionSelectVo
* JSON"radios"
* "radios"radio buttons
*/
@JsonProperty("radios")
private List<ExamQuestionSelectVo> examQuestionSelectVoRadioList;
/**
* ExamQuestionSelectVo
* JSON"checks"
* "checks"checkboxes
*/
@JsonProperty("checks")
private List<ExamQuestionSelectVo> examQuestionSelectVoCheckList;
/**
* ExamQuestionSelectVo
* JSON"judges"
* "judges"true/falseyes/no
*/
@JsonProperty("judges")
private List<ExamQuestionSelectVo> examQuestionSelectVoJudgeList;
}

@ -4,27 +4,34 @@
* @date : 2019/10/25 7:42
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.vo;
import lsgwr.exam.entity.Exam;
import lsgwr.exam.entity.ExamRecord;
import lsgwr.exam.entity.User;
package lsgwr.exam.vo;// 定义包名,用于组织类文件,避免命名冲突
// 导入相关的实体类
import lsgwr.exam.entity.Exam;// 考试实体类
import lsgwr.exam.entity.ExamRecord; // 考试记录实体类
import lsgwr.exam.entity.User;// 用户实体类
// 导入Lombok库的Data注解用于自动生成getter、setter、equals、hashCode和toString方法
import lombok.Data;
// 使用Lombok的@Data注解自动生成getter、setter等方法
@Data
public class ExamRecordVo {
/**
*
*
*
* ExamExam
*/
private Exam exam;
/**
*
*
*
* ExamRecordExamRecord
*/
private ExamRecord examRecord;
/**
*
*
*
* UserUser
*/
private User user;
}

@ -4,86 +4,83 @@
* @date : 2019/5/14 07:42
* @email : liangshanguang2@gmail.com
***********************************************************/
// 定义包名,用于组织类文件并避免命名冲突
package lsgwr.exam.vo;
// 导入所需的库和注解
import com.fasterxml.jackson.annotation.JsonFormat;// 用于日期格式的序列化和反序列化
import com.fasterxml.jackson.annotation.JsonProperty;// 用于自定义JSON字段名
import lombok.Data;// 用于自动生成getter、setter等方法
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
import java.util.Date;// Java日期类
import java.util.List;// Java列表接口
// 使用Lombok的@Data注解自动生成getter、setter等方法
@Data
public class ExamVo {
// 使用@JsonProperty注解自定义JSON字段名为"id"
@JsonProperty("id")
private String examId;
private String examId;// 考试的唯一标识符
// 使用@JsonProperty注解自定义JSON字段名为"name"
@JsonProperty("name")
private String examName;
private String examName;// 考试名称
// 使用@JsonProperty注解自定义JSON字段名为"avatar"
@JsonProperty("avatar")
private String examAvatar;
private String examAvatar;// 考试图标或头像
// 使用@JsonProperty注解自定义JSON字段名为"desc"
@JsonProperty("desc")
private String examDescription;
private String examDescription;// 考试描述
// 单选题列表JSON字段名为"radios"
@JsonProperty("radios")
private List<ExamQuestionSelectVo> examQuestionSelectVoRadioList;
private List<ExamQuestionSelectVo> examQuestionSelectVoRadioList;// 单选题选项列表
// 多选题列表JSON字段名为"checks"
@JsonProperty("checks")
private List<ExamQuestionSelectVo> examQuestionSelectVoCheckList;
private List<ExamQuestionSelectVo> examQuestionSelectVoCheckList;// 多选题选项列表
// 判断题列表JSON字段名为"judges"
@JsonProperty("judges")
private List<ExamQuestionSelectVo> examQuestionSelectVoJudgeList;
private List<ExamQuestionSelectVo> examQuestionSelectVoJudgeList;// 判断题选项列表
// 考试总分JSON字段名为"score"
@JsonProperty("score")
private Integer examScore;
private Integer examScore;// 考试的总分数
// 单选题总分JSON字段名为"radioScore"
@JsonProperty("radioScore")
private Integer examScoreRadio;
private Integer examScoreRadio;// 单选题的总分数
// 多选题总分JSON字段名为"checkScore"
@JsonProperty("checkScore")
private Integer examScoreCheck;
private Integer examScoreCheck;// 多选题的总分数
// 判断题总分JSON字段名为"judgeScore"
@JsonProperty("judgeScore")
private Integer examScoreJudge;
private Integer examScoreJudge;// 判断题的总分数
/**
* id
*/
// 考试创建人的名称JSON字段名为"creator"
// 注意这里假设只存储了创建人的名称实际可能需要根据ID查询数据库获取
@JsonProperty("creator")
private String examCreator;
private String examCreator;// 考试的创建者名称
/**
*
*/
// 考试的时间限制分钟JSON字段名为"elapse"
@JsonProperty("elapse")
private Integer examTimeLimit;
private Integer examTimeLimit;// 考试的时间限制,单位为分钟
/**
*
*/
// 考试开始时间JSON字段名为"startDate"
// 使用@JsonFormat注解指定日期格式和时区
@JsonProperty("startDate")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date examStartDate;
private Date examStartDate;// 考试的开始时间
/**
*
*/
// 考试结束时间JSON字段名为"endDate"
// 使用@JsonFormat注解指定日期格式和时区
@JsonProperty("endDate")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date examEndDate;
private Date examEndDate;// 考试的结束时间
/**
*
*/
// 创建时间JSON字段名为"createTime"
// 使用@JsonFormat注解指定日期格式和时区
@JsonProperty("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
private Date createTime;// 记录的创建时间
/**
*
*/
// 更新时间JSON字段名为"updateTime"
// 使用@JsonFormat注解指定日期格式和时区
@JsonProperty("updateTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
private Date updateTime;// 记录的更新时间
}

@ -16,7 +16,9 @@ import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor
public class JsonData implements Serializable {
/**
* id
*/
private static final long serialVersionUID = 1L;
/**

@ -10,15 +10,24 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
*
*/
@Data
public class PageVo {
/**
*
*/
@JsonProperty("actionEntitySet")
private List<ActionVo> actionVoList;
/**
*
*/
@JsonProperty("permissionId")
private String pageName;
/**
*
*/
@JsonProperty("permissionName")
private String pageDescription;
}

@ -10,7 +10,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* ,
*/
@Data
public class QuestionCreateSimplifyVo {
/**

@ -10,7 +10,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
*
*/
@Data
public class QuestionCreateVo {
/**

@ -11,7 +11,9 @@ import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
@Data
public class QuestionDetailVo {
/**

@ -8,7 +8,9 @@ package lsgwr.exam.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
*
*/
@Data
public class QuestionOptionCreateVo {

@ -8,18 +8,29 @@ package lsgwr.exam.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
*
*/
@Data
public class QuestionOptionVo {
/**
* id
*/
@JsonProperty("id")
private String questionOptionId;
/**
*
*/
@JsonProperty("content")
private String questionOptionContent;
/**
*
*/
@JsonProperty("answer")
private Boolean answer = false;
/**
*
*/
@JsonProperty("description")
private String questionOptionDescription;
}

@ -10,7 +10,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
*
*/
@Data
public class QuestionPageVo {

@ -13,15 +13,24 @@ import lsgwr.exam.entity.QuestionType;
import lombok.Data;
import java.util.List;
/**
*
*/
@Data
public class QuestionSelectionVo {
/**
*
*/
@JsonProperty("types")
private List<QuestionType> questionTypeList;
/**
*
*/
@JsonProperty("categories")
private List<QuestionCategory> questionCategoryList;
/**
*
*/
@JsonProperty("levels")
private List<QuestionLevel> questionLevelList;
}

@ -12,15 +12,24 @@ import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* VO
*/
@Data
public class QuestionVo {
/**
* idquestion
*/
@JsonProperty("id")
private String questionId;
/**
* question
*/
@JsonProperty("name")
private String questionName;
/**
* question
*/
@JsonProperty("score")
private Integer questionScore;

@ -11,7 +11,9 @@ import lombok.Data;
import java.util.HashMap;
import java.util.List;
/**
* VO
*/
@Data
public class RecordDetailVo {
/**

@ -13,7 +13,12 @@ import lombok.Data;
@JsonInclude(JsonInclude.Include.NON_NULL) // 避免返回NULL的字段
public class ResultVO<T> {
/**
*
* @param code
* @param msg
* @param data
*/
public ResultVO(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;

@ -10,18 +10,29 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* VO
*/
@Data
public class RoleVo {
/**
*
*/
@JsonProperty("id")
private String roleName;
/**
*
*/
@JsonProperty("name")
private String roleDescription;
/**
*
*/
@JsonProperty("describe")
private String roleDetail;
/**
*
*/
@JsonProperty("permissions")
private List<PageVo> pageVoList;
}

@ -8,19 +8,29 @@ package lsgwr.exam.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
*
*/
@Data
public class UserInfoVo {
/**
* ID
*/
@JsonProperty("id")
private String userId;
/**
* URL
*/
@JsonProperty("avatar")
private String userAvatar;
/**
*
*/
@JsonProperty("name")
private String userNickname;
/**
*
*/
@JsonProperty("username")
private String userUsername;

@ -8,30 +8,49 @@ package lsgwr.exam.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
*
*/
@Data
public class UserVo {
/**
* ID
*/
@JsonProperty("id")
private String userId;
/**
*
*/
@JsonProperty("username")
private String userUsername;
/**
*
*/
@JsonProperty("nickname")
private String userNickname;
/**
* ID
*/
@JsonProperty("role")
private Integer userRoleId;
/**
*
*/
@JsonProperty("avatar")
private String userAvatar;
/**
*
*/
@JsonProperty("description")
private String userDescription;
/**
*
*/
@JsonProperty("email")
private String userEmail;
/**
*
*/
@JsonProperty("phone")
private String userPhone;
}

@ -6,13 +6,91 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>logo.png">
<title>在线考试系统</title>
<style>#loading-mask{position:fixed;left:0;top:0;height:100%;width:100%;background:#fff;user-select:none;z-index:9999;overflow:hidden}.loading-wrapper{position:absolute;top:50%;left:50%;transform:translate(-50%,-100%)}.loading-dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:64px;width:64px;height:64px;box-sizing:border-box}.loading-dot i{width:22px;height:22px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.loading-dot i:nth-child(1){top:0;left:0}.loading-dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.loading-dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.loading-dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
<!-- 定义加载中的样式 -->
<style>#loading-mask{
position:fixed;left:0;top:0;height:100%;
width:100%;background:#fff;user-select:none;
z-index:9999;overflow:hidden}
.loading-wrapper{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-100%)
}
.loading-dot{
animation:antRotate 1.2s infinite linear;
transform:rotate(45deg);
position:relative;
display:inline-block;
font-size:64px;
width:64px;
height:64px;
box-sizing:border-box}
.loading-dot i{
width:22px;
height:22px;
position:absolute;
display:block;
background-color:#1890ff;
border-radius:100%;
transform:scale(.75);
transform-origin:50% 50%;
opacity:.3;
animation:antSpinMove 1s infinite linear alternate
}
.loading-dot i:nth-child(1){
top:0;
left:0
}
.loading-dot i:nth-child(2){
top:0;
right:0;
-webkit-animation-delay:.4s;
animation-delay:.4s
}
.loading-dot i:nth-child(3){
right:0;
bottom:0;
-webkit-animation-delay:.8s;
animation-delay:.8s
}
.loading-dot i:nth-child(4){
bottom:0;
left:0;
-webkit-animation-delay:1.2s;
animation-delay:1.2s
}
@keyframes antRotate{
to{
-webkit-transform:rotate(405deg);
transform:rotate(405deg)
}
}
@-webkit-keyframes antRotate{to{
-webkit-transform:rotate(405deg);
transform:rotate(405deg)
}
}
@keyframes antSpinMove{
to{
opacity:1
}
}
@-webkit-keyframes antSpinMove{
to{
opacity:1
}
}
</style>
</head>
<body>
<!-- 当JavaScript被禁用时显示提示信息 -->
<noscript>
<strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 应用的主容器 -->
<div id="app">
<!-- 加载中的遮罩层 -->
<div id="loading-mask">
<div class="loading-wrapper">
<span class="loading-dot loading-dot-spin"><i></i><i></i><i></i><i></i></span>

@ -1 +1,52 @@
#preloadingAnimation{position:fixed;left:0;top:0;height:100%;width:100%;background:#ffffff;user-select:none;z-index: 9999;overflow: hidden}.lds-roller{display:inline-block;position:relative;left:50%;top:50%;transform:translate(-50%,-50%);width:64px;height:64px;}.lds-roller div{animation:lds-roller 1.2s cubic-bezier(0.5,0,0.5,1) infinite;transform-origin:32px 32px;}.lds-roller div:after{content:" ";display:block;position:absolute;width:6px;height:6px;border-radius:50%;background:#13c2c2;margin:-3px 0 0 -3px;}.lds-roller div:nth-child(1){animation-delay:-0.036s;}.lds-roller div:nth-child(1):after{top:50px;left:50px;}.lds-roller div:nth-child(2){animation-delay:-0.072s;}.lds-roller div:nth-child(2):after{top:54px;left:45px;}.lds-roller div:nth-child(3){animation-delay:-0.108s;}.lds-roller div:nth-child(3):after{top:57px;left:39px;}.lds-roller div:nth-child(4){animation-delay:-0.144s;}.lds-roller div:nth-child(4):after{top:58px;left:32px;}.lds-roller div:nth-child(5){animation-delay:-0.18s;}.lds-roller div:nth-child(5):after{top:57px;left:25px;}.lds-roller div:nth-child(6){animation-delay:-0.216s;}.lds-roller div:nth-child(6):after{top:54px;left:19px;}.lds-roller div:nth-child(7){animation-delay:-0.252s;}.lds-roller div:nth-child(7):after{top:50px;left:14px;}.lds-roller div:nth-child(8){animation-delay:-0.288s;}.lds-roller div:nth-child(8):after{top:45px;left:10px;}#preloadingAnimation .load-tips{color: #13c2c2;font-size:2rem;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin-top:80px;text-align:center;width:400px;height:64px;} @keyframes lds-roller{0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);}}
#preloadingAnimation{
position:fixed;left:0;top:0;height:100%;width:100%;background:#ffffff;user-select:none;
z-index: 9999;overflow: hidden}
.lds-roller{
display:inline-block;
position:relative;
left:50%;
top:50%;
transform:translate(-50%,-50%);
width:64px;
height:64px;
}
.lds-roller div{
animation:lds-roller 1.2s cubic-bezier(0.5,0,0.5,1) infinite;
transform-origin:32px 32px;
}
.lds-roller div:after{
content:" ";
display:block;
position:absolute;
width:6px;
height:6px;
border-radius:50%;
background:#13c2c2;
margin:-3px 0 0 -3px;
}
.lds-roller div:nth-child(1){animation-delay:-0.036s;}
.lds-roller div:nth-child(1):after{top:50px;left:50px;}
.lds-roller div:nth-child(2){animation-delay:-0.072s;}
.lds-roller div:nth-child(2):after{top:54px;left:45px;}
.lds-roller div:nth-child(3){animation-delay:-0.108s;}
.lds-roller div:nth-child(3):after{top:57px;left:39px;}
.lds-roller div:nth-child(4){animation-delay:-0.144s;}
.lds-roller div:nth-child(4):after{top:58px;left:32px;}
.lds-roller div:nth-child(5){animation-delay:-0.18s;}
.lds-roller div:nth-child(5):after{top:57px;left:25px;}
.lds-roller div:nth-child(6){animation-delay:-0.216s;}
.lds-roller div:nth-child(6):after{top:54px;left:19px;}
.lds-roller div:nth-child(7){animation-delay:-0.252s;}
.lds-roller div:nth-child(7):after{top:50px;left:14px;}
.lds-roller div:nth-child(8){animation-delay:-0.288s;}
.lds-roller div:nth-child(8):after{top:45px;left:10px;}
#preloadingAnimation .load-tips{
color: #13c2c2;
font-size:2rem;
position:absolute;
left:50%;
top:50%;
transform:translate(-50%,-50%);margin-top:80px;
text-align:center;width:400px;height:64px;}
@keyframes lds-roller{0%{transform:rotate(0deg);}
100%{transform:rotate(360deg);}}

@ -1 +1,27 @@
<div id="preloadingAnimation"><div class=lds-roller><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div><div class=load-tips>Loading</div></div>
<div id="preloadingAnimation"><div class=lds-roller><div>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
</div>
<div class=load-tips>Loading</div>
</div>

@ -1,5 +1,31 @@
/**
* 预加载动画
*/
<div class="preloading-animate">
<!--预加载的包装容器 -->
<div class="preloading-wrapper">
<svg class="preloading-balls" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="67.802" cy="59.907" r="6" fill="#51CACC"><animate attributeName="cx" values="75;57.72542485937369" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="50;73.77641290737884" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#51CACC;#9DF871" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="46.079" cy="69.992" r="6" fill="#9DF871"><animate attributeName="cx" values="57.72542485937369;29.774575140626318" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="73.77641290737884;64.69463130731182" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#9DF871;#E0FF77" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="29.775" cy="52.449" r="6" fill="#E0FF77"><animate attributeName="cx" values="29.774575140626318;29.774575140626315" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="64.69463130731182;35.30536869268818" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#E0FF77;#DE9DD6" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="41.421" cy="31.521" r="6" fill="#DE9DD6"><animate attributeName="cx" values="29.774575140626315;57.72542485937368" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="35.30536869268818;26.22358709262116" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#DE9DD6;#FF708E" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="64.923" cy="36.13" r="6" fill="#FF708E"><animate attributeName="cx" values="57.72542485937368;75" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="26.22358709262116;49.99999999999999" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#FF708E;#51CACC" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle></svg>
<svg class="preloading-balls"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="67.802" cy="59.907" r="6" fill="#51CACC">
<animate attributeName="cx" values="75;57.72542485937369" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="cy" values="50;73.77641290737884" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="fill" values="#51CACC;#9DF871" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
</circle>
<circle cx="46.079" cy="69.992" r="6" fill="#9DF871"><animate attributeName="cx" values="57.72542485937369;29.774575140626318" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="cy" values="73.77641290737884;64.69463130731182" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="fill" values="#9DF871;#E0FF77" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
</circle><circle cx="29.775" cy="52.449" r="6" fill="#E0FF77">
<animate attributeName="cx" values="29.774575140626318;29.774575140626315" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="cy" values="64.69463130731182;35.30536869268818" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="fill" values="#E0FF77;#DE9DD6" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
</circle><circle cx="41.421" cy="31.521" r="6" fill="#DE9DD6"><animate attributeName="cx" values="29.774575140626315;57.72542485937368" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="cy" values="35.30536869268818;26.22358709262116" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="fill" values="#DE9DD6;#FF708E" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
</circle>
<circle cx="64.923" cy="36.13" r="6" fill="#FF708E"><animate attributeName="cx" values="57.72542485937368;75" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="cy" values="26.22358709262116;49.99999999999999" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
<animate attributeName="fill" values="#FF708E;#51CACC" keyTimes="0;1" dur="1s" repeatCount="indefinite"/>
</circle>
</svg>
</div>
</div>

@ -1 +1,20 @@
.preloading-animate{background:#ffffff;width:100%;height:100%;position:fixed;left:0;top:0;z-index:299;}.preloading-animate .preloading-wrapper{position:absolute;width:5rem;height:5rem;left:50%;top:50%;transform:translate(-50%,-50%);}.preloading-animate .preloading-wrapper .preloading-balls{font-size:5rem;}
.preloading-animate{
background:#ffffff;
width:100%;
height:100%;
position:fixed;
left:0;
top:0;
z-index:299;
}
.preloading-animate .preloading-wrapper{
position:absolute;
width:5rem;
height:5rem;
left:50%;
top:50%;
transform:translate(-50%,-50%);
}
.preloading-animate .preloading-wrapper .preloading-balls{
font-size:5rem;
}

@ -1,8 +1,15 @@
// 考试相关的接口,包括考试、问题、选项和评分等接口
/**
* @description: 考试模块接口包括考试问题选项和评分等接口
*/
import api from './index'
import { axios } from '../utils/request'
/**
* @description: 根据传入的参数获取问题列表
* @param {Object} parameter - 查询参数
* @returns {Promise} - 返回问题列表的Promise对象
*/
export function getQuestionList (parameter) {
return axios({
url: api.ExamQuestionList,
@ -11,6 +18,10 @@ export function getQuestionList (parameter) {
})
}
/**
* @description: 获取所有问题
* @returns {Promise} - 返回所有问题的Promise对象
*/
export function getQuestionAll () {
return axios({
url: api.ExamQuestionAll,
@ -18,6 +29,11 @@ export function getQuestionAll () {
})
}
/**
* @description: 更新问题信息
* @param {Object} parameter - 问题更新参数
* @returns {Promise} - 返回更新操作的Promise对象
*/
export function questionUpdate (parameter) {
console.log(parameter)
return axios({
@ -27,6 +43,10 @@ export function questionUpdate (parameter) {
})
}
/**
* @description: 获取问题的选项
* @returns {Promise} - 返回问题选项的Promise对象
*/
export function getQuestionSelection () {
return axios({
url: api.ExamQuestionSelection,
@ -37,6 +57,11 @@ export function getQuestionSelection () {
})
}
/**
* @description: 创建新问题
* @param {Object} parameter - 问题创建参数
* @returns {Promise} - 返回创建操作的Promise对象
*/
export function questionCreate (parameter) {
console.log(parameter)
return axios({
@ -46,6 +71,11 @@ export function questionCreate (parameter) {
})
}
/**
* @description: 根据传入的参数获取考试列表
* @param {Object} parameter - 查询参数
* @returns {Promise} - 返回考试列表的Promise对象
*/
export function getExamList (parameter) {
return axios({
url: api.ExamList,
@ -54,6 +84,10 @@ export function getExamList (parameter) {
})
}
/**
* @description: 获取所有考试
* @returns {Promise} - 返回所有考试的Promise对象
*/
export function getExamAll () {
return axios({
url: api.ExamAll,
@ -61,7 +95,10 @@ export function getExamAll () {
})
}
// 获取所有问题,按照单选、多选和判断进行分类
/**
* @description: 获取所有问题并按单选多选和判断进行分类
* @returns {Promise} - 返回分类问题列表的Promise对象
*/
export function getExamQuestionTypeList () {
return axios({
url: api.ExamQuestionTypeList,
@ -72,6 +109,10 @@ export function getExamQuestionTypeList () {
})
}
/**
* @description: 获取考试卡片列表
* @returns {Promise} - 返回考试卡片列表的Promise对象
*/
export function getExamCardList () {
return axios({
url: api.ExamCardList,
@ -82,6 +123,11 @@ export function getExamCardList () {
})
}
/**
* @description: 创建新考试
* @param {Object} parameter - 考试创建参数
* @returns {Promise} - 返回创建操作的Promise对象
*/
export function examCreate (parameter) {
console.log(parameter)
return axios({
@ -91,6 +137,11 @@ export function examCreate (parameter) {
})
}
/**
* @description: 更新考试信息
* @param {Object} parameter - 考试更新参数
* @returns {Promise} - 返回更新操作的Promise对象
*/
export function examUpdate (parameter) {
console.log(parameter)
return axios({
@ -100,6 +151,11 @@ export function examUpdate (parameter) {
})
}
/**
* @description: 根据考试ID获取考试详情
* @param {String} examId - 考试ID
* @returns {Promise} - 返回考试详情的Promise对象
*/
export function getExamDetail (examId) {
return axios({
url: api.ExamDetail + examId,
@ -110,6 +166,11 @@ export function getExamDetail (examId) {
})
}
/**
* @description: 根据记录ID获取考试记录详情
* @param {String} recordId - 记录ID
* @returns {Promise} - 返回考试记录详情的Promise对象
*/
export function getExamRecordDetail (recordId) {
return axios({
url: api.recordDetail + recordId,
@ -120,6 +181,11 @@ export function getExamRecordDetail (recordId) {
})
}
/**
* @description: 根据问题ID获取问题详情
* @param {String} questionId - 问题ID
* @returns {Promise} - 返回问题详情的Promise对象
*/
export function getQuestionDetail (questionId) {
return axios({
url: api.QuestionDetail + questionId,
@ -130,6 +196,12 @@ export function getQuestionDetail (questionId) {
})
}
/**
* @description: 提交考试答案完成考试
* @param {String} examId - 考试ID
* @param {Object} answersMap - 答案映射
* @returns {Promise} - 返回完成考试的Promise对象
*/
export function finishExam (examId, answersMap) {
console.log(answersMap)
return axios({
@ -142,6 +214,10 @@ export function finishExam (examId, answersMap) {
})
}
/**
* @description: 获取所有考试记录
* @returns {Promise} - 返回所有考试记录的Promise对象
*/
export function getExamRecordList () {
return axios({
url: api.ExamRecordList,

@ -1,38 +1,117 @@
/**
* @description: api接口统一管理
*/
const api = {
/**
* @description: 用户登录的接口
*/
Login: '/auth/login',
/**
* @description: 用户注销的接口
*/
Logout: '/auth/logout',
/**
* @description: 重置密码的接口
*/
ForgePassword: '/auth/forge-password',
/**
* @description: 用户注册的接口
*/
Register: '/auth/register',
/**
* @description: 两步验证码的接口
*/
twoStepCode: '/auth/2step-code',
/**
* @description: 发送短信的接口
*/
SendSms: '/account/sms',
/**
* @description: 发送短信错误的接口
*/
SendSmsErr: '/account/sms_err',
// get my info
/**
* @description: 获取用户信息的接口
*/
UserInfo: '/user/info',
// 下面是自己的用户认证的接口
/**
* @description: 用户注册自定义的接口
*/
UserRegister: '/user/register',
/**
* @description: 用户登录自定义的接口
*/
UserLogin: '/user/login',
// 考试的接口
/**
* @description: 获取考试问题列表的接口
*/
ExamQuestionList: '/exam/question/list',
/**
* @description: 获取所有考试问题的接口
*/
ExamQuestionAll: '/exam/question/all',
/**
* @description: 更新考试问题的接口
*/
ExamQuestionUpdate: '/exam/question/update',
/**
* @description: 选择考试问题的接口
*/
ExamQuestionSelection: '/exam/question/selection',
/**
* @description: 创建考试问题的接口
*/
ExamQuestionCreate: '/exam/question/create',
/**
* @description: 获取考试列表的接口
*/
ExamList: '/exam/list',
/**
* @description: 获取所有考试的接口
*/
ExamAll: '/exam/all',
// 获取问题列表,按照单选、多选和判断进行分类
/**
* @description: 按照单选多选和判断分类获取问题列表的接口
*/
ExamQuestionTypeList: '/exam/question/type/list',
/**
* @description: 创建考试的接口
*/
ExamCreate: '/exam/create',
/**
* @description: 更新考试的接口
*/
ExamUpdate: '/exam/update',
/**
* @description: 获取考试卡片列表的接口
*/
ExamCardList: '/exam/card/list',
// 获取考试详情
/**
* @description: 获取考试详情的接口
*/
ExamDetail: '/exam/detail/',
// 获取考试详情
/**
* @description: 获取问题详情的接口
*/
QuestionDetail: '/exam/question/detail/',
// 交卷
/**
* @description: 交卷的接口
*/
FinishExam: '/exam/finish/',
/**
* @description: 获取考试记录列表的接口
*/
ExamRecordList: '/exam/record/list',
/**
* @description: 获取考试记录详情的接口
*/
recordDetail: '/exam/record/detail/'
}
/**
* @description: 默认导出api对象
*/
export default api

@ -2,12 +2,12 @@ import api from './index'
import { axios } from '../utils/request'
/**
* login func
* parameter: {
* username: '',
* password: '',
* remember_me: true,
* captcha: '12345'
* 用户登录功能
* 参数: {
* username: 用户名
* password: 密码
* remember_me: 是否记住
* captcha: 验证码
* }
* @param parameter
* @returns {*}
@ -21,6 +21,11 @@ export function login (parameter) {
})
}
/**
* 获取短信验证码
* @param parameter
* @returns {*}
*/
export function getSmsCaptcha (parameter) {
return axios({
url: api.SendSms,
@ -29,6 +34,10 @@ export function getSmsCaptcha (parameter) {
})
}
/**
* 获取用户信息
* @returns {*}
*/
export function getInfo () {
return axios({
url: api.UserInfo,
@ -39,6 +48,10 @@ export function getInfo () {
})
}
/**
* 用户登出
* @returns {*}
*/
export function logout () {
return axios({
url: api.Logout,
@ -50,8 +63,9 @@ export function logout () {
}
/**
* get user 2step code open?
* @param parameter {*}
* 获取用户两步验证码开启状态
* @param parameter
* @returns {*}
*/
export function get2step (parameter) {
return axios({

@ -3,6 +3,11 @@
import api from './index'
import { axios } from '../utils/request'
/**
* 用户登录
* @param {Object} parameter - 登录参数
* @returns {Promise} - 返回登录请求的Promise
*/
export function login (parameter) {
return axios({
url: api.UserLogin,
@ -11,6 +16,11 @@ export function login (parameter) {
})
}
/**
* 用户注册
* @param {Object} parameter - 注册参数
* @returns {Promise} - 返回注册请求的Promise
*/
export function register (parameter) {
return axios({
url: api.UserRegister,

@ -1,24 +1,33 @@
<template>
<!--异常页面-->
<div class="exception">
<!--背景图-->
<div class="imgBlock">
<!-- 图片元素根据传入的type动态设置背景图片 -->
<div class="imgEle" :style="{backgroundImage: `url(${config[type].img})`}">
</div>
</div>
<!-- 内容区块 -->
<div class="content">
<!-- 根据传入的type动态显示标题 -->
<h1>{{ config[type].title }}</h1>
<!-- 根据传入的type动态显示描述 -->
<div class="desc">{{ config[type].desc }}</div>
<!-- 操作按钮区块 -->
<div class="actions">
<!-- 点击按钮返回首页 -->
<a-button type="primary" @click="handleToHome"></a-button>
</div>
</div>
</div>
</template>
<script>
<script>//
import types from './type'
export default {
name: 'Exception',
//
props: {
type: {
type: String,
@ -27,11 +36,14 @@ export default {
},
data () {
return {
//
config: types
}
},
methods: {
//
handleToHome () {
// 使Vue Routerdashboard
this.$router.push({ name: 'dashboard' })
}
}

@ -1,19 +1,27 @@
/**
* 定义一个常量对象types用于存储不同HTTP状态码的信息
* 每个状态码都包含一个图片URL状态码标题和描述信息
* 这些信息可以用于在界面上展示更友好的错误提示
*/
const types = {
// 403状态码信息禁止访问
403: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
title: '403',
desc: '抱歉,你无权访问该页面'
},
// 404状态码信息页面未找到
404: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
title: '404',
desc: '抱歉,你访问的页面不存在或仍在开发中'
},
// 500状态码信息服务器内部错误
500: {
img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
title: '500',
desc: '抱歉,服务器出错了'
}
}
// 将types对象导出以便在其他模块中使用
export default types

@ -1,18 +1,25 @@
<template>
<!-- 底部信息栏 -->
<div class="footer">
<!-- 友情链接区域 -->
<div class="links">
<!-- 链接项目代码仓库 -->
<a href="https://github.com/19920625lsg/spring-boot-online-exam" target="_blank">代码仓</a>
<!-- 链接关于我的页面 -->
<a href="https://19920625lsg.github.io" target="_blank">关于我</a>
<!-- 链接联系我 -->
<a href="mailto:liangshanguang2@gmail.com">联系我</a>
</div>
<!-- 版权信息区域 -->
<div class="copyright">
Copyright
<!-- 版权图标 -->
<a-icon type="copyright" /> 2020 <span>Liang Shan Guang</span>
</div>
</div>
</template>
<script>
<script>//
export default {
name: 'GlobalFooter',
data () {
@ -21,7 +28,7 @@ export default {
}
</script>
<style lang="less" scoped>
<style lang="less" scoped>//
.footer {
padding: 0 16px;
margin: 24px 0 24px;

@ -1,22 +1,31 @@
<template>
<!-- 使用 transition 组件为头部添加动画效果 -->
<transition name="showHeader">
<!-- 根据 visible 属性控制头部的显示与隐藏 -->
<div v-if="visible" class="header-animat">
<!-- 根据 visible 属性fixedHeadersidebarOpened 等属性动态设置头部样式 -->
<a-layout-header
v-if="visible"
:class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]"
:style="{ padding: '0' }">
<!-- 根据 mode 属性判断菜单类型并根据不同设备类型显示不同的折叠/展开图标 -->
<div v-if="mode === 'sidemenu'" class="header">
<a-icon v-if="device==='mobile'" class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle"/>
<a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle"/>
<!-- 渲染用户菜单组件 -->
<user-menu></user-menu>
</div>
<!-- 对于顶部菜单根据设备类型和折叠状态显示不同布局和图标 -->
<div v-else :class="['top-nav-header-index', theme]">
<div class="header-index-wide">
<div class="header-index-left">
<!-- 渲染 logo 组件根据设备类型决定是否显示标题 -->
<logo class="top-nav-header" :show-title="device !== 'mobile'"/>
<!-- 根据设备类型和折叠状态渲染菜单 -->
<s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" />
<a-icon v-else class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle" />
</div>
<!-- 渲染用户菜单组件 -->
<user-menu class="header-index-right"></user-menu>
</div>
</div>
@ -25,10 +34,11 @@
</transition>
</template>
<script>
<script>// logo
import UserMenu from '../tools/UserMenu'
import SMenu from '../Menu/'
import Logo from '../tools/Logo'
// 使
import { mixin } from '../../utils/mixin'
export default {
@ -39,6 +49,7 @@ export default {
Logo
},
mixins: [mixin],
//
props: {
mode: {
type: String,
@ -67,14 +78,18 @@ export default {
},
data () {
return {
//
visible: true,
//
oldScrollTop: 0
}
},
// /
mounted () {
document.body.addEventListener('scroll', this.handleScroll, { passive: true })
},
methods: {
//
handleScroll () {
if (!this.autoHideHeader) {
return
@ -96,17 +111,19 @@ export default {
})
}
},
// toggle
toggle () {
this.$emit('toggle')
}
},
//
beforeDestroy () {
document.body.removeEventListener('scroll', this.handleScroll, true)
}
}
</script>
<style lang="less">
<style lang="less">//
.header-animat{
position: relative;
z-index: 2;

@ -1,11 +1,14 @@
<template>
<!-- 侧边栏组件包含logo和菜单 -->
<a-layout-sider
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]"
width="256px"
:collapsible="collapsible"
v-model="collapsed"
:trigger="null">
<!-- Logo组件 -->
<logo />
<!-- 菜单组件 -->
<s-menu
:collapsed="collapsed"
:menu="menus"
@ -17,7 +20,7 @@
</template>
<script>
<script>// LogoSMenu
import Logo from '../../components/tools/Logo'
import SMenu from './index'
import { mixin, mixinDevice } from '../../utils/mixin'

@ -6,20 +6,24 @@ const { Item, SubMenu } = Menu
export default {
name: 'SMenu',
props: {
// 菜单数据
menu: {
type: Array,
required: true
},
// 主题
theme: {
type: String,
required: false,
default: 'dark'
},
// 模式
mode: {
type: String,
required: false,
default: 'inline'
},
// 是否折叠
collapsed: {
type: Boolean,
required: false,
@ -28,12 +32,16 @@ export default {
},
data () {
return {
// 打开的菜单项
openKeys: [],
// 选择的菜单项
selectedKeys: [],
// 缓存的打开的菜单项
cachedOpenKeys: []
}
},
computed: {
// 根菜单项的key
rootSubmenuKeys: vm => {
const keys = []
vm.menu.forEach(item => keys.push(item.path))
@ -41,23 +49,29 @@ export default {
}
},
mounted () {
// 组件挂载时更新菜单
this.updateMenu()
},
watch: {
// 监听折叠状态的变化
collapsed (val) {
if (val) {
// 折叠时,缓存打开的菜单项
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
} else {
// 展开时,恢复打开的菜单项
this.openKeys = this.cachedOpenKeys
}
},
// 监听路由的变化
$route: function () {
// 更新菜单
this.updateMenu()
}
},
methods: {
// select menu item
// 选择菜单项
onOpenChange (openKeys) {
// 在水平模式下时执行,并且不再执行后续
if (this.mode === 'horizontal') {
@ -72,6 +86,7 @@ export default {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
},
// 更新菜单
updateMenu () {
const routes = this.$route.matched.concat()
const { hidden } = this.$route.meta
@ -91,13 +106,14 @@ export default {
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
},
// render
// 渲染
renderItem (menu) {
if (!menu.hidden) {
return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu)
}
return null
},
// 渲染菜单项
renderMenuItem (menu) {
const target = menu.meta.target || null
const tag = target && 'a' || 'router-link'
@ -122,6 +138,7 @@ export default {
</Item>
)
},
// 渲染子菜单
renderSubMenu (menu) {
const itemArr = []
if (!menu.hideChildrenInMenu) {
@ -137,6 +154,7 @@ export default {
</SubMenu>
)
},
// 渲染图标
renderIcon (icon) {
if (icon === 'none' || icon === undefined) {
return null

@ -26,22 +26,35 @@
-->
<script>
/**
* 多标签页组件
*/
export default {
name: 'MultiTab',
data () {
return {
// fullPath
fullPathList: [],
//
pages: [],
// fullPath
activeKey: '',
//
newTabIndex: 0
}
},
created () {
//
this.pages.push(this.$route)
this.fullPathList.push(this.$route.fullPath)
this.selectedLastPath()
},
methods: {
/**
* 处理标签页编辑事件
* @param {String} targetKey - 目标标签页的 fullPath
* @param {String} action - 操作类型例如'remove'
*/
onEdit (targetKey, action) {
this[action](targetKey)
},
@ -53,11 +66,14 @@ export default {
this.selectedLastPath()
}
},
/**
* 选择最后一个标签页
*/
selectedLastPath () {
this.activeKey = this.fullPathList[this.fullPathList.length - 1]
},
// content menu
//
closeThat (e) {
this.remove(e)
},
@ -93,6 +109,10 @@ export default {
}
})
},
/**
* 处理右键菜单点击事件
* @param {Object} { key, item, domEvent } - 菜单项的相关信息
*/
closeMenuClick ({ key, item, domEvent }) {
const vkey = domEvent.target.getAttribute('data-vkey')
switch (key) {
@ -111,6 +131,10 @@ export default {
break
}
},
/**
* 渲染标签页右键菜单
* @param {String} e - 标签页的 fullPath
*/
renderTabPaneMenu (e) {
return (
<a-menu {...{ on: { click: this.closeMenuClick } }}>
@ -122,6 +146,11 @@ export default {
)
},
// render
/**
* 渲染标签页
* @param {String} title - 标签页的标题
* @param {String} keyPath - 标签页的 fullPath
*/
renderTabPane (title, keyPath) {
const menu = this.renderTabPaneMenu(keyPath)
@ -133,6 +162,10 @@ export default {
}
},
watch: {
/**
* 监听路由变化更新标签页列表和激活的标签页
* @param {Object} newVal - 新的路由信息
*/
'$route': function (newVal) {
this.activeKey = newVal.fullPath
if (this.fullPathList.indexOf(newVal.fullPath) < 0) {
@ -140,6 +173,10 @@ export default {
this.pages.push(newVal)
}
},
/**
* 监听激活的标签页变化更新路由
* @param {String} newPathKey - 新的激活标签页的 fullPath
*/
activeKey: function (newPathKey) {
this.$router.push({ path: newPathKey })
}

@ -1,4 +1,6 @@
<template>
<!-- 进入层展示一个通知消息组件透过点击设置触发展示-->
<!-- 通过 <a-popover> 实现通知消息框配置消息流和点击事件-->
<a-popover
v-model="visible"
trigger="click"
@ -9,23 +11,40 @@
:overlayStyle="{ width: '300px', top: '50px' }"
>
<template slot="content">
<!-- 透过加载线段和标签分类显示通知 -->
<a-spin :spinning="loadding">
<a-tabs>
<a-tab-pane tab="通知" key="1">
<!-- 显示通知列表具体消息以组件形式显示 -->
<a-list>
<a-list-item>
<a-list-item-meta title="你收到了 14 份新周报" description="一年前">
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/>
<a-list-item-meta
title="你收到了 14 份新周报"
description="一年前">
<a-avatar
style="background-color: white"
slot="avatar"
src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前">
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/>
<a-list-item-meta
title="你推荐的 曲妮妮 已通过第三轮面试"
description="一年前">
<a-avatar
style="background-color: white"
slot="avatar"
src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/>
</a-list-item-meta>
</a-list-item>
<a-list-item>
<a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前">
<a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/>
<a-list-item-meta
title="这种模板可以区分多种通知类型"
description="一年前">
<a-avatar
style="background-color: white"
slot="avatar"
src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/>
</a-list-item-meta>
</a-list-item>
</a-list>
@ -39,6 +58,7 @@
</a-tabs>
</a-spin>
</template>
<!-- 点击回调消息分类加载功能 -->
<span @click="fetchNotice" class="header-notice">
<a-badge count="12">
<a-icon style="font-size: 16px; padding: 4px" type="bell" />
@ -49,41 +69,45 @@
<script>
export default {
name: 'HeaderNotice',
name: 'HeaderNotice', //
data () {
return {
loadding: false,
visible: false
loadding: false, //
visible: false //
}
},
methods: {
//
fetchNotice () {
if (!this.visible) {
this.loadding = true
//
setTimeout(() => {
this.loadding = false
}, 2000)
} else {
this.loadding = false
}
this.visible = !this.visible
this.visible = !this.visible //
}
}
}
</script>
<style lang="css">
.header-notice-wrapper {
top: 50px !important;
}
/* 通知框样式配置 */
.header-notice-wrapper {
top: 50px !important;
}
</style>
<style lang="less" scoped>
.header-notice{
display: inline-block;
transition: all 0.3s;
/* 层级框样式 */
.header-notice{
display: inline-block;
transition: all 0.3s;
span {
vertical-align: initial;
}
span {
vertical-align: initial;
}
}
</style>

@ -1,28 +1,38 @@
<template>
<!-- 页面头部组件 -->
<div class="page-header">
<div class="page-header-index-wide">
<!-- 面包屑导航 -->
<s-breadcrumb />
<div class="detail">
<!-- 主要内容区域 -->
<div class="main" v-if="!$route.meta.hiddenHeaderContent">
<div class="row">
<!-- Logo图片 -->
<img v-if="logo" :src="logo" class="logo"/>
<!-- 页面标题 -->
<h1 v-if="title" class="title">{{ title }}</h1>
<!-- 自定义操作区域 -->
<div class="action">
<slot name="action"></slot>
</div>
</div>
<div class="row">
<!-- 头像 -->
<div v-if="avatar" class="avatar">
<a-avatar :src="avatar" />
</div>
<!-- 自定义内容区域 -->
<div v-if="this.$slots.content" class="headerContent">
<slot name="content"></slot>
</div>
<!-- 额外的自定义区域 -->
<div v-if="this.$slots.extra" class="extra">
<slot name="extra"></slot>
</div>
</div>
<div>
<!-- 页面菜单 -->
<slot name="pageMenu"></slot>
</div>
</div>
@ -31,25 +41,28 @@
</div>
</template>
<script>
<script>//
import Breadcrumb from '../../components/tools/Breadcrumb'
//
export default {
name: 'PageHeader',
components: {
's-breadcrumb': Breadcrumb
},
props: {
// true
title: {
type: [String, Boolean],
default: true,
required: false
},
// Logo
logo: {
type: String,
default: '',
required: false
},
//
avatar: {
type: String,
default: '',
@ -62,7 +75,7 @@ export default {
}
</script>
<style lang="less" scoped>
<style lang="less" scoped>//
.page-header {
background: #fff;
padding: 16px 32px 0;
@ -152,7 +165,7 @@ export default {
}
}
}
//
.mobile .page-header {
.main {
.row {

@ -1,28 +1,34 @@
<template>
<!-- 结果展示组件 -->
<div class="result">
<!-- 根据成功或错误状态动态显示对应图标 -->
<div>
<a-icon :class="{ 'icon': true, [`${type}`]: true }" :type="localIsSuccess ? 'check-circle' : 'close-circle'"/>
</div>
<!-- 标题区域支持自定义插槽内容 -->
<div class="title">
<slot name="title">
{{ title }}
</slot>
</div>
<!-- 描述区域支持自定义插槽内容 -->
<div class="description">
<slot name="description">
{{ description }}
</slot>
</div>
<!-- 额外信息区域根据插槽内容动态显示 -->
<div class="extra" v-if="$slots.default">
<slot></slot>
</div>
<!-- 操作区域根据插槽内容动态显示 -->
<div class="action" v-if="$slots.action">
<slot name="action"></slot>
</div>
</div>
</template>
<script>
<script>//
const resultEnum = ['success', 'error']
export default {
@ -33,6 +39,7 @@ export default {
type: Boolean,
default: false
},
// success error
type: {
type: String,
default: resultEnum[0],
@ -40,16 +47,19 @@ export default {
return (val) => resultEnum.includes(val)
}
},
//
title: {
type: String,
default: ''
},
//
description: {
type: String,
default: ''
}
},
computed: {
//
localIsSuccess: function () {
return this.type === resultEnum[0]
}
@ -69,9 +79,11 @@ export default {
line-height: 72px;
margin-bottom: 24px;
}
//
.success {
color: #52c41a;
}
//
.error {
color: red;
}

@ -1,12 +1,22 @@
<template>
<!-- 设置项容器 -->
<div class="setting-drawer-index-item">
<!-- 设置项标题 -->
<h3 class="setting-drawer-index-title">{{ title }}</h3>
<!-- 插槽内容用于显示设置项的具体内容 -->
<slot></slot>
<!-- 根据props中的divider决定是否显示分割线 -->
<a-divider v-if="divider"/>
</div>
</template>
<script>
/**
* 设置项组件
*
* @param {String} title - 设置项的标题默认为空字符串
* @param {Boolean} divider - 是否显示设置项后的分割线默认不显示
*/
export default {
name: 'SettingItem',
props: {
@ -22,11 +32,11 @@ export default {
}
</script>
<style lang="less" scoped>
<style lang="less" scoped>/* 设置项样式 */
.setting-drawer-index-item {
margin-bottom: 24px;
/* 设置项标题样式 */
.setting-drawer-index-title {
font-size: 14px;
color: rgba(0, 0, 0, .85);

@ -1,15 +1,18 @@
<template>
<!-- 标准表单行组件根据不同的属性组合有不同的样式表现 -->
<div :class="[prefixCls, lastCls, blockCls, gridCls]">
<!-- 标题部分只有当title属性存在时才显示 -->
<div v-if="title" class="antd-pro-components-standard-form-row-index-label">
<span>{{ title }}</span>
</div>
<!-- 内容部分使用slot以便于自定义内容 -->
<div class="antd-pro-components-standard-form-row-index-content">
<slot></slot>
</div>
</div>
</template>
<script>
<script>//
const classes = [
'antd-pro-components-standard-form-row-index-standardFormRowBlock',
'antd-pro-components-standard-form-row-index-standardFormRowGrid',
@ -17,6 +20,7 @@ const classes = [
]
export default {
name: 'StandardFormRow',
//
props: {
prefixCls: {
type: String,
@ -36,6 +40,7 @@ export default {
type: Boolean
}
},
// props
computed: {
lastCls () {
return this.last ? classes[2] : null
@ -50,9 +55,9 @@ export default {
}
</script>
<style lang="less" scoped>
<style lang="less" scoped>//
@import '../index.less';
//
.antd-pro-components-standard-form-row-index-standardFormRow {
display: flex;
margin-bottom: 16px;
@ -71,7 +76,7 @@ export default {
padding: 0;
line-height: 32px;
}
//
.antd-pro-components-standard-form-row-index-label {
flex: 0 0 auto;
margin-right: 24px;
@ -87,27 +92,27 @@ export default {
}
}
}
//
.antd-pro-components-standard-form-row-index-content {
flex: 1 1 0;
/deep/ .ant-form-item:last-child {
margin-right: 0;
}
}
//
&.antd-pro-components-standard-form-row-index-standardFormRowLast {
margin-bottom: 0;
padding-bottom: 0;
border: none;
}
// 使block
&.antd-pro-components-standard-form-row-index-standardFormRowBlock {
/deep/ .ant-form-item,
div.ant-form-item-control-wrapper {
display: block;
}
}
// 使grid
&.antd-pro-components-standard-form-row-index-standardFormRowGrid {
/deep/ .ant-form-item,
div.ant-form-item-control-wrapper {

@ -7,7 +7,9 @@
* @param children
* @returns {*[]}
*/
// 导出一个函数,用于过滤掉空节点
export function filterEmpty (children = []) {
// 过滤掉没有tag属性和text属性为空字符串的节点
return children.filter(c => c.tag || (c.text && c.text.trim() !== ''))
}
@ -15,12 +17,17 @@ export function filterEmpty (children = []) {
* 获取字符串长度英文字符 长度1中文字符长度2
* @param {*} str
*/
// 导出一个函数,用于获取字符串长度
export const getStrFullLength = (str = '') =>
// 将字符串分割成字符数组然后使用reduce方法遍历数组计算每个字符的长度
str.split('').reduce((pre, cur) => {
// 获取字符的Unicode编码
const charCode = cur.charCodeAt(0)
// 如果字符的Unicode编码在0-128之间说明是英文字符长度为1
if (charCode >= 0 && charCode <= 128) {
return pre + 1
}
// 否则说明是中文字符长度为2
return pre + 2
}, 0)
@ -29,18 +36,26 @@ export const getStrFullLength = (str = '') =>
* @param {*} str
* @param {*} maxLength
*/
// 导出一个函数,用于截取字符串
export const cutStrByFullLength = (str = '', maxLength) => {
// 初始化显示长度为0
let showLength = 0
// 将字符串分割成字符数组然后使用reduce方法遍历数组截取字符串
return str.split('').reduce((pre, cur) => {
// 获取字符的Unicode编码
const charCode = cur.charCodeAt(0)
// 如果字符的Unicode编码在0-128之间说明是英文字符长度为1
if (charCode >= 0 && charCode <= 128) {
showLength += 1
} else {
// 否则说明是中文字符长度为2
showLength += 2
}
// 如果显示长度小于等于maxLength则将字符添加到结果中
if (showLength <= maxLength) {
return pre + cur
}
// 否则,返回结果
return pre
}, '')
}

@ -1,6 +1,9 @@
<template>
<!-- 面包屑组件 -->
<a-breadcrumb class="breadcrumb">
<!-- 遍历面包屑列表生成每个面包屑项 -->
<a-breadcrumb-item v-for="(item, index) in breadList" :key="item.name">
<!-- 根据条件渲染面包屑项的链接或文本 -->
<router-link
v-if="item.name != name && index != 1"
:to="{ path: item.path === '' ? '/' : item.path }"
@ -14,26 +17,35 @@
export default {
data () {
return {
//
name: '',
//
breadList: []
}
},
created () {
//
this.getBreadcrumb()
},
methods: {
/**
* 获取面包屑信息
* 该方法根据当前路由生成面包屑列表
*/
getBreadcrumb () {
this.breadList = []
// this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: ''}})
//
this.name = this.$route.name
this.$route.matched.forEach(item => {
// item.name !== 'index' && this.breadList.push(item)
//
this.breadList.push(item)
})
}
},
watch: {
//
$route () {
this.getBreadcrumb()
}

@ -1,14 +1,19 @@
<template>
<!-- 头部信息组件根据props调整样式和布局 -->
<div class="head-info" :class="center && 'center'">
<!-- 显示标题 -->
<span>{{ title }}</span>
<!-- 显示内容 -->
<p>{{ content }}</p>
<!-- 根据bordered prop决定是否显示边框 -->
<em v-if="bordered"/>
</div>
</template>
<script>
<script>//
export default {
name: 'HeadInfo',
//
props: {
title: {
type: String,

@ -1,26 +1,34 @@
<template>
<!-- Logo组件主体 -->
<div class="logo">
<!-- 使用router-link组件创建到dashboard的链接 -->
<router-link :to="{name:'dashboard'}">
<!-- 渲染LogoSvg组件带alt属性说明 -->
<LogoSvg alt="logo" />
<!-- 根据showTitle属性决定是否显示标题 -->
<h1 v-if="showTitle">{{ title }}</h1>
</router-link>
</div>
</template>
<script>
<script>// logo.svg
import LogoSvg from '../../assets/logo.svg?inline'
// Logo
export default {
name: 'Logo',
//
components: {
LogoSvg
},
props: {
// titleString'Online Exam'
title: {
type: String,
default: 'Online Exam',
required: false
},
// showTitleBooleantrue
showTitle: {
type: Boolean,
default: true,

@ -1,19 +1,28 @@
<template>
<!-- 用户信息和操作菜单的容器 -->
<div class="user-wrapper">
<div class="content-box">
<!-- 用户下拉菜单 -->
<a-dropdown>
<!-- 用户信息展示区域 -->
<span class="action ant-dropdown-link user-dropdown-menu">
<!-- 用户头像 -->
<a-avatar class="avatar" size="small" :src="avatar()"/>
<!-- 用户昵称 -->
<span>{{ nickname() }}</span>
</span>
<!-- 下拉菜单内容 -->
<a-menu slot="overlay" class="user-dropdown-menu-wrapper">
<!-- 账户设置菜单项 -->
<a-menu-item key="1">
<router-link :to="{ name: 'settings' }">
<a-icon type="setting"/>
<span>账户设置</span>
</router-link>
</a-menu-item>
<!-- 菜单分割符 -->
<a-menu-divider/>
<!-- 退出登录菜单项 -->
<a-menu-item key="3">
<a href="javascript:;" @click="handleLogout">
<a-icon type="logout"/>
@ -44,6 +53,7 @@ export default {
return that.Logout({}).then(() => {
window.location.reload()
}).catch(err => {
//
that.$message.error({
title: '错误',
description: err.message
@ -51,6 +61,7 @@ export default {
})
},
onCancel () {
//
}
})
}

@ -14,16 +14,26 @@ import store from '../../store'
*
* @see https://github.com/sendya/ant-design-pro-vue/pull/53
*/
/**
* 创建一个 Vue 自定义指令 'action'用于控制元素基于用户权限的显示
*/
const action = Vue.directive('action', {
inserted: function (el, binding, vnode) {
// 获取指令的参数,即动作名称
const actionName = binding.arg
// 从 Vuex store 中获取当前用户的角色信息
const roles = store.getters.roles
// 获取当前路由的权限配置
const elVal = vnode.context.$route.meta.permission
// 确保权限配置为数组形式
const permissionId = elVal instanceof String && [elVal] || elVal
// 遍历用户角色的权限列表
roles.permissions.forEach(p => {
// 如果当前权限ID不在元素的权限配置中直接返回
if (!permissionId.includes(p.permissionId)) {
return
}
// 如果当前权限有动作列表限制,且动作列表中不包含指令的参数,移除或隐藏元素
if (p.actionList && !p.actionList.includes(actionName)) {
el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none')
}

@ -1,16 +1,20 @@
<template>
<!-- 主要用于渲染子路由组件 -->
<div>
<router-view />
</div>
</template>
<script>
/**
* 定义一个名为 BlankLayout Vue 组件
* 该组件用作空白布局通常用于需要一个干净的页面来进行特定的功能或展示
*/
export default {
name: 'BlankLayout'
}
</script>
<style scoped>
/* 此处添加特定于组件的样式,使用 scoped 属性确保样式仅在本组件内生效 */
</style>

@ -1,10 +1,16 @@
<template>
<!-- 根据路由元数据决定是否显示页面头部内容 -->
<div :style="!$route.meta.hiddenHeaderContent ? 'margin: -24px -24px 0px;' : null">
<!-- pageHeader , route meta :true on hide -->
<!-- 条件渲染页面头部组件 -->
<page-header v-if="!$route.meta.hiddenHeaderContent" :title="pageTitle" :logo="logo" :avatar="avatar">
<!-- 插槽用于自定义操作按钮 -->
<slot slot="action" name="action"></slot>
<!-- 插槽用于自定义头部内容 -->
<slot slot="content" name="headerContent"></slot>
<!-- 默认头部内容当没有自定义头部内容且存在描述时显示 -->
<div slot="content" v-if="!this.$slots.headerContent && description">
<!-- 显示链接列表 -->
<p style="font-size: 14px;color: rgba(0,0,0,.65)">{{ description }}</p>
<div class="link">
<template v-for="(link, index) in linkList">
@ -15,12 +21,15 @@
</template>
</div>
</div>
<!-- 插槽用于自定义额外内容 -->
<slot slot="extra" name="extra">
<div class="extra-img">
<img v-if="typeof extraImage !== 'undefined'" :src="extraImage"/>
</div>
</slot>
<!-- 页面菜单区域 -->
<div slot="pageMenu">
<!-- 条件渲染搜索框 -->
<div class="page-menu-search" v-if="search">
<a-input-search
style="width: 80%; max-width: 522px;"
@ -29,6 +38,7 @@
enterButton="搜索"
/>
</div>
<!-- 条件渲染标签页 -->
<div class="page-menu-tabs" v-if="tabs && tabs.items">
<!-- @change="callback" :activeKey="activeKey" -->
<a-tabs :tabBarStyle="{margin: 0}" :activeKey="tabs.active()" @change="tabs.callback">
@ -37,10 +47,13 @@
</div>
</div>
</page-header>
<!-- 主要内容区域 -->
<div class="content">
<!-- 插槽用于自定义页面内容 -->
<div class="page-header-index-wide">
<slot>
<!-- keep-alive -->
<!-- 条件渲染路由视图支持多页签 -->
<keep-alive v-if="multiTab">
<router-view ref="content" />
</keep-alive>
@ -96,15 +109,19 @@ export default {
this.getPageMeta()
},
methods: {
//
getPageMeta () {
// eslint-disable-next-line
// title
this.pageTitle = (typeof(this.title) === 'string' || !this.title) ? this.title : this.$route.meta.title
const content = this.$refs.content
if (content) {
if (content.pageMeta) {
// pageMeta
Object.assign(this, content.pageMeta)
} else {
//
this.description = content.description
this.linkList = content.linkList
this.extraImage = content.extraImage

@ -1,22 +1,32 @@
<script>
/**
* 定义一个名为 RouteView Vue 组件
* 该组件根据路由的 meta 信息和 store 中的 multiTab 状态决定是否缓存路由视图
*/
export default {
//
name: 'RouteView',
props: {
// true
keepAlive: {
type: Boolean,
default: true
}
},
//
data () {
return {}
},
//
render () {
// meta store getters
const { $route: { meta }, $store: { getters } } = this
const inKeep = (
<keep-alive>
<router-view />
</keep-alive>
)
//
const notKeep = (
<router-view />
)
@ -26,6 +36,7 @@ export default {
if (!getters.multiTab && meta.keepAlive === false) {
return notKeep
}
// keepAlive multiTab keepAlive
return this.keepAlive || getters.multiTab || meta.keepAlive ? inKeep : notKeep
}
}

@ -1,34 +1,39 @@
<template>
<!-- 用户布局模板根据设备类型调整样式 -->
<div id="userLayout" :class="['user-layout-wrapper', device]">
<div class="container">
<div class="top">
<div class="header">
<!-- 顶部标题和logo -->
<a href="/">
<img src="../assets/logo.svg" class="logo" alt="logo">
<span class="title">Online Exam</span>
</a>
</div>
<div class="desc">
<!-- 系统描述 -->
基于SpringBoot+Vue实现的在线考试系统
</div>
</div>
<!-- 路由视图用于显示子路由的内容 -->
<route-view></route-view>
<div class="footer">
<div class="links">
<!-- 底部链接 -->
<a href="https://github.com/19920625lsg/spring-boot-online-exam" target="_blank">代码仓</a>
<a href="https://19920625lsg.github.io" target="_blank">关于我</a>
<a href="mailto:liangshanguang2@gmail.com">联系我</a>
</div>
<div class="copyright">
<!-- 版权信息 -->
Copyright &copy; 2020 Liang Shan Guang
</div>
</div>
</div>
</div>
</template>
// RouteView
<script>
import RouteView from './RouteView'
import { mixinDevice } from '../utils/mixin'
@ -41,9 +46,11 @@ export default {
return {}
},
mounted () {
// bodyuserLayout
document.body.classList.add('userLayout')
},
beforeDestroy () {
// bodyuserLayout
document.body.classList.remove('userLayout')
}
}
@ -52,7 +59,7 @@ export default {
<style lang="less" scoped>
#userLayout.user-layout-wrapper {
height: 100%;
//
&.mobile {
.container {
.main {

@ -6,8 +6,10 @@
:confirmLoading="confirmLoading"
:width="800"
@cancel="cancelHandel">
<!-- 使用a-row和a-col布局左边是裁剪框右边是预览框 -->
<a-row>
<a-col :xs="24" :md="12" :style="{height: '350px'}">
<!-- 使用vue-cropper组件设置图片是否显示信息自动裁剪自动裁剪宽度自动裁剪高度固定框 -->
<vue-cropper
ref="cropper"
:img="options.img"
@ -21,12 +23,14 @@
</vue-cropper>
</a-col>
<a-col :xs="24" :md="12" :style="{height: '350px'}">
<!-- 预览框显示裁剪后的图片 -->
<div class="avatar-upload-preview">
<img :src="previews.url" :style="previews.img"/>
</div>
</a-col>
</a-row>
<!-- 底部按钮 -->
<template slot="footer">
<a-button key="back" @click="cancelHandel"></a-button>
<a-button key="submit" type="primary" :loading="confirmLoading" @click="okHandel"></a-button>
@ -44,18 +48,18 @@ export default {
*/
data () {
return {
visible: false,
id: null,
confirmLoading: false,
visible: false, //
id: null, // id
confirmLoading: false, //
options: {
img: '/avatar2.jpg',
autoCrop: true,
autoCropWidth: 200,
autoCropHeight: 200,
fixedBox: true
img: '/avatar2.jpg', //
autoCrop: true, //
autoCropWidth: 200, //
autoCropHeight: 200, //
fixedBox: true //
},
previews: {}
previews: {} //
}
},
methods: {

@ -1,26 +1,33 @@
<template>
<div class="account-settings-info-view">
<!-- 使用a-row和a-col组件创建一个两列的布局 -->
<a-row :gutter="16">
<!-- 左侧列占据24/24的宽度在lg及以上屏幕上占据16/24的宽度 -->
<a-col :md="24" :lg="16">
<!-- 使用a-form组件创建一个垂直布局的表单 -->
<a-form layout="vertical">
<!-- 表单项用于输入昵称 -->
<a-form-item
label="昵称"
>
<a-input placeholder="给自己起个名字" />
</a-form-item>
<!-- 表单项用于输入Bio -->
<a-form-item
label="Bio"
>
<a-textarea rows="4" placeholder="You are not alone."/>
</a-form-item>
<!-- 表单项用于输入电子邮件 -->
<a-form-item
label="电子邮件"
:required="false"
>
<a-input placeholder="exp@admin.com"/>
</a-form-item>
<!-- 表单项用于输入登录密码 -->
<a-form-item
label="登录密码"
:required="false"
@ -28,6 +35,7 @@
<a-input placeholder="密码"/>
</a-form-item>
<!-- 表单项用于提交和保存按钮 -->
<a-form-item>
<a-button type="primary">提交</a-button>
<a-button style="margin-left: 8px">保存</a-button>
@ -35,18 +43,25 @@
</a-form>
</a-col>
<!-- 右侧列占据24/24的宽度在lg及以上屏幕上占据8/24的宽度最小高度为180px -->
<a-col :md="24" :lg="8" :style="{ minHeight: '180px' }">
<!-- 使用ant-upload-preview组件创建一个头像预览框 -->
<div class="ant-upload-preview" @click="$refs.modal.edit(1)" >
<!-- 使用a-icon组件创建一个上传图标 -->
<a-icon type="cloud-upload-o" class="upload-icon"/>
<!-- 使用mask组件创建一个遮罩层 -->
<div class="mask">
<!-- 使用a-icon组件创建一个加号图标 -->
<a-icon type="plus" />
</div>
<!-- 显示头像图片 -->
<img :src="option.img"/>
</div>
</a-col>
</a-row>
<!-- 使用avatar-modal组件创建一个头像模态框 -->
<avatar-modal ref="modal">
</avatar-modal>
@ -54,6 +69,7 @@
</template>
<script>
// AvatarModal
import AvatarModal from './AvatarModal'
export default {
@ -65,18 +81,26 @@ export default {
// cropper
preview: {},
option: {
//
img: '/avatar2.jpg',
//
info: true,
//
size: 1,
//
outputType: 'jpeg',
//
canScale: false,
//
autoCrop: true,
//
autoCropWidth: 180,
autoCropHeight: 180,
//
fixedBox: true,
//
fixed: true,
//
fixedNumber: [1, 1]
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save