Compare commits

..

3 Commits

Author SHA1 Message Date
hunqiu 94e6719942 修改了view文件夹
2 days ago
hunqiu 0fe04dc92a 第二次修改
4 days ago
hunqiu b4a34f3397 修改了contrellon文件夹
5 days ago

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

@ -1,6 +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>

@ -1,108 +1,18 @@
# spring-boot-online-exam
> 在线Demo预览http://129.211.88.191 账户分别是admin、teacher、student密码是admin123。视频讲解代码https://www.bilibili.com/video/BV1FP4y1L7xt/
> 好消息一个小伙伴做了Python实现欢迎大家starhttps://github.com/xingxingzaixian/django-drf-online-exam
## 1.快速体验
### 1.1 事先准备
> clone代码并进入代码路径
```shell
git clone git@github.com:lsgwr/spring-boot-online-exam.git
cd spring-boot-online-exam
```
下面按照Linux和windows说明快速搭建的方法
### 1.2 Linux
执行代码下的脚本start.sh即可
然后访问 http://ip:80 即可访问自己搭建的考试系统
### 1.3 windows
+ 1.安装JDK推荐JDK8
+ 2.从官方仓库下载发布的jar包建议选择最新版https://github.com/lsgwr/spring-boot-online-exam/releases
+ 3.安装MySQL创建数据库exam并初始化密码为aA111111导入doc/sql/exam.sql文件来创建数据库
+ 4.启动jar包`java -jar exam.jar`
+ 5.访问http://ip:9527 即可访问自己搭建的考试系统
## 2.介绍
基于springboot的在线考试系统
### 2.1 功能简介
+ 支持单选题、多选题、判断题
+ 支持学生(student)、教师(teacher)、管理员(admin)三种角色
+ 学生:参加考试和查看我的考试
+ 教师:学生的所有权限+创建/编辑题目+创建/编辑考试
+ 管理员:教师的所有权限+管理用户
### 2.3 软件架构
> 前后端分离,前段组件化,方便二次开发;后端
+ 后端采用SpringBoot+JPA++Swagger2+JWT校验,根据不同用户的权限返回给用户不同的数据
+ 后端采用Vue+AntDesign,组件化拆分,封装了很多年公共组件,方便维护和二次开发
### 2.3 使用教程
+ 1.下载代码
```shell
git clone https://github.com/19920625lsg/spring-boot-online-exam.git
```
+ 2.初始化数据库
> 安装mysql的步骤这里省略网上的教程很多。安装好mysql后新建exam数据库密码和`spring-boot-online-exam/backend/exam/src/main/resources/application.yml`的`password: xxxxxx`保持一致,然后导入`spring-boot-online-exam/doc/sql/exam.sql`
+ 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`
+ 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)
+ 2.考试管理
+ 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)
## 4.参与贡献
1. Fork 本仓库
2. 新建 exam_xxx 分支
3. 提交代码
4. 新建 Pull Request
## 5.Todo
+ `√`0.修复issue提地bug题目创建失败
+ `√`1.考试详情编辑
+ 2.支持题目和考试的删除`删除的话比较麻烦先不做了最好是弄个visible字段不实际删除要不后面有些关联数据找不到就不好了`
> 如果题目有关联的考试则必须先删除对应的考试,反过来删除考试则不用删除题目
+ 3.图片改成base64存到数据库中
+ 4.题干和选项支持富文本
+ 5.支持批量导入题目
+ 6.新增用户管理、学科管理功能
+ 7.老师能考到所有学生的成绩以及考试的统计信息
+ 8.更多的数据分析功能
+ 9.支持容器化一键式部署(编好Dockerfile)
+ 10.支持移动端最好用uniapp做
+ ......抓紧做吧,争取每周末做一点......
ResultVO<RecordDetailVo> getExamRecordDetail(@PathVariable String recordId) {
// 定义返回结果
ResultVO<RecordDetailVo> resultVO;
try {
// 调用examService获取考试记录详情
RecordDetailVo recordDetailVo = examService.getRecordDetail(recordId);
// 返回成功结果
resultVO = new ResultVO<>(0, "获取考试记录详情成功", recordDetailVo);
} catch (Exception e) {
// 打印异常信息
e.printStackTrace();
// 返回失败结果
resultVO = new ResultVO<>(-1, "获取考试记录详情失败", null);
}
// 返回结果
return resultVO;
}
}

@ -29,15 +29,22 @@ public class ExamController {
@GetMapping("/question/all")
@ApiOperation("获取所有问题的列表")
// 获取全部问题列表
ResultVO<List<QuestionVo>> getQuestionAll() {
// 定义返回值
ResultVO<List<QuestionVo>> resultVO;
try {
// 调用examService获取全部问题列表
List<QuestionVo> questionAll = examService.getQuestionAll();
// 返回成功结果
resultVO = new ResultVO<>(0, "获取全部问题列表成功", questionAll);
} catch (Exception e) {
// 打印异常信息
e.printStackTrace();
// 返回失败结果
resultVO = new ResultVO<>(-1, "获取全部问题列表失败", null);
}
// 返回结果
return resultVO;
}
@ -47,10 +54,14 @@ public class ExamController {
// 完成问题的更新
System.out.println(questionVo);
try {
// 调用examService的updateQuestion方法更新问题
QuestionVo questionVoResult = examService.updateQuestion(questionVo);
// 返回更新成功的结果
return new ResultVO<>(0, "更新问题成功", questionVoResult);
} catch (Exception e) {
// 打印异常信息
e.printStackTrace();
// 返回更新失败的结果
return new ResultVO<>(-1, "更新问题失败", null);
}
}
@ -58,6 +69,7 @@ public class ExamController {
@PostMapping("/question/create")
@ApiOperation("创建问题")
ResultVO<String> questionCreate(@RequestBody QuestionCreateSimplifyVo questionCreateSimplifyVo, HttpServletRequest request) {
// 创建一个QuestionCreateVo对象
QuestionCreateVo questionCreateVo = new QuestionCreateVo();
// 把能拷贝过来的属性都拷贝过来
BeanUtils.copyProperties(questionCreateSimplifyVo, questionCreateVo);
@ -66,25 +78,33 @@ public class ExamController {
questionCreateVo.setQuestionCreatorId(userId);
System.out.println(questionCreateVo);
try {
// 调用examService的questionCreate方法创建问题
examService.questionCreate(questionCreateVo);
// 返回问题创建成功的ResultVO
return new ResultVO<>(0, "问题创建成功", null);
} catch (Exception e) {
// 打印异常信息
e.printStackTrace();
// 返回创建问题失败的ResultVO
return new ResultVO<>(-1, "创建问题失败", null);
}
}
@GetMapping("/question/selection")
@ApiOperation("获取问题分类的相关选项")
// 获取问题分类选项
ResultVO<QuestionSelectionVo> getSelections() {
// 调用examService的getSelections方法获取问题分类选项
QuestionSelectionVo questionSelectionVo = examService.getSelections();
// 如果获取成功
if (questionSelectionVo != null) {
// 返回成功的结果
return new ResultVO<>(0, "获取问题分类选项成功", questionSelectionVo);
} else {
// 否则返回失败的结果
return new ResultVO<>(-1, "获取问题分类选项失败", null);
}
}
@GetMapping("/question/detail/{id}")
@ApiOperation("根据问题的id获取问题的详细信息")
ResultVO<QuestionDetailVo> getQuestionDetail(@PathVariable String id) {
@ -92,58 +112,72 @@ public class ExamController {
System.out.println(id);
ResultVO<QuestionDetailVo> resultVO;
try {
// 调用examService的getQuestionDetail方法根据问题id获取问题的详细信息
QuestionDetailVo questionDetailVo = examService.getQuestionDetail(id);
// 如果获取成功则返回ResultVO对象状态码为0提示信息为"获取问题详情成功"数据为questionDetailVo
resultVO = new ResultVO<>(0, "获取问题详情成功", questionDetailVo);
} catch (Exception e) {
// 如果获取失败则打印异常信息并返回ResultVO对象状态码为-1提示信息为"获取问题详情失败"数据为null
e.printStackTrace();
resultVO = new ResultVO<>(-1, "获取问题详情失败", null);
}
// 返回ResultVO对象
return resultVO;
}
@GetMapping("/all")
@ApiOperation("获取全部考试的列表")
ResultVO<List<ExamVo>> getExamAll() {
// 需要拼接前端需要的考试列表对象
ResultVO<List<ExamVo>> resultVO;
try {
// 调用examService的getExamAll方法获取全部考试的列表
List<ExamVo> examVos = examService.getExamAll();
// 将获取到的考试列表封装到ResultVO对象中并返回
resultVO = new ResultVO<>(0, "获取全部考试的列表成功", examVos);
} catch (Exception e) {
// 捕获异常,并打印异常信息
e.printStackTrace();
// 将异常信息封装到ResultVO对象中并返回
resultVO = new ResultVO<>(-1, "获取全部考试的列表失败", null);
}
return resultVO;
}
@GetMapping("/question/type/list")
@ApiOperation("获取问题列表,按照单选、多选和判断题分类返回")
ResultVO<ExamQuestionTypeVo> getExamQuestionTypeList() {
// 获取问题的分类列表
ResultVO<ExamQuestionTypeVo> resultVO;
try {
// 调用examService的getExamQuestionType方法获取问题分类列表
ExamQuestionTypeVo examQuestionTypeVo = examService.getExamQuestionType();
// 如果获取成功,则返回成功的结果
resultVO = new ResultVO<>(0, "获取问题列表成功", examQuestionTypeVo);
} catch (Exception e) {
// 如果获取失败,则打印异常信息,并返回失败的结果
e.printStackTrace();
resultVO = new ResultVO<>(-1, "获取问题列表失败", null);
}
// 返回结果
return resultVO;
}
@PostMapping("/create")
@ApiOperation("创建考试")
ResultVO<Exam> createExam(@RequestBody ExamCreateVo examCreateVo, HttpServletRequest request) {
// 从前端传参数过来,在这里完成考试的入库
ResultVO<Exam> resultVO;
// 获取当前用户的id
String userId = (String) request.getAttribute("user_id");
try {
// 调用examService的create方法将examCreateVo和userId作为参数传入创建考试
Exam exam = examService.create(examCreateVo, userId);
// 创建一个ResultVO对象将创建成功的考试信息返回
resultVO = new ResultVO<>(0, "创建考试成功", exam);
} catch (Exception e) {
// 捕获异常打印异常信息并创建一个ResultVO对象将创建失败的考试信息返回
e.printStackTrace();
resultVO = new ResultVO<>(-1, "创建考试失败", null);
}
// 返回ResultVO对象
return resultVO;
}
@ -152,12 +186,17 @@ public class ExamController {
ResultVO<Exam> updateExam(@RequestBody ExamVo examVo, HttpServletRequest request) {
// 从前端传参数过来,在这里完成考试的入库
ResultVO<Exam> resultVO;
// 获取当前用户id
String userId = (String) request.getAttribute("user_id");
try {
// 调用service层更新考试
Exam exam = examService.update(examVo, userId);
// 返回更新成功的resultVO
resultVO = new ResultVO<>(0, "更新考试成功", exam);
} catch (Exception e) {
// 打印异常信息
e.printStackTrace();
// 返回更新失败的resultVO
resultVO = new ResultVO<>(-1, "更新考试失败", null);
}
return resultVO;
@ -169,12 +208,16 @@ public class ExamController {
// 获取考试列表卡片
ResultVO<List<ExamCardVo>> resultVO;
try {
// 调用examService的getExamCardList方法获取考试列表卡片
List<ExamCardVo> examCardVoList = examService.getExamCardList();
// 如果获取成功,则返回成功的结果
resultVO = new ResultVO<>(0, "获取考试列表卡片成功", examCardVoList);
} catch (Exception e) {
// 如果获取失败,则打印异常信息,并返回失败的结果
e.printStackTrace();
resultVO = new ResultVO<>(-1, "获取考试列表卡片失败", null);
}
// 返回结果
return resultVO;
}
@ -194,16 +237,20 @@ public class ExamController {
@PostMapping("/finish/{examId}")
@ApiOperation("根据用户提交的答案对指定id的考试判分")
// 完成考试
ResultVO<ExamRecord> finishExam(@PathVariable String examId, @RequestBody HashMap<String, List<String>> answersMap, HttpServletRequest request) {
// 定义返回结果
ResultVO<ExamRecord> resultVO;
try {
// 拦截器里设置上的用户id
String userId = (String) request.getAttribute("user_id");
// 下面根据用户提交的信息进行判分,返回用户的得分情况
ExamRecord examRecord = examService.judge(userId, examId, answersMap);
// 返回结果
resultVO = new ResultVO<>(0, "考卷提交成功", examRecord);
} catch (Exception e) {
e.printStackTrace();
// 返回错误结果
resultVO = new ResultVO<>(-1, "考卷提交失败", null);
}
return resultVO;
@ -211,6 +258,7 @@ public class ExamController {
@GetMapping("/record/list")
@ApiOperation("获取当前用户的考试记录")
// 获取考试记录列表
ResultVO<List<ExamRecordVo>> getExamRecordList(HttpServletRequest request) {
ResultVO<List<ExamRecordVo>> resultVO;
try {
@ -229,14 +277,20 @@ public class ExamController {
@GetMapping("/record/detail/{recordId}")
@ApiOperation("根据考试记录id获取考试记录详情")
ResultVO<RecordDetailVo> getExamRecordDetail(@PathVariable String recordId) {
// 定义返回结果
ResultVO<RecordDetailVo> resultVO;
try {
// 调用examService获取考试记录详情
RecordDetailVo recordDetailVo = examService.getRecordDetail(recordId);
// 返回成功结果
resultVO = new ResultVO<>(0, "获取考试记录详情成功", recordDetailVo);
} catch (Exception e) {
// 打印异常信息
e.printStackTrace();
// 返回失败结果
resultVO = new ResultVO<>(-1, "获取考试记录详情失败", null);
}
// 返回结果
return resultVO;
}
}

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

@ -1,90 +1,10 @@
/***********************************************************
* @Description : REST
* @author : 广(Laing Shan Guang)
* @date : 2019-05-16 23:45
* @email : liangshanguang2@gmail.com
***********************************************************/
package lsgwr.exam.controller;
import lsgwr.exam.dto.RegisterDTO;
import lsgwr.exam.entity.User;
import lsgwr.exam.enums.ResultEnum;
import lsgwr.exam.qo.LoginQo;
import lsgwr.exam.service.UserService;
import lsgwr.exam.vo.ResultVO;
import lsgwr.exam.vo.UserInfoVo;
import lsgwr.exam.vo.UserVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@RestController
@Api(tags = "User APIs")
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
@ApiOperation("注册")
ResultVO<User> register(@RequestBody RegisterDTO registerDTO) {
ResultVO<User> resultVO;
// 注册信息的完善,还有唯一性校验没(用户名、邮箱和手机号)已经在user表中通过unique来设置了
User user = userService.register(registerDTO);
if (user != null) {
// 注册成功
resultVO = new ResultVO<>(ResultEnum.REGISTER_SUCCESS.getCode(), ResultEnum.REGISTER_SUCCESS.getMessage(), user);
} else {
resultVO = new ResultVO<>(ResultEnum.REGISTER_FAILED.getCode(), ResultEnum.REGISTER_FAILED.getMessage(), null);
}
return resultVO;
}
@PostMapping("/login")
@ApiOperation("根据用户名或邮箱登录,登录成功返回token")
ResultVO<String> login(@RequestBody LoginQo loginQo) { // 这里不用手机号是因为手机号和用户名难以进行格式区分,而用户名和
// 用户登录
ResultVO<String> resultVO;
String token = userService.login(loginQo);
if (token != null) {
// 登录成功
resultVO = new ResultVO<>(ResultEnum.LOGIN_SUCCESS.getCode(), ResultEnum.LOGIN_SUCCESS.getMessage(), token);
} else {
// 登录失败
resultVO = new ResultVO<>(ResultEnum.LOGIN_FAILED.getCode(), ResultEnum.LOGIN_FAILED.getMessage(), null);
}
return resultVO;
}
@GetMapping("/user-info")
@ApiOperation("获取用户信息")
ResultVO<UserVo> getUserInfo(HttpServletRequest request) {
String userId = (String) request.getAttribute("user_id");
UserVo userVo = userService.getUserInfo(userId);
return new ResultVO<>(ResultEnum.GET_INFO_SUCCESS.getCode(), ResultEnum.GET_INFO_SUCCESS.getMessage(), userVo);
}
@GetMapping("/info")
@ApiOperation("获取用户的详细信息,包括个人信息页面和操作权限")
ResultVO<UserInfoVo> getInfo(HttpServletRequest request) {
System.out.println("进入/user/info的获取用户信息的接口");
String userId = (String) request.getAttribute("user_id");
UserInfoVo userInfoVo = userService.getInfo(userId);
return new ResultVO<>(ResultEnum.GET_INFO_SUCCESS.getCode(), ResultEnum.GET_INFO_SUCCESS.getMessage(), userInfoVo);
}
@GetMapping("/test")
@ApiOperation("测试接口")
String test(HttpServletRequest request) {
// 下面这两个属性都是登录拦截器从token中解析地当用户名不对或者token过期时是走不到接口内的
String userId = (String) request.getAttribute("user_id");
String username = (String) request.getAttribute("username");
System.out.println("用户id" + userId);
System.out.println("用户名:" + username);
return "用户id" + userId + "\n用户名" + username;
// 使用Spring Boot框架
@SpringBootApplication
public class ExamApplication {
// 主方法
public static void main(String[] args) {
// 运行Spring Boot应用程序
SpringApplication.run(ExamApplication.class, args);
}
}

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

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

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

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

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

@ -1,10 +1,14 @@
<script>
//
import { colorList } from '../../../components/SettingDrawer/settingConfig'
// ant-design-vueswitchlistitem
import ASwitch from 'ant-design-vue/es/switch'
import AList from 'ant-design-vue/es/list'
import AListItem from 'ant-design-vue/es/list/Item'
// mixin
import { mixin } from '../../../utils/mixin'
// Meta
const Meta = AListItem.Meta
export default {
@ -20,6 +24,7 @@ export default {
}
},
filters: {
//
themeFilter (theme) {
const themeMap = {
'dark': '暗色',
@ -29,11 +34,13 @@ export default {
}
},
methods: {
//
colorFilter (color) {
const c = colorList.filter(o => o.color === color)[0]
return c && c.key
},
//
onChange (checked) {
if (checked) {
this.$store.dispatch('ToggleTheme', 'dark')

@ -1,8 +1,12 @@
<template>
<div class="page-header-index-wide">
<!-- 卡片组件用于展示内容 -->
<a-card :bordered="false" :bodyStyle="{ padding: '16px 0', height: '100%' }" :style="{ height: '100%' }">
<!-- 账户设置信息主容器 -->
<div class="account-settings-info-main" :class="device">
<!-- 账户设置信息左侧 -->
<div class="account-settings-info-left">
<!-- 菜单组件用于展示导航 -->
<a-menu
:mode="device == 'mobile' ? 'horizontal' : 'inline'"
:style="{ border: '0', width: device == 'mobile' ? '560px' : 'auto'}"
@ -10,22 +14,28 @@
type="inner"
@openChange="onOpenChange"
>
<!-- 菜单项用于展示导航链接 -->
<a-menu-item key="/account/settings/base">
<!-- 路由链接用于跳转到基本设置页面 -->
<router-link :to="{ name: 'BaseSettings' }">
基本设置
</router-link>
</a-menu-item>
<a-menu-item key="/account/settings/custom">
<!-- 路由链接用于跳转到个性化设置页面 -->
<router-link :to="{ name: 'CustomSettings' }">
个性化
</router-link>
</a-menu-item>
</a-menu>
</div>
<!-- 账户设置信息右侧 -->
<div class="account-settings-info-right">
<!-- 账户设置信息标题 -->
<div class="account-settings-info-title">
<span>{{ $route.meta.title }}</span>
</div>
<!-- 路由视图用于展示当前路由对应的组件 -->
<route-view></route-view>
</div>
</div>

Loading…
Cancel
Save