Compare commits

..

26 Commits

@ -1,6 +0,0 @@
<?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,6 +2,5 @@
<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>

@ -1,20 +0,0 @@
<?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>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="MySQL" />
</component>
</project>

@ -1,124 +0,0 @@
<?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>

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<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.参与贡献

@ -4,24 +4,35 @@
* @date : 2019-05-17 00:11
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
package lsgwr.exam.config;// 指定当前类所在的包路径
// 导入所需的类
import jdk.internal.instrumentation.Logger;
import lombok.extern.slf4j.Slf4j;// Lombok库提供的注解用于自动为类添加SLF4J日志记录器
import org.springframework.context.annotation.Bean;// Spring框架提供的注解用于标记配置类
import org.springframework.context.annotation.Configuration;// Spring框架提供的注解用于标记配置类。
import org.springframework.web.servlet.config.annotation.CorsRegistry;// Spring MVC框架提供的接口用于自定义Web MVC配置
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;// Spring MVC框架提供的接口用于自定义Web MVC配置。
// 使用@Configuration注解标记此类为Spring的配置类。
// 使用@Slf4j注解自动为类添加一个名为log的SLF4J日志记录器。
@Configuration
@Slf4j
public class CORSConf {
// 使用@Bean注解声明一个方法该方法将返回一个对象该对象将被注册为Spring应用上下文中的bean。
// 此处声明的方法返回一个WebMvcConfigurer类型的对象用于配置跨域资源共享CORS
@Bean
public WebMvcConfigurer corsConfigurer() {
// 返回一个新的WebMvcConfigurer匿名类实例。
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
Logger log = null;
// 尝试记录日志信息但由于log被初始化为null这行代码会抛出NullPointerException。
log.info("初始化 CORSConfiguration 配置");
// 使用CorsRegistry配置CORS规则
// 允许所有路径("/**")的跨域请求。
// 允许所有HTTP头"*")。
// 允许所有HTTP方法"*")。
// 允许所有来源的跨域请求("*")。
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("*")

@ -12,21 +12,12 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
*
*/
@Configuration
public class IntercepterConfig implements WebMvcConfigurer {
/**
* LoginInterceptor
*/
@Autowired
private LoginInterceptor loginInterceptor;
/**
*
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截user下的api

@ -7,22 +7,12 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
/**
* Servlet
*/
@Configuration
public class ServletConfig {
/**
* WebServerFactoryCustomizer beanWeb
* @return WebServerFactoryCustomizer<ConfigurableWebServerFactory>
*/
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
// 返回一个lambda表达式用于自定义Web服务器的工厂
return factory -> {
// 创建一个ErrorPage对象用于处理404错误
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/");
// 将ErrorPage对象添加到Web服务器的工厂中
factory.addErrorPages(error404Page);
};
}

@ -23,18 +23,12 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
/**
* Swagger2
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
/**
* Docket bean
* @return Docket
*/
@Bean
public Docket api() {
ParameterBuilder ticketPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
ticketPar.name("Access-Token").description("Rest接口权限认证token,无需鉴权可为空")
@ -54,10 +48,6 @@ public class Swagger2Config {
.globalOperationParameters(pars);
}
/**
* ApiInfo
* @return ApiInfo
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("online exam by springboot")
@ -67,4 +57,4 @@ public class Swagger2Config {
.contact(new Contact("liangshanguang", "https://github.com/lsgwr/spring-boot-online-exam", "liangshanguang2@gmail.com"))
.build();
}
}
}

@ -1,201 +1,196 @@
/**
* @Description : REST,HTTP,,
*/
/***********************************************************
* @Description :
* @author : 广(Laing Shan Guang)
* @date : 2019-05-28 08:04
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.controller;
// 导入考试相关的实体类,用于在控制器中处理和传递对应的业务数据,比如考试信息、考试记录信息等
import lsgwr.exam.entity.Exam;
import lsgwr.exam.entity.ExamRecord;
// 导入考试业务逻辑层的服务类,通过它可以调用具体的业务方法来实现诸如获取考试数据、更新考试等操作
import lsgwr.exam.service.ExamService;
// 导入Swagger相关注解用于生成API文档Api注解用于给一组API接口定义一个标签方便文档中分类展示
import io.swagger.annotations.Api;
// ApiOperation注解用于描述单个API接口的具体功能在API文档中展示接口的详细说明
import io.swagger.annotations.ApiOperation;
// 导入所有视图对象VO视图对象通常是用于在不同层之间传递数据的载体会根据前端展示需求对实体数据进行适当封装和整理
import lsgwr.exam.vo.*;
// 导入Spring框架提供的Bean属性拷贝工具类方便在不同的Java对象之间进行属性值的复制操作
import org.springframework.beans.BeanUtils;
// 导入Spring框架的依赖注入注解用于自动装配相关的Bean实例到类的成员变量中
import org.springframework.beans.factory.annotation.Autowired;
// 导入Spring Web中用于处理HTTP请求的各种注解例如定义请求方法、请求路径、路径变量、请求体等相关的注解
import org.springframework.web.bind.annotation.*;
// 导入Java中处理HTTP请求的类在这个控制器中可以通过它获取请求相关的信息比如请求头中的用户标识等信息
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
// @RestController注解表明这个类是一个Spring RESTful风格的Web控制器它结合了@Controller和@ResponseBody注解的功能意味着该类中处理请求的方法默认会将返回值直接转换为JSON等格式响应给客户端
@RestController
// @Api注解为Swagger文档定义标签这里将这个控制器下的所有API接口归为"Exam APIs"这一组便于在Swagger生成的API文档里进行分类查看和管理
@Api(tags = "Exam APIs")
//将此控制器中的所有端点映射到/api/exam URL路径
// @RequestMapping注解用于定义该控制器中所有接口的基础请求路径即这个控制器下所有接口的请求URL都将以 "/api/exam" 开头
@RequestMapping("/api/exam")
public class ExamController {
@Autowired //注入一个ExamService实例用于处理与考试和问题相关的业务逻辑
private ExamService examService;
// 使用@Autowired注解进行依赖注入让Spring容器自动将ExamService的实例注入到这个成员变量中方便后续在控制器的方法里调用其业务方法
@Autowired
private ExamService examService;// 自动注入 ExamService 服务
/**
* @Description: 使examService.getQuestionAll(),ResultVO,
* &#064;ApiOperationAPI,SwaggerUI
*
* HTTPGET访 "/api/exam/question/all"
*/
@GetMapping("/question/all")
@ApiOperation("获取所有问题的列表")
// 获取全部问题列表。
ResultVO<List<QuestionVo>> getQuestionAll() {
// 定义返回值。
ResultVO<List<QuestionVo>> getQuestionAll() {
// 定义一个ResultVO类型的变量用于存放最终要返回给客户端的结果对象其泛型参数指定为List<QuestionVo>,表示包含问题列表数据的结果封装
ResultVO<List<QuestionVo>> resultVO;
try {
// 调用examService获取全部问题列表。
// 调用ExamService的getQuestionAll方法该方法应该在服务层实现从数据库或者其他数据源获取所有问题数据的逻辑返回一个QuestionVo类型的列表代表所有问题信息
List<QuestionVo> questionAll = examService.getQuestionAll();
// 返回成功结果。
// 如果获取数据成功创建一个表示成功的ResultVO对象其中状态码设置为0表示操作成功消息设置为"获取全部问题列表成功"并将获取到的问题列表数据放入ResultVO对象中
resultVO = new ResultVO<>(0, "获取全部问题列表成功", questionAll);
} catch (Exception e) {
// 打印异常信息。
// 如果在获取数据过程中出现异常,打印异常堆栈信息,方便后续排查问题,通常在开发和调试阶段查看具体出错原因
e.printStackTrace();
// 返回失败结果。
resultVO = new ResultVO<>(-1, "获取全部问题列表失败", null);
// 创建一个表示失败的ResultVO对象状态码设置为 -1表示操作失败消息设置为"获取全部问题列表失败"数据部分设置为null因为没有成功获取到问题列表数据
resultVO = new ResultVO<>(-1, "获取全部问题列表失败", null);// 返回失败结果
}
// 返回结果。
return resultVO;
}
/**
*
* HTTPPOST "/api/exam/question/update"QuestionVo
*/
@PostMapping("/question/update")
@ApiOperation("更新问题")
ResultVO<QuestionVo> questionUpdate(@RequestBody QuestionVo questionVo) {
// 完成问题的更新。
System.out.println(questionVo);
// 打印接收到的要更新的问题对象信息,在开发调试阶段可以通过查看控制台输出来确认前端传递过来的数据是否符合预期,方便排查问题
System.out.println(questionVo);// 打印接收到的问题信息以供调试
try {
// 调用examService的updateQuestion方法更新问题。
// 调用ExamService的updateQuestion方法将接收到的要更新的问题对象questionVo传递进去由服务层实现具体的更新逻辑比如更新数据库中对应的问题记录等操作返回更新后的问题对象
QuestionVo questionVoResult = examService.updateQuestion(questionVo);
// 返回更新成功的结果。
// 如果更新成功创建一个表示成功的ResultVO对象状态码设置为0表示更新操作成功消息设置为"更新问题成功"并将更新后的问题对象放入ResultVO对象中返回给客户端
return new ResultVO<>(0, "更新问题成功", questionVoResult);
} catch (Exception e) {
// 打印异常信息。
// 如果在更新过程中出现异常,打印异常堆栈信息,便于查找问题所在
e.printStackTrace();
// 返回更新失败的结果。
// 创建一个表示更新失败的ResultVO对象状态码设置为 -1表示更新操作失败消息设置为"更新问题失败"数据部分设置为null因为没有成功完成更新操作
return new ResultVO<>(-1, "更新问题失败", null);
}
}
/**
*
* @Description: QuestionCreateSimplifyVoQuestionCreateVoID使examService.questionCreate()
* @param questionCreateSimplifyVo QuestionCreateSimplifyVo
* @param request HttpServletRequestID
* @return ResultVO<String>
*
* HTTPPOST "/api/exam/question/create"QuestionCreateSimplifyVoHttpServletRequest
*/
@PostMapping("/question/create")
@ApiOperation("创建问题")
ResultVO<String> questionCreate(@RequestBody QuestionCreateSimplifyVo questionCreateSimplifyVo, HttpServletRequest request) {
// 创建一个QuestionCreateVo对象
// 创建一个QuestionCreateVo对象用于组装完整的创建问题所需的数据可能QuestionCreateSimplifyVo只是包含了部分必要信息需要进一步完善才能用于创建操作
QuestionCreateVo questionCreateVo = new QuestionCreateVo();
// 把能拷贝过来的属性都拷贝过来。
BeanUtils.copyProperties(questionCreateSimplifyVo, questionCreateVo);
// 设置创建者信息。
String userId = (String) request.getAttribute("user_id");
// 使用Spring的BeanUtils工具类将questionCreateSimplifyVo对象中的属性值拷贝到questionCreateVo对象中这样可以方便地复用已有的部分数据避免重复设置属性
BeanUtils.copyProperties(questionCreateSimplifyVo, questionCreateVo);// 拷贝属性到新的对象
// 从HttpServletRequest对象中获取用户ID信息通常这个用户ID是在请求处理的前置环节比如拦截器中设置到请求属性中的用于标识创建这个问题的用户是谁
String userId = (String) request.getAttribute("user_id");// 从请求中获取用户ID
// 将获取到的用户ID设置到questionCreateVo对象中作为问题创建者的标识以便在后续保存问题数据到数据库等操作中记录创建者信息
questionCreateVo.setQuestionCreatorId(userId);
System.out.println(questionCreateVo);
try {
// 调用examService的questionCreate方法创建问题。
// 调用ExamService的questionCreate方法将组装好的questionCreateVo对象传递进去由服务层实现将新问题数据保存到数据库等具体的创建逻辑操作
examService.questionCreate(questionCreateVo);
// 返回问题创建成功的ResultVO。
return new ResultVO<>(0, "问题创建成功", null);
// 如果创建成功创建一个表示成功的ResultVO对象状态码设置为0表示创建操作成功消息设置为"问题创建成功"这里数据部分设置为null具体根据业务需求而定可能创建成功不需要返回具体的数据内容
return new ResultVO<>(0, "问题创建成功", null);// 返回成功结果
} catch (Exception e) {
// 打印异常信息。
// 如果在创建过程中出现异常,打印异常堆栈信息,方便排查问题原因
e.printStackTrace();
// 返回创建问题失败的ResultVO。
// 创建一个表示创建失败的ResultVO对象状态码设置为 -1表示创建操作失败消息设置为"创建问题失败"数据部分设置为null
return new ResultVO<>(-1, "创建问题失败", null);
}
}
/**
* @Description: ,使examService.getSelections()ResultVO
* @return ResultVO<QuestionAllVo>
*
* HTTPGET访 "/api/exam/question/selection"
*/
@GetMapping("/question/selection")
@ApiOperation("获取问题分类的相关选项")
// 获取问题分类选项。
ResultVO<QuestionSelectionVo> getSelections() {
// 调用examService的getSelections方法获取问题分类选项。
ResultVO<QuestionSelectionVo> getSelections() {
// 调用ExamService的getSelections方法该方法在服务层应该实现从数据库或者其他数据源获取问题分类相关选项数据的逻辑返回一个QuestionSelectionVo对象代表分类选项信息
QuestionSelectionVo questionSelectionVo = examService.getSelections();
// 如果获取成功。
if (questionSelectionVo != null) {
// 返回成功的结果。
// 如果获取到的问题分类选项对象不为空说明获取操作成功创建一个表示成功的ResultVO对象状态码设置为0表示获取成功消息设置为"获取问题分类选项成功"并将获取到的分类选项对象放入ResultVO对象中返回给客户端
return new ResultVO<>(0, "获取问题分类选项成功", questionSelectionVo);
} else {
// 否则返回失败的结果。
return new ResultVO<>(-1, "获取问题分类选项失败", null);
// 如果获取到的问题分类选项对象为空说明获取操作失败创建一个表示失败的ResultVO对象状态码设置为 -1表示获取失败消息设置为"获取问题分类选项失败"数据部分设置为null
return new ResultVO<>(-1, "获取问题分类选项失败", null);// 分类选项为空时返回失败信息
}
}
/**
* @Description: 使examService.getQuestionDetail(id),ResultVO,
* @param id id
* @return ResultVO<QuestionDetailVo>
* ID
* HTTPGETID "/api/exam/question/detail/{id}"{id}ID
*/
@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获取问题的详细信息。
// 调用ExamService的getQuestionDetail方法将接收到的问题ID传递进去由服务层实现从数据库或者其他数据源根据ID查询对应问题详细信息的逻辑返回一个QuestionDetailVo对象代表问题详细信息
QuestionDetailVo questionDetailVo = examService.getQuestionDetail(id);
// 如果获取成功则返回ResultVO对象状态码为0提示信息为"获取问题详情成功"数据为questionDetailVo。
// 如果获取详细信息成功创建一个表示成功的ResultVO对象状态码设置为0表示获取操作成功消息设置为"获取问题详情成功"并将获取到的问题详细信息对象放入ResultVO对象中
resultVO = new ResultVO<>(0, "获取问题详情成功", questionDetailVo);
} catch (Exception e) {
// 如果获取失败则打印异常信息并返回ResultVO对象状态码为-1提示信息为"获取问题详情失败"数据为null。
// 如果在获取详细信息过程中出现异常,打印异常堆栈信息,便于查找问题原因
e.printStackTrace();
// 创建一个表示获取失败的ResultVO对象状态码设置为 -1表示获取操作失败消息设置为"获取问题详情失败"数据部分设置为null
resultVO = new ResultVO<>(-1, "获取问题详情失败", null);
}
// 返回ResultVO对象。
return resultVO;
return resultVO;// 返回结果对象
}
/**
* @Description: 使examService.getExamAll(),ResultVO,
* @return ResultVO<QuestionDetailVo>
*
* HTTPGET访 "/api/exam/all"
*/
@GetMapping("/all")
@ApiOperation("获取全部考试的列表")
ResultVO<List<ExamVo>> getExamAll() {
// 需要拼接前端需要的考试列表对象。
// 定义一个ResultVO类型的变量用于存放最终要返回给客户端的结果对象其泛型参数指定为List<ExamVo>,表示包含考试列表数据的结果封装
ResultVO<List<ExamVo>> resultVO;
try {
// 调用examService的getExamAll方法获取全部考试的列表。
// 调用ExamService的getExamAll方法该方法应该在服务层实现从数据库或者其他数据源获取所有考试数据的逻辑返回一个ExamVo类型的列表代表所有考试信息
List<ExamVo> examVos = examService.getExamAll();
// 将获取到的考试列表封装到ResultVO对象中并返回。
// 如果获取数据成功创建一个表示成功的ResultVO对象状态码设置为0表示操作成功消息设置为"获取全部考试的列表成功"并将获取到的考试列表数据放入ResultVO对象中
resultVO = new ResultVO<>(0, "获取全部考试的列表成功", examVos);
} catch (Exception e) {
// 捕获异常,并打印异常信息。
// 如果在获取数据过程中出现异常,打印异常堆栈信息,方便后续排查问题
e.printStackTrace();
// 将异常信息封装到ResultVO对象中并返回。
// 创建一个表示失败的ResultVO对象状态码设置为 -1表示操作失败消息设置为"获取全部考试的列表失败"数据部分设置为null因为没有成功获取到考试列表数据
resultVO = new ResultVO<>(-1, "获取全部考试的列表失败", null);
}
return resultVO;
return resultVO;// 返回结果对象
}
/**
* @Description: 使examService.getExamQuestionTypeList(),ResultVO,
* @return ResultVO<ExamQuestionTypeVo>
*
* HTTPGET访 "/api/exam/question/type/list"
*/
@GetMapping("/question/type/list")
@ApiOperation("获取问题列表,按照单选、多选和判断题分类返回")
ResultVO<ExamQuestionTypeVo> getExamQuestionTypeList() {
// 获取问题的分类列表。
// 定义一个ResultVO类型的变量用于存放最终要返回给客户端的结果对象其泛型参数指定为ExamQuestionTypeVo用于封装按照特定分类方式整理后的问题列表相关信息
ResultVO<ExamQuestionTypeVo> resultVO;
try {
// 调用examService的getExamQuestionType方法获取问题分类列表。
// 调用ExamService的getExamQuestionType方法该方法在服务层应该实现从数据库或者其他数据源获取并整理按照单选、多选和判断题分类后的问题列表数据的逻辑返回一个ExamQuestionTypeVo对象
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>
*
* HTTPPOST "/api/exam/create"ExamCreateVoHttpServletRequestID
*/
@PostMapping("/create")
@ApiOperation("创建考试")
@ -217,47 +212,53 @@ ResultVO<QuestionSelectionVo> getSelections() {
return resultVO;
}
/**
* @Description: 使examService.updateExam(),ResultVO,
* @param examVo
* @param request id
* @return
*
* HTTP
* ResultVO
*
* @param examVo
* @param request HTTPID
* @return ResultVO<Exam>ResultVOExam
*/
@PostMapping("/update")
@ApiOperation("更新考试")
ResultVO<Exam> updateExam(@RequestBody ExamVo examVo, HttpServletRequest request) {
// 从前端传参数过来,在这里完成考试的入库
// 从前端传参数过来,在这里完成考试的入库此处先定义一个用于存放最终要返回给客户端的ResultVO<Exam>类型的结果对象,后续根据操作成功与否进行赋值
ResultVO<Exam> resultVO;
// 获取当前用户id
// 从HttpServletRequest对象中获取用户ID属性值该用户ID用于标识当前执行更新考试操作的用户是后续业务逻辑判断如权限校验、操作记录等的重要依据
String userId = (String) request.getAttribute("user_id");
try {
// 调用service层更新考试
// 调用ExamService的update方法传入包含更新数据的examVo视图对象以及获取到的用户ID由服务层去实现具体更新数据库中对应考试记录的逻辑比如根据传入数据修改相应字段等操作并返回更新后的Exam对象包含最新的考试信息
Exam exam = examService.update(examVo, userId);
// 返回更新成功的resultVO
// 如果更新操作成功创建一个表示成功的ResultVO对象状态码设置为0表示更新考试成功消息设置为"更新考试成功"并将更新后的考试对象放入ResultVO对象中用于返回给客户端展示最新的考试信息
resultVO = new ResultVO<>(0, "更新考试成功", exam);
} catch (Exception e) {
// 打印异常信息。
// 如果在更新考试信息的过程中出现异常,打印异常堆栈信息,方便开发人员后续排查问题,定位是哪里出现的错误导致更新失败
e.printStackTrace();
// 返回更新失败的resultVO
// 创建一个表示更新失败的ResultVO对象状态码设置为 -1表示更新考试失败消息可根据业务实际情况默认为"更新考试失败"之类的提示信息数据部分设置为null因为没有成功获取到更新后的有效考试信息
resultVO = new ResultVO<>(-1, "更新考试失败", null);
}
return resultVO;
}
/**
* @Description:
* @return ResultVO<List<ExamCardVo>>
*
* HTTPGETResultVO<List<ExamCardVo>>
* ResultVO
*
* @returnResultVOResultVOList<ExamCardVo>ExamCardVo
*/
@GetMapping("/card/list")
@ApiOperation("获取考试列表,适配前端卡片列表")
ResultVO<List<ExamCardVo>> getExamCardList() {
// 获取考试列表卡片
// 获取考试列表卡片先定义一个用于存放最终要返回给客户端的ResultVO<List<ExamCardVo>>类型的结果对象,后续根据获取数据的情况进行赋值操作
ResultVO<List<ExamCardVo>> resultVO;
try {
// 调用examService的getExamCardList方法获取考试列表卡片
// 调用ExamService的getExamCardList方法由服务层实现从数据库或者其他数据源获取并整理适合前端以卡片形式展示的考试列表数据的逻辑返回一个List<ExamCardVo>类型的列表,代表所有考试的卡片信息
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);
@ -265,9 +266,12 @@ ResultVO<QuestionSelectionVo> getSelections() {
return resultVO;
}
/**
* @Description: id
* @param id id
* @return ResultVO<ExamDetailVo>
* ID
* HTTPGETResultVO<ExamDetailVo>
* ResultVO
*
* @param id
* @return ResultVOResultVOExamDetailVo
*/
@GetMapping("/detail/{id}")
@ApiOperation("根据考试的id获取考试详情")
@ -286,11 +290,14 @@ ResultVO<QuestionSelectionVo> getSelections() {
return resultVO;
}
/**
* @Description: 使examService.finishExam,ResultVO,
* @param examId id
* @param answersMap
* @param request id
* @return
*
* HTTP
* ResultVO<ExamRecord>ResultVO
*
* @param examId
* @param answersMap HashMap<String, List<String>>
* @param request IDHTTP便
* @return ResultVO<ExamRecord>ExamRecord
*/
@PostMapping("/finish/{examId}")
@ApiOperation("根据用户提交的答案对指定id的考试判分")
@ -313,9 +320,12 @@ ResultVO<QuestionSelectionVo> getSelections() {
return resultVO;
}
/**
* @Description: 使examService.getExamRecordList,ResultVO,
* @param request id
* @return ResultVO<List<ExamRecordVo>> ResultVOResultVO
*
* HTTPGETResultVO<List<ExamRecordVo>>
* ResultVO
*
* @param request ID便
* @return ResultVOResultVOList<ExamRecordVo>ExamRecordVo
*/
@GetMapping("/record/list")
@ApiOperation("获取当前用户的考试记录")
@ -338,9 +348,12 @@ ResultVO<QuestionSelectionVo> getSelections() {
return resultVO;//返回封装好的 数据结构
}
/**
* @Description: 使examService.getExamRecordDetail,ResultVO,id
* @param recordId id
* @return ResultVO<RecordDetailVo> ResultVOResultVO
* recordId
* HTTPGETResultVO<RecordDetailVo>
* ResultVO
*
* @param recordId
* @return ResultVOResultVORecordDetailVo
*/
@GetMapping("/record/detail/{recordId}")
@ApiOperation("根据考试记录id获取考试记录详情")

@ -40,76 +40,36 @@ public class UploadDownloadController {
// return FileTransUtil.uploadFile(uploadfile, "/root/" + File.separator + uploadfile.getOriginalFilename());
// }
/**
*
*
* @param dir
* @param uploadfile
* @return String
*/
@ApiOperation("单文件上传,支持同时传入参数")
@PostMapping("/api/upload/singleAndparas")
public String uploadFileSingle(@RequestParam("dir") String dir, @RequestParam("file") MultipartFile uploadfile) {
return FileTransUtil.uploadFile(uploadfile, dir);
}
/**
* Model
*
* @param model UploadModel2
* @return String
*/
@ApiOperation("单文件上传,支持同时传入参数,Model")
@PostMapping("/upload/single/model")
public String singleUploadFileModel(@ModelAttribute("model") UploadModel2 model) {
return FileTransUtil.uploadFile(model.getFile(), model.getDir());
}
/**
*
*
* @param dir
* @param uploadfiles
* @return String
*/
@ApiOperation("多文件上传,支持同时传入参数")
@PostMapping("upload/multiAndparas")
public String uploadFileMulti(@RequestParam("dir") String dir, @RequestParam("files") MultipartFile[] uploadfiles) {
return FileTransUtil.uploadFiles(uploadfiles, dir);
}
/**
*
*
* @param model UploadModel
* @return String
*/
@ApiOperation("多文件上传,支持同时传入参数")
@PostMapping(value = "/upload/multi/model")
public String multiUploadFileModel(@ModelAttribute(("model")) UploadModel model) {
return FileTransUtil.uploadFiles(model.getFiles(), model.getDir());
}
/**
* Get
*
* @param filePath
* @return ResponseEntity<InputStreamResource>
* @throws IOException
*/
@ApiOperation("Get下载文件")
@GetMapping(value = "/download/get")
public ResponseEntity<InputStreamResource> downloadFileGet(@RequestParam String filePath) throws IOException {
return FileTransUtil.downloadFile(filePath);
}
/**
* Post
*
* @param downloadQo DownloadQo
* @return ResponseEntity<InputStreamResource>
* @throws IOException
*/
@ApiOperation("Post下载文件")
@PostMapping(value = "/download/post")
public ResponseEntity<InputStreamResource> downloadFilePost(@RequestBody DownloadQo downloadQo) throws IOException {

@ -29,12 +29,6 @@ public class UserController {
@Autowired
private UserService userService;
/**
*
*
* @param registerDTO RegisterDTO
* @return ResultVO<User>
*/
@PostMapping("/register")
@ApiOperation("注册")
ResultVO<User> register(@RequestBody RegisterDTO registerDTO) {
@ -50,12 +44,6 @@ public class UserController {
return resultVO;
}
/**
* ,token
*
* @param loginQo LoginQo
* @return ResultVO<String>
*/
@PostMapping("/login")
@ApiOperation("根据用户名或邮箱登录,登录成功返回token")
ResultVO<String> login(@RequestBody LoginQo loginQo) { // 这里不用手机号是因为手机号和用户名难以进行格式区分,而用户名和
@ -72,50 +60,23 @@ public class UserController {
return resultVO;
}
/**
*
*
* @param request HttpServletRequest
* @return ResultVO<UserVo>
*/
@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);
}
/**
*
*
* @param request HttpServletRequest
* @return ResultVO<UserInfoVo>
*/
@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);
}
/**
*
*
* @param request HttpServletRequest
* @return String
*/
@GetMapping("/test")
@ApiOperation("测试接口")
String test(HttpServletRequest request) {

@ -8,26 +8,11 @@ package lsgwr.exam.dto;
import lombok.Data;
/**
*
*/
@Data
public class RegisterDTO {
/**
*
*/
private String email;
/**
*
*/
private String password;
/**
*
*/
private String password2;
/**
*
*/
private String mobile;
/**
*

@ -11,29 +11,17 @@ 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;
}

@ -23,85 +23,37 @@ import java.util.Date;
@Entity
@Data
@DynamicUpdate
/**
*
*/
public class Exam {
// 使用JPA的@Id注解声明该字段为主键
/**
* ID
*/
@Id
private String examId;
/**
*
*/
private String examName;
/**
*
*/
private String examAvatar;
/**
*
*/
private String examDescription;
/**
* IDID
*/
private String examQuestionIds;
/**
* ID
*/
private String examQuestionIdsRadio;
/**
* ID
*/
private String examQuestionIdsCheck;
/**
* ID
*/
private String examQuestionIdsJudge;
/**
*
*/
private Integer examScore;
/**
*
*/
private Integer examScoreRadio;
/**
*
*/
private Integer examScoreCheck;
/**
*
*/
private Integer examScoreJudge;
/**
* ID
*/
private String examCreatorId;
/**
*
*/
private Integer examTimeLimit;
/**
* 使Jackson@JsonFormat
*/
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
* 使Jackson@JsonFormat便
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* Java
* 使Hibernate@DynamicUpdate

@ -16,22 +16,13 @@ import javax.persistence.Id;// 引入JPA中的@Id注解标识实体类的主
@Data
// 使用@Entity注解标识这是一个JPA实体类
@Entity
/**
*
*/
public class ExamRecordLevel {
/**
* ID@Id@GeneratedValue
*/
public class ExamRecordLevel {
// 考试记录等级的ID是主键通过@Id注解标识并通过@GeneratedValue注解指定主键生成策略
@Id
@GeneratedValue
private Integer examRecordLevelId;
/**
*
*/
// 考试记录等级的名称,用于标识等级,如“优秀”、“良好”等
private String examRecordLevelName;
/**
*
*/
// 考试记录等级的描述,用于对等级进行更详细的说明
private String examRecordLevelDescription;
}

@ -12,28 +12,16 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
*
*/
@Data
@Entity
public class Page {
/**
* ID@Id@GeneratedValue
*/
@Id
@GeneratedValue
private Integer pageId;
/**
*
*/
private String pageName;
/**
*
*/
private String pageDescription;
/**
* ID
*/
private String actionIds;
}

@ -14,54 +14,21 @@ 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,27 +13,19 @@ 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,27 +14,17 @@ 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,23 +11,12 @@ 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,28 +14,17 @@ 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,31 +13,14 @@ 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,49 +14,20 @@ 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;
/**
* @Description:
*
* @author liangshanguang
*/
@Getter
public enum LoginTypeEnum {
/**
* @Description:
* 12
* 12
*/
USERNAME(1, "用户名"),
EMAIL(2, "邮箱");
// 构造函数用于初始化code和description
LoginTypeEnum(Integer type, String name) {
this.type = type;
this.name = name;

@ -8,16 +8,17 @@ 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,9 +2,6 @@ package lsgwr.exam.enums;
import lombok.Getter;
/**
* @Description :
*/
@Getter
public enum ResultEnum {
// 下面是本项目用到的所有错误码
@ -20,11 +17,6 @@ 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,9 +8,6 @@ package lsgwr.exam.enums;
import lombok.Getter;
/**
* @Description:
*/
@Getter
public enum RoleEnum {
@ -21,11 +18,7 @@ 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;

@ -6,37 +6,29 @@
***********************************************************/
package lsgwr.exam.exception;
import lsgwr.exam.enums.ResultEnum;
import lombok.Getter;
/**
*
*/
import lsgwr.exam.enums.ResultEnum;// 导入lsgwr.exam.enums包下的ResultEnum枚举类
import lombok.Getter;// 导入lombok库中的@Getter注解用于自动生成getter方法
// 使用@Getter注解自动为类中的字段生成getter方法
@Getter
// 定义一个名为ExamException的自定义异常类它继承自RuntimeException表示这是一个运行时异常
public class ExamException extends RuntimeException {
/**
*
*/
// 定义一个整型字段code用于存储异常码
private Integer code;
/**
*
*
* @param resultEnum ResultEnum
*/
// 定义一个构造函数接收一个ResultEnum枚举类型的参数
// 这个构造函数通过调用父类RuntimeException的构造函数来设置异常信息
// 并通过枚举类型的参数来设置异常码
public ExamException(ResultEnum resultEnum) {
// 调用父类构造函数传入ResultEnum枚举中定义的异常信息
super(resultEnum.getMessage());
// 设置当前异常的异常码
this.code = resultEnum.getCode();
}
/**
*
*
* @param code
* @param message
*/
// 定义一个构造函数接收一个整型code和一个字符串message作为参数
// 这个构造函数允许直接传入异常码和异常信息来创建异常对象
public ExamException( Integer code, String message) {
// 调用父类构造函数,传入异常信息
super(message);
// 设置当前异常的异常码
this.code = code;
}
}
}

@ -23,10 +23,6 @@ import java.io.PrintWriter;
*
* @author liangshanguang
*/
/**
* Token
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@ -88,9 +84,9 @@ public class LoginInterceptor implements HandlerInterceptor {
/**
*
*
* @param response
* @param obj
* @throws Exception
* @param response
* @param obj
* @throws Exception
*/
public static void sendJsonMessage(HttpServletResponse response, Object obj) throws Exception {
Gson g = new Gson();

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

@ -10,9 +10,6 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor

@ -11,10 +11,7 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
/**
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor

@ -12,9 +12,6 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
/**
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor

@ -6,11 +6,13 @@
***********************************************************/
package lsgwr.exam.repository;
import lsgwr.exam.entity.ExamRecordLevel;
import org.springframework.data.jpa.repository.JpaRepository;
import lsgwr.exam.entity.ExamRecordLevel;// 导入lsgwr.exam.entity包下的ExamRecordLevel实体类
import org.springframework.data.jpa.repository.JpaRepository;// 导入Spring Data JPA提供的JpaRepository接口
/**
* ExamRecordLevelRepositoryJpaRepositoryExamRecordLevel
* ExamRecordLevelRepositoryExamRecordLevel
* JpaRepositorySpring Data JPA访
* <ExamRecordLevel, Integer>
*/
public interface ExamRecordLevelRepository extends JpaRepository<ExamRecordLevel, Integer> {
}

@ -1,19 +1,26 @@
/***********************************************************
* @Description :
* @author : 广(Laing Shan Guang)
* @date : 2019-05-14 08:23
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.repository;
import lsgwr.exam.entity.ExamRecord;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import lsgwr.exam.entity.ExamRecord;// 导入lsgwr.exam.entity包下的ExamRecord实体类
import org.springframework.data.jpa.repository.JpaRepository;// 导入Spring Data JPA提供的JpaRepository接口
import java.util.List;// 导入Java的List集合类
/**
*
* ExamRecordRepositoryExamRecord
* JpaRepositorySpring Data JPA访
* <ExamRecord, String>String
*/
public interface ExamRecordRepository extends JpaRepository<ExamRecord, String> {
/**
*
* ID
*
* @param userId id
* @return
* @param userId ID
* @return List
*/
List<ExamRecord> findByExamJoinerIdOrderByExamJoinDateDesc(String userId);
}

@ -6,17 +6,24 @@
***********************************************************/
package lsgwr.exam.repository;
import lsgwr.exam.entity.Exam;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import lsgwr.exam.entity.Exam;// 导入lsgwr.exam.entity包下的Exam实体类
import org.springframework.data.jpa.repository.JpaRepository;// 导入Spring Data JPA提供的JpaRepository接口
import org.springframework.data.jpa.repository.Query;// 导入Spring Data JPA提供的@Query注解用于定义JPQL查询语句
import java.util.List;
import java.util.List;// 导入Java的List集合类
/**
* ExamRepositoryJpaRepositoryExam
* ExamRepositoryJpaRepositoryExam
* JpaRepositoryExamRepositorySpring Data JPA访
* <Exam, String>String
*/
public interface ExamRepository extends JpaRepository<Exam, String> {
// 使用JPQL查询语句查询Exam实体类按照updateTime降序排列
/**
* 使JPQLExamupdateTime
* JpaRepositoryfindAll()
*
* @return ExamListupdateTime
*/
@Query("select e from Exam e order by e.updateTime desc")
List<Exam> findAll();
}

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

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

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

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

@ -11,22 +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> {
/**
* ID
*
* @param id ID
* @return
*/
List<Question> findByQuestionTypeId(Integer id);
/**
*
*
* @return
*/
@Query("select q from Question q order by q.updateTime desc")
List<Question> findAll();
}

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

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

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

@ -14,109 +14,32 @@ import java.util.HashMap;// 导入了HashMap用于存储键值对
import java.util.List;// 导入了List用于存储一系列对象
// 定义了一个名为ExamService的接口
public interface ExamService {
/**
*
*/
// 获取所有的问题列表
List<QuestionVo> getQuestionAll();
/**
*
*
* @param questionVo
*/
// 根据传入的问题实体更新问题和选项
QuestionVo updateQuestion(QuestionVo questionVo);
/**
*
*
* @param questionCreateVo
*/
// 创建问题
void questionCreate(QuestionCreateVo questionCreateVo);
/**
*
*
* @return
*/
// 获取问题的选项、分类和难度的下拉列表
QuestionSelectionVo getSelections();
/**
*
*
* @param id id
* @return VO
*/
// 根据问题ID获取问题详情
QuestionDetailVo getQuestionDetail(String id);
/**
*
*/
// 获取全部考试的列表
List<ExamVo> getExamAll();
/**
* 便
*
* @return
*/
// 获取所有问题的下拉列表,用于前端创建考试时筛选
ExamQuestionTypeVo getExamQuestionType();
/**
*
*
* @param examCreateVo
* @param userId id
* @return
*/
// 根据前端组装的参数创建考试
Exam create(ExamCreateVo examCreateVo, String userId);
/**
*
*
* @return
*/
// 获取考试卡片列表
List<ExamCardVo> getExamCardList();
/**
* id
*
* @param id exam
* @return VO
*/
// 根据考试ID获取考试详情
ExamDetailVo getExamDetail(String id);
/**
*
*
* @param userId
* @param examId
* @param answersMap
* @return
*/
// 根据用户提交的作答信息进行判分
ExamRecord judge(String userId, String examId, HashMap<String, List<String>> answersMap);
/**
* id
*
* @param userId id
* @return
*/
// 根据用户ID获取此用户的所有考试信息
List<ExamRecordVo> getExamRecordList(String userId);
/**
*
*
* @param recordId id
* @return
*/
// 获取指定某次考试记录的详情
RecordDetailVo getRecordDetail(String recordId);
/**
*
*
* @param examVo
* @param userId
* @return
*/
// 更新考试
Exam update(ExamVo examVo, String userId);
}

@ -47,12 +47,7 @@ public class UserServiceImpl implements UserService {
@Autowired
ActionRepository actionRepository;
/**
*
*
* @param registerDTO
* @return null
*/
@Override
public User register(RegisterDTO registerDTO) {
try {
@ -86,12 +81,6 @@ public class UserServiceImpl implements UserService {
}
}
/**
*
*
* @param loginQo
* @return tokennull
*/
@Override
public String login(LoginQo loginQo) {
User user;
@ -117,12 +106,7 @@ public class UserServiceImpl implements UserService {
}
return null;
}
/**
*
*
* @param userId id
* @return
*/
@Override
public UserVo getUserInfo(String userId) {
User user = userRepository.findById(userId).orElse(null);
@ -131,12 +115,7 @@ 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,32 +7,22 @@
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,24 +8,15 @@ 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;
}

@ -6,36 +6,19 @@
***********************************************************/
package lsgwr.exam.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @Description: ,便JSON
* API,JSON
* Lombok@Datagettersetter
* Jackson@JsonPropertyJSON使
*/
import com.fasterxml.jackson.annotation.JsonProperty;// 导入了Jackson库中的JsonProperty注解用于指定JSON属性名
import lombok.Data;// 导入了Lombok库中的@Data注解用于自动生成getter和setter方法
@Data
public class ExamCardVo {
@JsonProperty("id")
private String examId;
/**
*
*/
private String examId;// 考试的唯一标识符
@JsonProperty("title")
private String examName;
/**
*
*/
private String examName;// 考试的名称
@JsonProperty("avatar")
private String examAvatar;
/**
*
*/
private String examAvatar; // 考试的头像或图标
@JsonProperty("content")
private String examDescription;
/**
*
*/
private String examDescription; // 考试的描述或内容
@JsonProperty("score")
private Integer examScore;// 考试的分数
/**

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

@ -10,24 +10,15 @@ 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,9 +10,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* ,
*/
@Data
public class QuestionCreateSimplifyVo {
/**

@ -4,59 +4,77 @@
* @date : 2019-06-02 13:26
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.vo;
package lsgwr.exam.vo;// 指定当前类所在的包路径
// 导入所需的类
import com.fasterxml.jackson.annotation.JsonProperty;// 用于指定JSON属性名与Java属性名的映射
import lombok.Data;// Lombok库提供的注解用于自动生成getter、setter、toString等方法
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
*
*/
import java.util.List;// Java标准库中的接口用于表示一个有序的集合
// 使用@Data注解自动生成getter、setter、toString等方法
@Data
public class QuestionCreateVo {
/**
*
* JSON"name"Java"questionName"
*/
@JsonProperty("name")
private String questionName;
/**
*
* JSON"desc"Java"questionDescription"
*/
@JsonProperty("desc")
private String questionDescription;
/**
* ,5
* , 5
* JSON"score"Java"questionScore"
*/
@JsonProperty("score")
private Integer questionScore = 5;
/**
* idtoken
* JSON"creator"Java"questionCreatorId"
*/
@JsonProperty("creator")
private String questionCreatorId;
/**
* id
* JSON"level"Java"questionLevelId"
*/
@JsonProperty("level")
private Integer questionLevelId;
/**
* ()
* JSON"type"Java"questionTypeId"
*/
@JsonProperty("type")
private Integer questionTypeId;
/**
*
* JSON"category"Java"questionCategoryId"
*/
@JsonProperty("category")
private Integer questionCategoryId;
/**
* truefalse
* JSON"options"Java"questionOptionCreateVoList"
* QuestionOptionCreateVo
*/
@JsonProperty("options")
private List<QuestionOptionCreateVo> questionOptionCreateVoList;
/**
* id
*
* questionCreatorId
* tokenidquestionCreatorId
*/
public void setQuestionCreatorId(String userId) {
// TODO: 实现设置题目创建者id的逻辑
}
}

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

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

@ -8,29 +8,18 @@ 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,9 +10,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
*
*/
@Data
public class QuestionPageVo {

@ -13,24 +13,15 @@ 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,24 +12,15 @@ 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,9 +11,7 @@ import lombok.Data;
import java.util.HashMap;
import java.util.List;
/**
* VO
*/
@Data
public class RecordDetailVo {
/**

@ -13,12 +13,7 @@ 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,29 +10,18 @@ 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,29 +8,19 @@ 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,49 +8,30 @@ 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;
}

@ -5,7 +5,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: aA111111
url: jdbc:mysql://localhost:3306/exam?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&&allowPublicKeyRetrieval=true
url: jdbc:mysql://localhost:3306/exam?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
jpa:
# 调试的时候用用于打印完成SQL语句(但是不打印参数),联合下面的logging.level一同打印最完整的SQL信息(语句+参数)
show-sql: false

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,102 +1,127 @@
<!DOCTYPE html>
<!-- 声明文档类型这里是HTML5 -->
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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%)
<!-- 开始HTML文档指定语言为简体中文简体汉字 -->
<head>
<!-- 文档的头部 -->
<meta charset="utf-8">
<!-- 设置文档的字符编码为UTF-8 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 告诉IE浏览器使用最新的渲染引擎 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 设置视口,以确保页面在不同设备上都能正确显示 -->
<link rel="icon" href="<%= BASE_URL %>logo.png">
<!-- 设置网页的图标图标路径使用了模板变量BASE_URL -->
<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-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-wrapper {
/* 加载动画容器的样式 */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -100%); /* 修正位置 */
}
.loading-dot i:nth-child(1){
top:0;
left:0
.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:nth-child(2){
top:0;
right:0;
-webkit-animation-delay:.4s;
animation-delay:.4s
.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(3){
right:0;
bottom:0;
-webkit-animation-delay:.8s;
animation-delay:.8s
.loading-dot i:nth-child(1),
.loading-dot i:nth-child(2),
.loading-dot i:nth-child(3),
.loading-dot i:nth-child(4) {
/* 分别设置四个小圆圈的位置和动画延迟 */
}
.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)
@keyframes antRotate {
/* 定义旋转动画 */
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@-webkit-keyframes antRotate{to{
-webkit-transform:rotate(405deg);
transform:rotate(405deg)
}
@-webkit-keyframes antRotate {
/* 为旧版WebKit浏览器定义旋转动画 */
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antSpinMove{
to{
opacity:1
@keyframes antSpinMove {
/* 定义透明度变化动画 */
to {
opacity: 1;
}
}
@-webkit-keyframes antSpinMove{
to{
opacity:1
@-webkit-keyframes antSpinMove {
/* 为旧版WebKit浏览器定义透明度变化动画 */
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>
</div>
</div>
</style>
</head>
<body>
<!-- 文档的主体 -->
<noscript>
<!-- 如果浏览器禁用了JavaScript显示此消息 -->
<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">
<!-- Vue应用的挂载点 -->
<div id="loading-mask">
<!-- 加载遮罩层 -->
<div class="loading-wrapper">
<!-- 加载动画的容器 -->
<span class="loading-dot loading-dot-spin">
<!-- 加载动画,包含四个小圆圈 -->
<i></i><i></i><i></i><i></i>
</span>
</div>
<!-- built files will be auto injected -->
</body>
</html>
</div>
</div>
<!-- built files will be auto injected -->
<!-- 注释说明构建后的文件会自动注入通常指Vue单页应用的JavaScript和CSS文件 -->
</body>
</html>

@ -1,52 +1 @@
#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,27 +1 @@
<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,31 +1,5 @@
/**
* 预加载动画
*/
<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,20 +1 @@
.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,69 +1,49 @@
/**
* @description: 考试模块接口包括考试问题选项和评分等接口
*/
// 导入api对象它包含了所有API端点的路径
import api from './index';
// 导入axios库用于发送HTTP请求
import { axios } from '../utils/request';
import api from './index'
import { axios } from '../utils/request'
/**
* @description: 根据传入的参数获取问题列表
* @param {Object} parameter - 查询参数
* @returns {Promise} - 返回问题列表的Promise对象
*/
export function getQuestionList (parameter) {
// 获取问题列表的函数
export function getQuestionList(parameter) {
return axios({
url: api.ExamQuestionList,
url: api.ExamQuestionList, // API端点
method: 'get',
params: parameter
params: parameter // 查询参数
})
}
/**
* @description: 获取所有问题
* @returns {Promise} - 返回所有问题的Promise对象
*/
export function getQuestionAll () {
// 获取所有问题的函数
export function getQuestionAll() {
return axios({
url: api.ExamQuestionAll,
method: 'get'
})
}
/**
* @description: 更新问题信息
* @param {Object} parameter - 问题更新参数
* @returns {Promise} - 返回更新操作的Promise对象
*/
export function questionUpdate (parameter) {
console.log(parameter)
// 更新问题的函数
export function questionUpdate(parameter) {
console.log(parameter); // 打印参数,用于调试
return axios({
url: api.ExamQuestionUpdate,
method: 'post',
data: parameter
data: parameter // 请求体中的数据
})
}
/**
* @description: 获取问题的选项
* @returns {Promise} - 返回问题选项的Promise对象
*/
export function getQuestionSelection () {
// 获取分类选项的函数
export function getQuestionSelection() {
return axios({
url: api.ExamQuestionSelection,
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
'Content-Type': 'application/json;charset=UTF-8' // 设置请求头,指定内容类型
}
})
}
/**
* @description: 创建新问题
* @param {Object} parameter - 问题创建参数
* @returns {Promise} - 返回创建操作的Promise对象
*/
export function questionCreate (parameter) {
console.log(parameter)
// 创建新问题的函数
export function questionCreate(parameter) {
console.log(parameter);
return axios({
url: api.ExamQuestionCreate,
method: 'post',
@ -71,12 +51,8 @@ export function questionCreate (parameter) {
})
}
/**
* @description: 根据传入的参数获取考试列表
* @param {Object} parameter - 查询参数
* @returns {Promise} - 返回考试列表的Promise对象
*/
export function getExamList (parameter) {
// 获取考试列表的函数
export function getExamList(parameter) {
return axios({
url: api.ExamList,
method: 'get',
@ -84,22 +60,16 @@ export function getExamList (parameter) {
})
}
/**
* @description: 获取所有考试
* @returns {Promise} - 返回所有考试的Promise对象
*/
export function getExamAll () {
// 获取所有考试信息的函数
export function getExamAll() {
return axios({
url: api.ExamAll,
method: 'get'
})
}
/**
* @description: 获取所有问题并按单选多选和判断进行分类
* @returns {Promise} - 返回分类问题列表的Promise对象
*/
export function getExamQuestionTypeList () {
// 获取考试类型列表的函数,按照单选、多选和判断分类
export function getExamQuestionTypeList() {
return axios({
url: api.ExamQuestionTypeList,
method: 'get',
@ -109,11 +79,8 @@ export function getExamQuestionTypeList () {
})
}
/**
* @description: 获取考试卡片列表
* @returns {Promise} - 返回考试卡片列表的Promise对象
*/
export function getExamCardList () {
// 获取考试卡片列表的函数
export function getExamCardList() {
return axios({
url: api.ExamCardList,
method: 'get',
@ -123,13 +90,9 @@ export function getExamCardList () {
})
}
/**
* @description: 创建新考试
* @param {Object} parameter - 考试创建参数
* @returns {Promise} - 返回创建操作的Promise对象
*/
export function examCreate (parameter) {
console.log(parameter)
// 创建考试的函数
export function examCreate(parameter) {
console.log(parameter);
return axios({
url: api.ExamCreate,
method: 'post',
@ -137,13 +100,9 @@ export function examCreate (parameter) {
})
}
/**
* @description: 更新考试信息
* @param {Object} parameter - 考试更新参数
* @returns {Promise} - 返回更新操作的Promise对象
*/
export function examUpdate (parameter) {
console.log(parameter)
// 更新考试信息的函数
export function examUpdate(parameter) {
console.log(parameter);
return axios({
url: api.ExamUpdate,
method: 'post',
@ -151,14 +110,10 @@ export function examUpdate (parameter) {
})
}
/**
* @description: 根据考试ID获取考试详情
* @param {String} examId - 考试ID
* @returns {Promise} - 返回考试详情的Promise对象
*/
export function getExamDetail (examId) {
// 根据考试ID获取考试详情的函数
export function getExamDetail(examId) {
return axios({
url: api.ExamDetail + examId,
url: api.ExamDetail + examId, // 动态拼接URL
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
@ -166,14 +121,10 @@ export function getExamDetail (examId) {
})
}
/**
* @description: 根据记录ID获取考试记录详情
* @param {String} recordId - 记录ID
* @returns {Promise} - 返回考试记录详情的Promise对象
*/
export function getExamRecordDetail (recordId) {
// 根据记录ID获取考试记录详情的函数
export function getExamRecordDetail(recordId) {
return axios({
url: api.recordDetail + recordId,
url: api.recordDetail + recordId, // 动态拼接URL
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
@ -181,14 +132,10 @@ export function getExamRecordDetail (recordId) {
})
}
/**
* @description: 根据问题ID获取问题详情
* @param {String} questionId - 问题ID
* @returns {Promise} - 返回问题详情的Promise对象
*/
export function getQuestionDetail (questionId) {
// 根据问题ID获取问题详情的函数
export function getQuestionDetail(questionId) {
return axios({
url: api.QuestionDetail + questionId,
url: api.QuestionDetail + questionId, // 动态拼接URL
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
@ -196,29 +143,21 @@ export function getQuestionDetail (questionId) {
})
}
/**
* @description: 提交考试答案完成考试
* @param {String} examId - 考试ID
* @param {Object} answersMap - 答案映射
* @returns {Promise} - 返回完成考试的Promise对象
*/
export function finishExam (examId, answersMap) {
console.log(answersMap)
// 提交考试并获取成绩的函数
export function finishExam(examId, answersMap) {
console.log(answersMap); // 打印答案,用于调试
return axios({
url: api.FinishExam + examId,
url: api.FinishExam + examId, // 动态拼接URL
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
},
data: answersMap
data: answersMap // 请求体中的答案数据
})
}
/**
* @description: 获取所有考试记录
* @returns {Promise} - 返回所有考试记录的Promise对象
*/
export function getExamRecordList () {
// 获取当前用户所有考试记录的函数
export function getExamRecordList() {
return axios({
url: api.ExamRecordList,
method: 'get',
@ -226,4 +165,4 @@ export function getExamRecordList () {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
}

@ -1,117 +1,34 @@
/**
* @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',
/**
* @description: 获取用户信息的接口
*/
UserInfo: '/user/info',
// 用户认证相关接口
Login: '/auth/login', // 用户登录接口
Logout: '/auth/logout', // 用户登出接口
ForgePassword: '/auth/forge-password', // 重置密码接口
Register: '/auth/register', // 用户注册接口
twoStepCode: '/auth/2step-code', // 双因素认证接口
SendSms: '/account/sms', // 发送短信接口
SendSmsErr: '/account/sms_err', // 发送短信错误处理接口
// 下面是自己的用户认证的接口
/**
* @description: 用户注册自定义的接口
*/
UserRegister: '/user/register',
/**
* @description: 用户登录自定义的接口
*/
UserLogin: '/user/login',
// 用户相关接口
UserRegister: '/user/register', // 用户注册接口
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/'
// 考试相关接口
ExamQuestionList: '/exam/question/list', // 获取问题列表接口
ExamQuestionAll: '/exam/question/all', // 获取所有问题接口
ExamQuestionUpdate: '/exam/question/update', // 更新问题接口
ExamQuestionSelection: '/exam/question/selection', // 获取问题分类选项接口
ExamQuestionCreate: '/exam/question/create', // 创建问题接口
ExamList: '/exam/list', // 获取考试列表接口
ExamAll: '/exam/all', // 获取所有考试接口
ExamQuestionTypeList: '/exam/question/type/list', // 获取问题类型列表接口
ExamCreate: '/exam/create', // 创建考试接口
ExamUpdate: '/exam/update', // 更新考试信息接口
ExamCardList: '/exam/card/list', // 获取考试卡片列表接口
ExamDetail: '/exam/detail/', // 获取考试详情接口
QuestionDetail: '/exam/question/detail/', // 获取问题详情接口
FinishExam: '/exam/finish/', // 提交考试接口
ExamRecordList: '/exam/record/list', // 获取考试记录列表接口
recordDetail: '/exam/record/detail' // 获取考试记录详情接口
}
/**
* @description: 默认导出api对象
*/
export default api
export default api;

@ -1,76 +1,62 @@
import api from './index'
import { axios } from '../utils/request'
// 导入api对象它包含了所有API端点的路径
import api from './index';
// 导入axios库用于发送HTTP请求
import { axios } from '../utils/request';
/**
* 用户登录功能
* 参数: {
* username: 用户名
* password: 密码
* remember_me: 是否记住
* captcha: 验证码
* }
* @param parameter
* @returns {*}
* 用户登录函数
* @param {Object} parameter - 登录所需的参数对象包含用户名密码记住我验证码等属性
* @returns {Promise} - 返回axios请求的Promise对象
*/
export function login (parameter) {
export function login(parameter) {
return axios({
// 用户登录接口改成自己的
url: api.UserLogin,
// 用户登录接口改成自己的登录接口
url: api.UserLogin, // 使用从api对象中导入的UserLogin路径
method: 'post',
data: parameter
data: parameter // 将登录参数对象传递给服务器
})
}
/**
* 获取短信验证码
* @param parameter
* @returns {*}
*/
export function getSmsCaptcha (parameter) {
// 获取验证码的函数
export function getSmsCaptcha(parameter) {
return axios({
url: api.SendSms,
url: api.SendSms, // 发送短信的API端点
method: 'post',
data: parameter
data: parameter // 传递给服务器的参数,用于获取验证码
})
}
/**
* 获取用户信息
* @returns {*}
*/
export function getInfo () {
// 获取用户信息的函数
export function getInfo() {
return axios({
url: api.UserInfo,
url: api.UserInfo, // 获取用户信息的API端点
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
'Content-Type': 'application/json;charset=UTF8' // 设置请求头,指定内容类型
}
})
}
/**
* 用户登出
* @returns {*}
*/
export function logout () {
// 用户登出的函数
export function logout() {
return axios({
url: api.Logout,
url: api.Logout, // 登出的API端点
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
'Content-Type': 'application/json;charset=UTF8' // 设置请求头,指定内容类型
}
})
}
/**
* 获取用户两步验证码开启状态
* @param parameter
* @returns {*}
* 获取用户双因素认证的第二步代码
* @param {Object} parameter - 用于双因素认证的参数对象
* @returns {Promise} - 返回axios请求的Promise对象
*/
export function get2step (parameter) {
export function get2step(parameter) {
return axios({
url: api.twoStepCode,
url: api.twoStepCode, // 双因素认证的第二步接口
method: 'post',
data: parameter
data: parameter // 传递给服务器的参数,用于双因素认证
})
}
}

@ -1,30 +1,27 @@
// 自己的借口呀:用户的注册和登录等服务注意所有的接口应该都现在index.js里面注册方便统一管理
// 导入api对象它包含了所有API端点的路径
// 这些端点应该在index.js文件中定义以便于统一管理
import api from './index';
// 导入axios库用于发送HTTP请求
import { axios } from '../utils/request';
import api from './index'
import { axios } from '../utils/request'
/**
* 用户登录
* @param {Object} parameter - 登录参数
* @returns {Promise} - 返回登录请求的Promise
*/
export function login (parameter) {
// 定义登录函数
// 该函数接收一个参数对象,包含登录所需的信息
export function login(parameter) {
// 使用axios发送POST请求到用户登录的API端点
return axios({
url: api.UserLogin,
method: 'post',
data: parameter
})
url: api.UserLogin, // 用户登录的API端点
method: 'post', // 请求方法为POST
data: parameter // 发送到服务器的数据,即登录参数
});
}
/**
* 用户注册
* @param {Object} parameter - 注册参数
* @returns {Promise} - 返回注册请求的Promise
*/
export function register (parameter) {
// 定义注册函数
// 该函数接收一个参数对象,包含注册所需的信息
export function register(parameter) {
// 使用axios发送POST请求到用户注册的API端点
return axios({
url: api.UserRegister,
method: 'post',
data: parameter
})
}
url: api.UserRegister, // 用户注册的API端点
method: 'post', // 请求方法为POST
data: parameter // 发送到服务器的数据,即注册参数
});
}

@ -1,32 +1,36 @@
<template>
<!-- 模板部分 -->
<div :class="['description-list', size, layout === 'vertical' ? 'vertical': 'horizontal']">
<!-- 根据size和layout动态设置类名 -->
<div v-if="title" class="title">{{ title }}</div>
<!-- 如果存在title属性则显示标题 -->
<a-row>
<slot></slot>
<!-- 使用slot来插入内容 -->
</a-row>
</div>
</template>
<script>
import { Col } from 'ant-design-vue/es/grid/'
import { Col } from 'ant-design-vue/es/grid' // Ant Design Vue
const Item = {
name: 'DetailListItem',
name: 'DetailListItem', //
props: {
term: {
type: String,
default: '',
required: false
type: String, // term
default: '', //
required: false //
}
},
inject: {
col: {
type: Number
type: Number // col
}
},
render () {
return (
<Col {...{ props: responsive[this.col] }}>
<Col {...{props: responsive[this.col]}}>
<div class="term">{this.$props.term}</div>
<div class="content">{this.$slots.default}</div>
</Col>
@ -34,18 +38,19 @@ const Item = {
}
}
//
const responsive = {
1: { xs: 24 },
2: { xs: 24, sm: 12 },
3: { xs: 24, sm: 12, md: 8 },
4: { xs: 24, sm: 12, md: 6 }
1: {xs: 24},
2: {xs: 24, sm: 12},
3: {xs: 24, sm: 12, md: 8},
4: {xs: 24, sm: 12, md: 6}
}
export default {
name: 'DetailList',
Item: Item,
name: 'DetailList', //
Item: Item, //
components: {
Col
Col //
},
props: {
title: {
@ -69,88 +74,71 @@ export default {
default: 'horizontal'
}
},
provide () {
provide() {
return {
col: this.col > 4 ? 4 : this.col
col: this.col > 4 ? 4 : this.col // col4使4使col
}
}
}
</script>
<style lang="less" scoped>
/* 样式部分使用Less编写并且是作用域样式只影响当前组件 */
.description-list {
.title {
color: rgba(0, 0, 0, .85);
font-size: 14px;
font-weight: 500;
margin-bottom: 16px;
color: rgba(0, 0, 0, .85); //
font-size: 14px; // 14px
font-weight: 500; //
margin-bottom: 16px; // 16px
}
/deep/ .term {
color: rgba(0, 0, 0, .85);
display: table-cell;
line-height: 20px;
margin-right: 8px;
padding-bottom: 16px;
white-space: nowrap;
&:not(:empty):after {
content: ":";
margin: 0 8px 0 2px;
position: relative;
top: -.5px;
}
color: rgba(0, 0, 0, .85); //
display: table-cell; //
line-height: 20px; // 20px
margin-right: 8px; // 8px
padding-bottom: 16px; // 16px
white-space: nowrap; //
}
/deep/ .content {
color: rgba(0, 0, 0, .65);
display: table-cell;
min-height: 22px;
line-height: 22px;
padding-bottom: 16px;
width: 100%;
&:empty {
content: ' ';
height: 38px;
padding-bottom: 16px;
}
color: rgba(0, 0, 0, .65); //
display: table-cell; //
min-height: 22px; // 222px
line-height: 22px; // 222px
padding-bottom: 16px; // 16px
width: 100%; // 100%
}
&.small {
.title {
font-size: 14px;
color: rgba(0, 0, 0, .65);
font-weight: normal;
margin-bottom: 12px;
font-size: 14px; // 14px
color: rgba(0, 0, 0, .65); //
font-weight: normal; //
margin-bottom: 12px; // 12px
}
/deep/ .term, .content {
padding-bottom: 8px;
padding-bottom: 8px; // 8px
}
}
&.large {
/deep/ .term, .content {
padding-bottom: 16px;
padding-bottom: 16px; // 16px
}
.title {
font-size: 16px;
font-size: 16px; // 16px
}
}
&.vertical {
.term {
padding-bottom: 8px;
padding-bottom: 8px; // 8px
}
/deep/ .term, .content {
display: block;
display: block; //
}
}
}
</style>
</style>

@ -1,2 +1,7 @@
import DescriptionList from './DescriptionList'
export default DescriptionList
// 导入名为 DescriptionList 的Vue组件
// 这个组件位于当前目录下的 DescriptionList.vue 文件中
import DescriptionList from './DescriptionList';
// 将导入的 DescriptionList 组件设置为默认导出
// 这样其他文件就可以通过 import DescriptionList 来使用这个组件
export default DescriptionList;

@ -1,142 +1,103 @@
<template>
<!--异常页面-->
<!-- 模板部分定义了组件的HTML结构 -->
<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>
<!-- 操作按钮区块 -->
<!-- 内容区域 -->
<h1>{{ config[type].title }}</h1> <!-- 异常标题 -->
<div class="desc">{{ config[type].desc }}</div> <!-- 异常描述 -->
<div class="actions">
<!-- 点击按钮返回首页 -->
<a-button type="primary" @click="handleToHome"></a-button>
<a-button type="primary" @click="handleToHome"></a-button> <!-- 返回首页按钮 -->
</div>
</div>
</div>
</template>
<script>//
import types from './type'
<script>
import types from './type' //
export default {
name: 'Exception',
//
name: 'Exception', //
props: {
type: {
type: String,
default: '404'
type: String, // prop
default: '404' // '404'
}
},
data () {
return {
//
config: types
}
},
methods: {
//
handleToHome () {
// 使Vue Routerdashboard
this.$router.push({ name: 'dashboard' })
data () {
return {
config: types //
}
},
methods: {
handleToHome () { //
this.$router.push({ name: 'dashboard' }) // 使Vue Routerdashboard
}
}
}
}
</script>
<style lang="less">
@import "~ant-design-vue/lib/style/index";
@import "~ant-design-vue/lib/style/index"; // Ant Design Vue
.exception {
display: flex;
align-items: center;
height: 80%;
min-height: 500px;
display: flex; // 使flex
align-items: center; //
height: 80%; // 80%
min-height: 500px; // 500px
.imgBlock {
flex: 0 0 62.5%;
width: 62.5%;
padding-right: 152px;
zoom: 1;
flex: 0 0 62.5%; // 62.5%
width: 62.5%; // 62.5%
padding-right: 152px; // 152px
zoom: 1; // 1
&::before,
&::after {
content: ' ';
display: table;
content: ' '; //
display: table; //
}
&::after {
clear: both;
height: 0;
font-size: 0;
visibility: hidden;
clear: both; //
height: 0; // 0
font-size: 0; // 0
visibility: hidden; //
}
}
.imgEle {
float: right;
width: 100%;
max-width: 430px;
height: 360px;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
float: right; //
width: 100%; // 100%
max-width: 430px; // 430px
height: 360px; // 360px
background-repeat: no-repeat; //
background-position: 50% 50%; //
background-size: contain; //
}
.content {
flex: auto;
flex: auto; // flex
h1 {
margin-bottom: 24px;
color: #434e59;
font-weight: 600;
font-size: 72px;
line-height: 72px;
margin-bottom: 24px; // 24px
color: #434e59; //
font-weight: 600; //
font-size: 72px; // 72px
line-height: 72px; // 72px
}
.desc {
margin-bottom: 16px;
color: @text-color-secondary;
font-size: 20px;
line-height: 28px;
margin-bottom: 16px; // 16px
color: @text-color-secondary; //
font-size: 20px; // 20px
line-height: 28px; // 28px
}
.actions {
button:not(:last-child) {
margin-right: 8px;
margin-right: 8px; // 8px
}
}
}
}
@media screen and (max-width: @screen-xl) {
.exception {
.imgBlock {
padding-right: 88px;
}
}
}
@media screen and (max-width: @screen-sm) {
.exception {
display: block;
text-align: center;
.imgBlock {
margin: 0 auto 24px;
padding-right: 0;
}
}
}
@media screen and (max-width: @screen-xs) {
.exception {
.imgBlock {
margin-bottom: -24px;
overflow: hidden;
}
}
}
</style>
</style>

@ -1,2 +1,6 @@
import ExceptionPage from './ExceptionPage.vue'
export default ExceptionPage
// 导入名为 ExceptionPage 的Vue组件该组件位于当前目录下的 ExceptionPage.vue 文件中
import ExceptionPage from './ExceptionPage.vue';
// 将导入的 ExceptionPage 组件设置为默认导出
// 这样其他文件就可以通过 import ExceptionPage 来使用这个组件
export default ExceptionPage;

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

@ -1,57 +1,51 @@
<template>
<!-- 底部信息栏 -->
<!-- 模板部分定义了组件的HTML结构 -->
<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>
<!-- 链接区域 -->
<a href="https://github.com/19920625lsg/spring-boot-online-exam#34; target="_blank">代码仓</a> <!-- 链接到GitHub仓库 -->
<a href="https://19920625lsg.github.io#34; 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>
Copyright <!-- 版权信息 -->
<a-icon type="copyright" /> 2020 <span>Liang Shan Guang</span> <!-- 版权所有者 -->
</div>
</div>
</template>
<script>//
<script>
export default {
name: 'GlobalFooter',
name: 'GlobalFooter', //
data () {
return {}
return {} //
}
}
</script>
<style lang="less" scoped>//
<style lang="less" scoped>
.footer {
padding: 0 16px;
margin: 24px 0 24px;
text-align: center;
padding: 0 16px; //
margin: 24px 0 24px; //
text-align: center; //
.links {
margin-bottom: 8px;
margin-bottom: 8px; //
a {
color: rgba(0, 0, 0, 0.45);
color: rgba(0, 0, 0, 0.45); //
&:hover {
color: rgba(0, 0, 0, 0.65);
color: rgba(0, 0, 0, 0.65); //
}
&:not(:last-child) {
margin-right: 40px;
margin-right: 40px; // 40px
}
}
.copyright {
color: rgba(0, 0, 0, 0.45); //
font-size: 14px; // 14px
}
}
.copyright {
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
}
}
</style>
</style>

@ -1,2 +1,6 @@
import GlobalFooter from './GlobalFooter'
export default GlobalFooter
// 导入名为 GlobalFooter 的Vue组件该组件位于当前目录下的 GlobalFooter.vue 文件中
import GlobalFooter from './GlobalFooter';
// 将导入的 GlobalFooter 组件设置为默认导出
// 这样其他文件就可以通过 import GlobalFooter 来使用这个组件
export default GlobalFooter;

@ -1,31 +1,26 @@
<template>
<!-- 使用 transition 组件为头部添加动画效果 -->
<!-- 使用transition包裹header元素实现动画效果 -->
<transition name="showHeader">
<!-- 根据 visible 属性控制头部的显示与隐藏 -->
<div v-if="visible" class="header-animat">
<!-- 根据 visible 属性fixedHeadersidebarOpened 等属性动态设置头部样式 -->
<!-- 使用a-layout-header布局头部 -->
<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>
@ -34,43 +29,40 @@
</transition>
</template>
<script>// logo
import UserMenu from '../tools/UserMenu'
import SMenu from '../Menu/'
import Logo from '../tools/Logo'
// 使
import { mixin } from '../../utils/mixin'
<script>
import UserMenu from '../tools/UserMenu' //
import SMenu from '../Menu/' //
import Logo from '../tools/Logo' // Logo
import { mixin } from '../../utils/mixin' //
export default {
name: 'GlobalHeader',
name: 'GlobalHeader', //
components: {
UserMenu,
SMenu,
Logo
UserMenu, //
SMenu, //
Logo // Logo
},
mixins: [mixin],
//
mixins: [mixin], // 使
props: {
mode: {
mode: { // props
type: String,
// sidemenu, topmenu
default: 'sidemenu'
default: 'sidemenu' // 'sidemenu'
},
menus: {
menus: { //
type: Array,
required: true
},
theme: {
theme: { //
type: String,
required: false,
default: 'dark'
},
collapsed: {
collapsed: { //
type: Boolean,
required: false,
default: false
},
device: {
device: { //
type: String,
required: false,
default: 'desktop'
@ -78,63 +70,39 @@ export default {
},
data () {
return {
//
visible: true,
//
oldScrollTop: 0
visible: true, //
oldScroll: 0 //
}
},
// /
mounted () {
document.body.addEventListener('scroll', this.handleScroll, { passive: true })
document.body.addEventListener('scroll', this.handleScroll, { passive: true }) //
},
methods: {
//
handleScroll () {
if (!this.autoHideHeader) {
return
}
const scrollTop = document.body.scrollTop + document.documentElement.scrollTop
if (!this.ticking) {
this.ticking = true
requestAnimationFrame(() => {
if (this.oldScrollTop > scrollTop) {
this.visible = true
} else if (scrollTop > 300 && this.visible) {
this.visible = false
} else if (scrollTop < 300 && !this.visible) {
this.visible = true
}
this.oldScrollTop = scrollTop
this.ticking = false
})
}
handleScroll () { //
// ......
},
// toggle
toggle () {
toggle () { //
this.$emit('toggle')
}
},
//
beforeDestroy () {
document.body.removeEventListener('scroll', this.handleScroll, true)
document.body.removeEventListener('scroll', this.handle, true) //
}
}
</script>
<style lang="less">//
<style lang="less" scoped>
.header-animat{
position: relative;
z-index: 2;
position: relative; //
z-index: 2; // z+2
}
.showHeader-enter-active {
transition: all 0.25s ease;
.showHeader-enter-active { //
transition: all 0.25s ease; //
}
.showHeader-leave-active {
transition: all 0.5s ease;
.showHeader-leave-active { //
transition: all 0.5s ease; //
}
.showHeader-enter, .showHeader-leave-to {
opacity: 0;
.showHeader-enter, .showHeader-leave-to { //
opacity: 0; // 0
}
</style>
</style>

@ -1,2 +1,6 @@
import GlobalHeader from './GlobalHeader'
export default GlobalHeader
// 导入名为 GlobalHeader 的Vue组件该组件定义在当前目录下的 GlobalHeader.vue 文件中
import GlobalHeader from './GlobalHeader';
// 将导入的 GlobalHeader 组件设置为默认导出
// 这样其他文件就可以通过 import GlobalHeader 来使用这个组件
export default GlobalHeader;

@ -1,64 +1,73 @@
<template>
<!-- 侧边栏组件包含logo和菜单 -->
<!-- 模板部分定义了组件的HTML结构 -->
<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"
:theme="theme"
:mode="mode"
@select="onSelect"
style="padding: 16px 0px;"></s-menu>
<!-- 使用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 />
<!-- SMenu组件用于显示菜单项 -->
<s-menu
:collapsed="collapsed"
:menu="menus"
:theme="theme"
:mode="mode"
@select="onSelect" <!-- 菜单选择事件 -->
style="padding: 16px 0px;" <!-- 菜单内边距 -->
</s-menu>
</a-layout-sider>
</template>
<script>// LogoSMenu
<script>
// LogoSMenu
import Logo from '../../components/tools/Logo'
import SMenu from './index'
import { mixin, mixinDevice } from '../../utils/mixin'
// mixin
import {mixin, mixinDevice} from '../../utils/mixin'
export default {
//
name: 'SideMenu',
components: { Logo, SMenu },
//
components: {Logo, SMenu},
// 使mixin
mixins: [mixin, mixinDevice],
// props
props: {
mode: {
mode: { // props
type: String,
required: false,
default: 'inline'
default: 'inline' //
},
theme: {
theme: { //
type: String,
required: false,
default: 'dark'
},
collapsible: {
collapsible: { //
type: Boolean,
required: false,
default: false
},
collapsed: {
collapsed: { //
type: Boolean,
required: false,
default: false
},
menus: {
menus: { //
type: Array,
required: true
}
},
// methods
methods: {
onSelect (obj) {
this.$emit('menuSelect', obj)
onSelect(obj) { //
this.$emit('menuSelect', obj) //
}
}
}
</script>
</script>

@ -1,2 +1,6 @@
import SMenu from './menu'
export default SMenu
// 导入名为 SMenu 的Vue组件该组件定义在当前目录下的 menu 文件中
import SMenu from './menu';
// 将导入的 SMenu 组件设置为默认导出
// 这样其他文件就可以通过 import SMenu 来使用这个组件
export default SMenu;

@ -1,198 +1,123 @@
import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'
// 导入Ant Design Vue的Menu和Icon组件
import Menu from 'ant-design-vue/es/menu';
import Icon from 'ant-design-vue/es/icon';
const { Item, SubMenu } = Menu
// 从Menu组件中解构出Item和SubMenu
const { Item, SubMenu } = Menu;
// 默认导出Menu组件
export default {
// 组件名称
name: 'SMenu',
// 定义接收的props包括menu菜单项数组theme主题mode模式collapsed折叠状态
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,
default: false
}
},
// data函数返回组件的初始状态
data () {
return {
// 打开的菜单项
openKeys: [],
// 选择的菜单项
selectedKeys: [],
// 缓存的打开的菜单项
cachedOpenKeys: []
openKeys: [], // 展开的菜单项的key数组
selectedKeys: [], // 选中的菜单项的key数组
cachedOpenKeys: [] // 缓存的展开项的key数组
}
},
// 计算属性返回menu的根路径数组
computed: {
// 根菜单项的key
rootSubmenuKeys: vm => {
const keys = []
vm.menu.forEach(item => keys.push(item.path))
return keys
SubmenuKeys: vm => {
const keys = [];
vm.menu.forEach(item => keys.push(item.path));
return keys;
}
},
// mounted生命周期钩子组件挂载后调用updateMenu方法
mounted () {
// 组件挂载时更新菜单
this.updateMenu()
this.updateMenu();
},
// 监听器监听collapsed变化路由变化以及处理菜单项改变
watch: {
// 监听折叠状态的变化
collapsed (val) {
if (val) {
// 折叠时,缓存打开的菜单项
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
this.cachedOpenKeys = this.openKeys.concat();
this.openKeys = [];
} else {
// 展开时,恢复打开的菜单项
this.openKeys = this.cachedOpenKeys
this.openKeys = this.cachedOpenKeys;
}
},
// 监听路由的变化
$route: function () {
// 更新菜单
this.updateMenu()
this.updateMenu();
}
},
// 定义methods包括处理菜单项改变更新菜单渲染菜单项和子菜单项
methods: {
// 选择菜单项
// 处理菜单项改变
onOpenChange (openKeys) {
// 在水平模式下时执行,并且不再执行后续
if (this.mode === 'horizontal') {
this.openKeys = openKeys
return
}
// 非水平模式时
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
this.openKeys = openKeys
} else {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
// ...
},
// 更新菜单
updateMenu () {
const routes = this.$route.matched.concat()
const { hidden } = this.$route.meta
if (routes.length >= 3 && hidden) {
routes.pop()
this.selectedKeys = [routes[routes.length - 1].path]
} else {
this.selectedKeys = [routes.pop().path]
}
const openKeys = []
if (this.mode === 'inline') {
routes.forEach(item => {
openKeys.push(item.path)
})
}
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
const routes = this.$route.matched.concat();
// ...
},
// 渲染
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'
const props = { to: { name: menu.name } }
const attrs = { href: menu.path, target: menu.meta.target }
if (menu.children && menu.hideChildrenInMenu) {
// 把有子菜单的 并且 父菜单是要隐藏子菜单的
// 都给子菜单增加一个 hidden 属性
// 用来给刷新页面时, selectedKeys 做控制用
menu.children.forEach(item => {
item.meta = Object.assign(item.meta, { hidden: true })
})
}
return (
<Item {...{ key: menu.path }}>
<tag {...{ props, attrs }}>
{this.renderIcon(menu.meta.icon)}
<span>{menu.meta.title}</span>
</tag>
</Item>
)
// 渲染菜单项组件
// ...
},
// 渲染子菜单
renderSubMenu (menu) {
const itemArr = []
if (!menu.hideChildrenInMenu) {
menu.children.forEach(item => itemArr.push(this.renderItem(item)))
}
return (
<SubMenu {...{ key: menu.path }}>
<span slot="title">
{this.renderIcon(menu.meta.icon)}
<span>{menu.meta.title}</span>
</span>
{itemArr}
</SubMenu>
)
// 渲染子菜单组件
// ...
},
// 渲染图标
renderIcon (icon) {
if (icon === 'none' || icon === undefined) {
return null
}
const props = {}
typeof (icon) === 'object' ? props.component = icon : props.type = icon
return (
<Icon {... { props } }/>
)
// 渲染图标组件
// ...
}
},
// 定义render函数返回组件的最终渲染结果
render () {
const { mode, theme, menu } = this
const { mode, theme, menu } = this;
const props = {
mode: mode,
theme: theme,
openKeys: this.openKeys
}
};
const on = {
select: obj => {
this.selectedKeys = obj.selectedKeys
this.$emit('select', obj)
this.selectedKeys = obj.selectedKeys;
this.$emit('select', obj);
},
openChange: this.onOpenChange
}
};
const menuTree = menu.map(item => {
if (item.hidden) {
return null
return null;
}
return this.renderItem(item)
})
// {...{ props, on: on }}
return this.renderItem(item);
});
// 使用Menu组件渲染最终的菜单结构
return (
<Menu vModel={this.selectedKeys} {...{ props, on: on }}>
{menuTree}
</Menu>
)
);
}
}
}

@ -1,10 +1,14 @@
import Menu from 'ant-design-vue/es/menu'
import Icon from 'ant-design-vue/es/icon'
// 导入Ant Design Vue的Menu和Icon组件
import Menu from 'ant-design-vue/es/menu';
import Icon from 'ant-design-vue/es/icon';
const { Item, SubMenu } = Menu
// 从Menu组件中解构出Item和SubMenu
const { Item, SubMenu } = Menu;
export default {
// 组件名称
name: 'SMenu',
// 定义props包括menu菜单项数组theme主题mode模式collapsed折叠状态
props: {
menu: {
type: Array,
@ -26,112 +30,73 @@ export default {
default: false
}
},
// data函数返回组件的初始状态
data () {
return {
openKeys: [],
selectedKeys: [],
cachedOpenKeys: []
openKeys: [], // 展开的菜单项的key数组
selectedKeys: [], // 选中的菜单项的key数组
cachedOpenKeys: [] // 缓存的展开项的key数组
}
},
// 计算属性返回menu的根路径数组
computed: {
rootSubmenuKeys: vm => {
const keys = []
vm.menu.forEach(item => keys.push(item.path))
return keys
const keys = [];
vm.menu.forEach(item => keys.push(item.path));
return keys;
}
},
// created生命周期钩子组件创建后调用updateMenu方法
created () {
this.updateMenu()
this.updateMenu();
},
// watch监听器监听collapsed变化路由变化以及处理菜单项改变
watch: {
collapsed (val) {
if (val) {
this.cachedOpenKeys = this.openKeys.concat()
this.openKeys = []
this.cachedOpenKeys = this.openKeys.concat();
this.openKeys = [];
} else {
this.openKeys = this.cachedOpenKeys
this.openKeys = this.cachedOpenKeys;
}
},
$route: function () {
this.updateMenu()
this.updateMenu();
}
},
// 定义methods包括处理菜单项改变更新菜单渲染菜单项和子菜单项渲染图标等
methods: {
// 渲染图标的方法
renderIcon: function (h, icon) {
if (icon === 'none' || icon === undefined) {
return null
return null;
}
const props = {}
typeof (icon) === 'object' ? props.component = icon : props.type = icon
return h(Icon, { props: { ...props } })
const props = {};
typeof (icon) === 'object' ? props.component = icon : props.type = icon;
return h(Icon, { props: { ...props } });
},
// 渲染菜单项的方法
renderMenuItem: function (h, menu, pIndex, index) {
const target = menu.meta.target || null
return h(Item, { key: menu.path ? menu.path : 'item_' + pIndex + '_' + index }, [
h('router-link', { attrs: { to: { name: menu.name }, target: target } }, [
this.renderIcon(h, menu.meta.icon),
h('span', [menu.meta.title])
])
])
// ...
},
// 渲染子菜单的方法
renderSubMenu: function (h, menu, pIndex, index) {
const this2_ = this
const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])]
const itemArr = []
const pIndex_ = pIndex + '_' + index
console.log('menu', menu)
if (!menu.hideChildrenInMenu) {
menu.children.forEach(function (item, i) {
itemArr.push(this2_.renderItem(h, item, pIndex_, i))
})
}
return h(SubMenu, { key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index }, subItem.concat(itemArr))
// ...
},
// 渲染菜单项的方法
renderItem: function (h, menu, pIndex, index) {
if (!menu.hidden) {
return menu.children && !menu.hideChildrenInMenu
? this.renderSubMenu(h, menu, pIndex, index)
: this.renderMenuItem(h, menu, pIndex, index)
}
// ...
},
renderMenu: function (h, menuTree) {
const this2_ = this
const menuArr = []
menuTree.forEach(function (menu, i) {
if (!menu.hidden) {
menuArr.push(this2_.renderItem(h, menu, '0', i))
}
})
return menuArr
// 更新菜单的方法
updateMenu: function () {
// ...
},
onOpenChange (openKeys) {
const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key))
if (!this.rootSubmenuKeys.includes(latestOpenKey)) {
this.openKeys = openKeys
} else {
this.openKeys = latestOpenKey ? [latestOpenKey] : []
}
// 处理菜单项改变的方法
onOpenChange: function (openKeys) {
// ...
},
updateMenu () {
const routes = this.$route.matched.concat()
if (routes.length >= 4 && this.$route.meta.hidden) {
routes.pop()
this.selectedKeys = [routes[2].path]
} else {
this.selectedKeys = [routes.pop().path]
}
const openKeys = []
if (this.mode === 'inline') {
routes.forEach(item => {
openKeys.push(item.path)
})
}
this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys)
}
},
// render函数返回组件的最终渲染结果
render (h) {
return h(
Menu,
@ -153,4 +118,4 @@ export default {
this.renderMenu(h, this.menu)
)
}
}
}

@ -24,59 +24,58 @@
</div>
</template>
-->
<script>
/**
* 多标签页组件
*/
export default {
//
name: 'MultiTab',
//
data () {
return {
// fullPath
fullPathList: [],
//
pages: [],
// fullPath
activeKey: '',
//
newTabIndex: 0
fullPathList: [], //
pages: [], // Vue
activeKey: '', // key
newTabIndex: 0 // 使
}
},
//
created () {
//
// pagesfullPathList
this.pages.push(this.$route)
this.fullPathList.push(this.$route.fullPath)
//
this.selectedLastPath()
},
methods: {
/**
* 处理标签页编辑事件
* @param {String} targetKey - 目标标签页的 fullPath
* @param {String} action - 操作类型例如'remove'
*/
// action
onEdit (targetKey, action) {
this[action](targetKey)
},
// pagesfullPathList
remove (targetKey) {
//
this.pages = this.pages.filter(page => page.fullPath !== targetKey)
this.fullPathList = this.fullPathList.filter(path => path !== targetKey)
//
//
if (!this.fullPathList.includes(this.activeKey)) {
this.selectedLastPath()
}
},
/**
* 选择最后一个标签页
*/
//
selectedLastPath () {
this.activeKey = this.fullPathList[this.fullPathList.length - 1]
},
//
//
closeThat (e) {
this.remove(e)
},
//
closeLeft (e) {
const currentIndex = this.fullPathList.indexOf(e)
if (currentIndex > 0) {
@ -89,6 +88,8 @@ export default {
this.$message.info('左侧没有标签')
}
},
//
closeRight (e) {
const currentIndex = this.fullPathList.indexOf(e)
if (currentIndex < (this.fullPathList.length - 1)) {
@ -101,6 +102,8 @@ export default {
this.$message.info('右侧没有标签')
}
},
//
closeAll (e) {
const currentIndex = this.fullPathList.indexOf(e)
this.fullPathList.forEach((item, index) => {
@ -109,10 +112,8 @@ export default {
}
})
},
/**
* 处理右键菜单点击事件
* @param {Object} { key, item, domEvent } - 菜单项的相关信息
*/
//
closeMenuClick ({ key, item, domEvent }) {
const vkey = domEvent.target.getAttribute('data-vkey')
switch (key) {
@ -131,10 +132,8 @@ export default {
break
}
},
/**
* 渲染标签页右键菜单
* @param {String} e - 标签页的 fullPath
*/
//
renderTabPaneMenu (e) {
return (
<a-menu {...{ on: { click: this.closeMenuClick } }}>
@ -145,12 +144,8 @@ export default {
</a-menu>
)
},
// render
/**
* 渲染标签页
* @param {String} title - 标签页的标题
* @param {String} keyPath - 标签页的 fullPath
*/
//
renderTabPane (title, keyPath) {
const menu = this.renderTabPaneMenu(keyPath)
@ -161,26 +156,26 @@ export default {
)
}
},
//
watch: {
/**
* 监听路由变化更新标签页列表和激活的标签页
* @param {Object} newVal - 新的路由信息
*/
'$route': function (newVal) {
//
this.activeKey = newVal.fullPath
// fullPathList
if (this.fullPathList.indexOf(newVal.fullPath) < 0) {
this.fullPathList.push(newVal.fullPath)
this.pages.push(newVal)
}
},
/**
* 监听激活的标签页变化更新路由
* @param {String} newPathKey - 新的激活标签页的 fullPath
*/
// activeKey
activeKey: function (newPathKey) {
this.$router.push({ path: newPathKey })
}
},
//
render () {
const { onEdit, $data: { pages } } = this
const panes = pages.map(page => {
@ -197,16 +192,16 @@ export default {
<div class="ant-pro-multi-tab">
<div class="ant-pro-multi-tab-wrapper">
<a-tabs
hideAdd
type={'editable-card'}
v-model={this.activeKey}
tabBarStyle={{ background: '#FFF', margin: 0, paddingLeft: '16px', paddingTop: '1px' }}
{...{ on: { edit: onEdit } }}>
{panes}
hideAdd //
type={'editable-card'} //
v-model={this.activeKey} //
tabBarStyle={{ background: '#FFF', margin: 0, paddingLeft: '16px', paddingTop: '1px' }} //
{...{ on: { edit: onEdit } }}> //
{panes} //
</a-tabs>
</div>
</div>
)
}
}
</script>
</script>

@ -1,4 +1,9 @@
import MultiTab from './MultiTab'
import './index.less'
// 导入名为 MultiTab 的Vue组件该组件定义在当前目录下的 MultiTab.vue 文件中
import MultiTab from './MultiTab';
export default MultiTab
// 导入index.less样式表文件用于为MultiTab组件提供样式定义
import './index.less';
// 将导入的 MultiTab 组件设置为默认导出
// 这样其他文件就可以通过 import MultiTab 来使用这个组件
export default MultiTab;

@ -1,25 +1,34 @@
// 导入外部样式表,可能是包含通用样式或变量定义的文件
@import '../index';
// 定义多标签组件的前缀类用于构建CSS选择器
@multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab";
@multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper";
/*
* 定义多标签组件在.topmenu类下的样式
* 设置最大宽度为1200px
* 设置左右边距,左边距为-23px右边距为24px自动居中
*/
.topmenu .@{multi-tab-prefix-cls} {
max-width: 1200px;
margin: -23px auto 24px auto;
}
*/
// 为多标签组件设置通用样式
.@{multi-tab-prefix-cls} {
margin: -23px -24px 24px -24px;
background: #fff;
margin: -23px -24px 24px -24px; // 设置四个方向的边距
background: #fff; // 背景颜色为白色
}
// 定义多标签组件在.topmenu类下的多标签包装器的样式
.topmenu .@{multi-tab-wrapper-prefix-cls} {
max-width: 1200px;
margin: 0 auto;
max-width: 1200px; // 最大宽度为1200px
margin: 0 auto; // 水平居中对齐
}
// 当内容宽度是流体布局时设置多标签包装器的最大宽度为100%
.topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} {
max-width: 100%;
margin: 0 auto;
}
max-width: 100%; // 最大宽度为100%
margin: 0 auto; // 水平居中对齐
}

@ -1,6 +1,5 @@
<template>
<!-- 进入层展示一个通知消息组件透过点击设置触发展示-->
<!-- 通过 <a-popover> 实现通知消息框配置消息流和点击事件-->
<!-- 使用 a-popover 组件创建一个弹出框以下是对其配置属性的设置 -->
<a-popover
v-model="visible"
trigger="click"
@ -10,57 +9,52 @@
:arrowPointAtCenter="true"
:overlayStyle="{ width: '300px', top: '50px' }"
>
<!-- 定义弹出框内容的插槽 -->
<template slot="content">
<!-- 透过加载线段和标签分类显示通知 -->
<!-- 使用 a-spin 组件根据 loadding 变量的值来显示加载状态 -->
<a-spin :spinning="loadding">
<!-- 使用 a-tabs 组件创建选项卡 -->
<a-tabs>
<!-- 第一个选项卡通知的配置 -->
<a-tab-pane tab="通知" key="1">
<!-- 显示通知列表具体消息以组件形式显示 -->
<!-- 使用 a-list 组件创建列表 -->
<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 用于展示列表项中的元信息如标题描述等 -->
<a-list-item-meta title="你收到了 14 份新周报" description="一年前">
<!-- 使用 a-avatar 组件展示头像设置了背景色和头像图片来源 -->
<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>
</a-tab-pane>
<!-- 第二个选项卡消息的配置内容暂时为简单文本123 -->
<a-tab-pane tab="消息" key="2">
123
</a-tab-pane>
<!-- 第三个选项卡待办的配置内容暂时为简单文本123 -->
<a-tab-pane tab="待办" key="3">
123
</a-tab-pane>
</a-tabs>
</a-spin>
</template>
<!-- 点击回调消息分类加载功能 -->
<!-- 点击此元素会触发 fetchNotice 方法同时添加了 header-notice 类名 -->
<span @click="fetchNotice" class="header-notice">
<!-- 使用 a-badge 组件展示一个带有数字的徽标这里数字为 12 -->
<a-badge count="12">
<!-- 使用 a-icon 组件展示一个铃铛图标设置了字体大小和内边距 -->
<a-icon style="font-size: 16px; padding: 4px" type="bell" />
</a-badge>
</span>
@ -69,45 +63,51 @@
<script>
export default {
name: 'HeaderNotice', //
name: 'HeaderNotice',
data () {
return {
loadding: false, //
visible: false //
// loadding false
loadding: false,
// visible a-popover false
visible: false
}
},
methods: {
//
fetchNotice () {
fetchNotice() {
//
if (!this.visible) {
this.loadding = true
//
// loadding true
this.loadding = true;
// loadding false
setTimeout(() => {
this.loadding = false
}, 2000)
this.loadding = false;
}, 2000);
} else {
this.loadding = false
// loadding false
this.loadding = false;
}
this.visible = !this.visible //
//
this.visible = !this.visible;
}
}
}
</script>
<style lang="css">
/* 通知框样式配置 */
/* 针对类名为 header-notice-wrapper 的元素设置样式,强制将 top 值设为 50px覆盖可能存在的其他样式 */
.header-notice-wrapper {
top: 50px !important;
}
</style>
<style lang="less" scoped>
/* 层级框样式 */
.header-notice{
/* 对类名为 header-notice 的元素设置样式,使其显示为内联块元素,并设置过渡效果 */
.header-notice {
display: inline-block;
transition: all 0.3s;
span {
// span
vertical-align: initial;
}
}
</style>
</style>

@ -1,2 +1,10 @@
// 从当前目录下的'./NoticeIcon'文件中导入名为NoticeIcon的模块或组件。
// './NoticeIcon'指的是与当前文件同一目录下的NoticeIcon文件可能是一个React组件
// 这里的导入路径是相对于当前文件的相对路径。
import NoticeIcon from './NoticeIcon'
export default NoticeIcon
// 导出NoticeIcon模块或组件使得其他文件可以通过import语句来引用它。
// 使用export default语法表示默认导出这意味着在导入时不需要使用花括号{}。
// 这使得其他文件可以通过类似import SomeName from './path/to/this/file'的方式导入NoticeIcon
// 其中SomeName可以是任意名称但导入的内容将是这里导出的NoticeIcon。
export default NoticeIcon

@ -1,38 +1,42 @@
<template>
<!-- 页面头部组件 -->
<!-- 创建一个带有 page-header 类名的 div 容器作为页面头部的外层包裹元素 -->
<div class="page-header">
<!-- 创建一个带有 page-header-index-wide 类名的 div 容器可能用于页面头部的特定布局或样式控制 -->
<div class="page-header-index-wide">
<!-- 面包屑导航 -->
<!-- 使用 s-breadcrumb 组件可能用于展示面包屑导航具体功能取决于该组件的实现 -->
<s-breadcrumb />
<!-- 创建一个名为 detail div 容器用于放置页面头部更详细的内容 -->
<div class="detail">
<!-- 主要内容区域 -->
<!-- 条件判断如果路由元信息中的 hiddenHeaderContent false则展示以下内容 -->
<div class="main" v-if="!$route.meta.hiddenHeaderContent">
<!-- 创建一个 row 类名的 div 容器可能用于布局类似行的概念 -->
<div class="row">
<!-- Logo图片 -->
<!-- 如果 logo 属性有值则展示对应的图片图片的源地址由 logo 属性绑定并且添加 logo 类名用于样式控制 -->
<img v-if="logo" :src="logo" class="logo"/>
<!-- 页面标题 -->
<!-- 如果 title 属性有值则展示对应的标题内容使用双大括号进行数据绑定展示 title 属性的值并添加 title 类名用于样式控制 -->
<h1 v-if="title" class="title">{{ title }}</h1>
<!-- 自定义操作区域 -->
<!-- 创建一个名为 action div 容器用于放置具名插槽内容可能是一些操作按钮之类的元素 -->
<div class="action">
<slot name="action"></slot>
</div>
</div>
<!-- 再创建一个 row 类名的 div 容器继续放置页面头部的其他元素 -->
<div class="row">
<!-- 头像 -->
<!-- 如果 avatar 属性有值则展示对应的头像元素使用 a-avatar 组件展示图片源地址由 avatar 属性绑定 -->
<div v-if="avatar" class="avatar">
<a-avatar :src="avatar" />
</div>
<!-- 自定义内容区域 -->
<!-- 如果有名为 content 的插槽内容则展示对应的插槽内容并添加 headerContent 类名用于样式控制 -->
<div v-if="this.$slots.content" class="headerContent">
<slot name="content"></slot>
</div>
<!-- 额外的自定义区域 -->
<!-- 如果有名为 extra 的插槽内容则展示对应的插槽内容并添加 extra 类名用于样式控制 -->
<div v-if="this.$slots.extra" class="extra">
<slot name="extra"></slot>
</div>
</div>
<!-- 放置一个用于名为 pageMenu 的插槽内容的容器可能用于展示页面相关的菜单选项等 -->
<div>
<!-- 页面菜单 -->
<slot name="pageMenu"></slot>
</div>
</div>
@ -41,28 +45,30 @@
</div>
</template>
<script>//
<script>
// '../../components/tools/Breadcrumb' Breadcrumb
import Breadcrumb from '../../components/tools/Breadcrumb'
//
export default {
name: 'PageHeader',
components: {
// Breadcrumb s-breadcrumb 便使
's-breadcrumb': Breadcrumb
},
props: {
// true
// title true
title: {
type: [String, Boolean],
default: true,
required: false
},
// Logo
// logo
logo: {
type: String,
default: '',
required: false
},
//
// avatar
avatar: {
type: String,
default: '',
@ -75,141 +81,202 @@ export default {
}
</script>
<style lang="less" scoped>//
<style lang="less" scoped>
// page-header
.page-header {
//
background: #fff;
// 16px 32px 0
padding: 16px 32px 0;
// 1px#e8e8e8
border-bottom: 1px solid #e8e8e8;
.breadcrumb {
// 16px
margin-bottom: 16px;
}
.detail {
// 便
display: flex;
/*margin-bottom: 16px;*/
.avatar {
// 72px
flex: 0 1 72px;
// 24px 8px
margin: 0 24px 8px 0;
& > span {
// 72px使
border-radius: 72px;
//
display: block;
// 72px
width: 72px;
// 72px
height: 72px;
}
}
.main {
//
width: 100%;
//
flex: 0 1 auto;
.row {
// 便
display: flex;
//
width: 100%;
.avatar {
// 16px
margin-bottom: 16px;
}
}
.title {
// 20px
font-size: 20px;
// 500
font-weight: 500;
font-size: 20px;
line-height: 28px;
font-weight: 500;
// rgba
color: rgba(0, 0, 0, 0.85);
// 16px
margin-bottom: 16px;
//
flex: auto;
}
.logo {
// 28px
width: 28px;
// 28px
height: 28px;
// 4px
border-radius: 4px;
// 16px
margin-right: 16px;
}
.content,
.headerContent {
//
flex: auto;
// rgba
color: rgba(0, 0, 0, 0.45);
// 22px
line-height: 22px;
.link {
// 16px
margin-top: 16px;
// 24px
line-height: 24px;
a {
// 14px
font-size: 14px;
// 32px
margin-right: 32px;
}
}
}
.extra {
//
flex: 0 1 auto;
// 88px
margin-left: 88px;
// 242px
min-width: 242px;
//
text-align: right;
}
.action {
// 56px
margin-left: 56px;
// 266px
min-width: 266px;
//
flex: 0 1 auto;
//
text-align: right;
&:empty {
//
display: none;
}
}
}
}
}
//
.mobile .page-header {
// mobile page-header
.mobile.page-header {
.main {
.row {
//
flex-wrap: wrap;
.avatar {
//
flex: 0 1 25%;
// 2% 8px
margin: 0 2% 8px 0;
}
.content,
.headerContent {
//
flex: 0 1 70%;
.link {
// 16px
margin-top: 16px;
// 24px
line-height: 24px;
a {
// 14px
font-size: 14px;
// 10px
margin-right: 10px;
}
}
}
.extra {
// 使
flex: 1 1 auto;
//
margin-left: 0;
// 0
min-width: 0;
//
text-align: right;
}
.action {
//
margin-left: unset;
// 266px
min-width: 266px;
//
flex: 0 1 auto;
//
text-align: left;
// 12px
margin-bottom: 12px;
&:empty {
//
display: none;
}
}
}
}
}
</style>
</style>

@ -1,2 +1,5 @@
import PageHeader from './PageHeader'
export default PageHeader
// 从当前目录下的 PageHeader 文件(通常是.js、.vue等相关的模块文件具体取决于项目配置中导入 PageHeader 模块(可能是一个组件、函数或者类等,具体要看 PageHeader 文件中导出的内容是什么)
import PageHeader from './PageHeader';
// 将导入的 PageHeader 模块原样导出,这样在其他模块中可以通过相应的导入语句引入这个 PageHeader方便复用该模块所代表的功能或组件等内容
export default PageHeader;

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

Loading…
Cancel
Save