├── .gitignore ├── LICENSE ├── README.en.md ├── README.md ├── backend └── exam │ ├── .gitignore │ ├── README.md │ ├── images │ ├── 拦截器注入配置文件属性.png │ └── 拦截器注入配置文件属性2.png │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── com │ │ └── huawei │ │ └── l00379880 │ │ └── exam │ │ ├── ExamApplication.java │ │ ├── config │ │ ├── CORSConf.java │ │ ├── IntercepterConfig.java │ │ ├── Swagger2Config.java │ │ └── package-info.java │ │ ├── controller │ │ ├── ExamController.java │ │ ├── UploadDownloadController.java │ │ ├── UserController.java │ │ └── package-info.java │ │ ├── dto │ │ ├── RegisterDTO.java │ │ └── package-info.java │ │ ├── entity │ │ ├── Action.java │ │ ├── Exam.java │ │ ├── ExamRecord.java │ │ ├── ExamRecordLevel.java │ │ ├── Page.java │ │ ├── Question.java │ │ ├── QuestionCategory.java │ │ ├── QuestionLevel.java │ │ ├── QuestionOption.java │ │ ├── QuestionType.java │ │ ├── Role.java │ │ ├── User.java │ │ └── package-info.java │ │ ├── enums │ │ ├── LoginTypeEnum.java │ │ ├── QuestionEnum.java │ │ ├── ResultEnum.java │ │ └── RoleEnum.java │ │ ├── exception │ │ ├── ExamException.java │ │ └── package-info.java │ │ ├── interceptor │ │ └── LoginInterceptor.java │ │ ├── qo │ │ ├── DownloadQo.java │ │ ├── LoginQo.java │ │ ├── UploadModel.java │ │ ├── UploadModel2.java │ │ └── package-info.java │ │ ├── repository │ │ ├── ActionRepository.java │ │ ├── ExamRecordLevelRepository.java │ │ ├── ExamRecordRepository.java │ │ ├── ExamRepository.java │ │ ├── PageRepository.java │ │ ├── QuestionCategoryRepository.java │ │ ├── QuestionLevelRepository.java │ │ ├── QuestionOptionRepository.java │ │ ├── QuestionRepository.java │ │ ├── QuestionTypeRepository.java │ │ ├── RoleRepository.java │ │ ├── UserRepository.java │ │ └── package-info.java │ │ ├── service │ │ ├── ExamService.java │ │ ├── UserService.java │ │ ├── impl │ │ │ ├── ExamServiceImpl.java │ │ │ └── UserServiceImpl.java │ │ └── package-info.java │ │ ├── utils │ │ ├── FileTransUtil.java │ │ ├── FileUtils.java │ │ ├── JwtUtils.java │ │ ├── ResultVOUtil.java │ │ └── package-info.java │ │ └── vo │ │ ├── ActionVo.java │ │ ├── ExamCardVo.java │ │ ├── ExamCreateVo.java │ │ ├── ExamDetailVo.java │ │ ├── ExamPageVo.java │ │ ├── ExamQuestionSelectVo.java │ │ ├── ExamQuestionTypeVo.java │ │ ├── ExamRecordVo.java │ │ ├── ExamVo.java │ │ ├── JsonData.java │ │ ├── PageVo.java │ │ ├── QuestionCreateSimplifyVo.java │ │ ├── QuestionCreateVo.java │ │ ├── QuestionDetailVo.java │ │ ├── QuestionOptionCreateVo.java │ │ ├── QuestionOptionVo.java │ │ ├── QuestionPageVo.java │ │ ├── QuestionSelectionVo.java │ │ ├── QuestionVo.java │ │ ├── RecordDetailVo.java │ │ ├── ResultVO.java │ │ ├── RoleVo.java │ │ ├── UserInfoVo.java │ │ └── UserVo.java │ └── resources │ ├── application-dev.yml │ ├── application-prod.yml │ └── application.yml ├── doc ├── README.md ├── deploy │ ├── README.md │ └── nginx.conf ├── images │ ├── exam_create.png │ ├── exam_detail.png │ ├── exam_join.png │ ├── exam_join2.png │ ├── exam_list.png │ ├── exam_record.png │ ├── exam_update.png │ ├── question_create.png │ ├── question_list.png │ └── question_update.png ├── references │ ├── JWT校验.md │ └── images │ │ ├── JWT概要.png │ │ ├── tomcat开启session共享.png │ │ ├── 单机tomcat应用登录检验.png │ │ └── 登录校验方案.png └── sql │ ├── exam.sql │ ├── exam_bak.sql │ ├── exam_bak_2019-10-19.sql │ ├── exam_bak_2019-10-23.sql │ ├── exam_bak_2019-10-24.sql │ ├── exam_bak_2019-10-27.sql │ ├── exam_bak_2019-10-28.sql │ ├── exam_bak_2019-10-29.sql │ ├── exam_bak_2019-10-30.sql │ ├── exam_bak_2019-11-03.sql │ └── exam_bak_2020-02-24.sql └── frontend └── exam ├── .editorconfig ├── .env ├── .gitattributes ├── .gitignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── README.zh-CN.md ├── babel.config.js ├── docs ├── add-page-loading-animate.md ├── load-on-demand.md ├── multi-tabs.md └── webpack-bundle-analyzer.md ├── jest.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── avatar2.jpg ├── color.less ├── home │ ├── cover1.jpg │ ├── cover2.jpg │ ├── cover3.jpg │ ├── cover4.jpg │ └── cover5.jpg ├── index.html ├── loading │ ├── loading.css │ ├── loading.html │ └── option2 │ │ ├── html_code_segment.html │ │ ├── loading.css │ │ └── loading.svg └── logo.png ├── src ├── App.vue ├── api │ ├── exam.js │ ├── index.js │ ├── login.js │ └── user.js ├── assets │ ├── background.svg │ ├── icons │ │ ├── bx-analyse.svg │ │ ├── exam-admin.svg │ │ ├── exam-list.svg │ │ ├── mine.svg │ │ └── question-admin.svg │ ├── logo.png │ └── logo.svg ├── components │ ├── ArticleListContent │ │ ├── ArticleListContent.vue │ │ └── index.js │ ├── AvatarList │ │ ├── Item.vue │ │ ├── List.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── Charts │ │ ├── Bar.vue │ │ ├── ChartCard.vue │ │ ├── Liquid.vue │ │ ├── MiniArea.vue │ │ ├── MiniBar.vue │ │ ├── MiniProgress.vue │ │ ├── MiniSmoothArea.vue │ │ ├── Radar.vue │ │ ├── RankList.vue │ │ ├── TagCloud.vue │ │ ├── TransferBar.vue │ │ ├── Trend.vue │ │ ├── chart.less │ │ └── smooth.area.less │ ├── CountDown │ │ ├── CountDown.vue │ │ ├── index.js │ │ └── index.md │ ├── DescriptionList │ │ ├── DescriptionList.vue │ │ └── index.js │ ├── Ellipsis │ │ ├── Ellipsis.vue │ │ ├── index.js │ │ └── index.md │ ├── Exception │ │ ├── ExceptionPage.vue │ │ ├── index.js │ │ └── type.js │ ├── FooterToolbar │ │ ├── FooterToolBar.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── GlobalFooter │ │ ├── GlobalFooter.vue │ │ └── index.js │ ├── GlobalHeader │ │ ├── GlobalHeader.vue │ │ └── index.js │ ├── IconSelector │ │ ├── IconSelector.vue │ │ ├── README.md │ │ ├── icons.js │ │ └── index.js │ ├── Menu │ │ ├── SideMenu.vue │ │ ├── index.js │ │ ├── menu.js │ │ └── menu.render.js │ ├── MultiTab │ │ ├── MultiTab.vue │ │ ├── index.js │ │ └── index.less │ ├── NoticeIcon │ │ ├── NoticeIcon.vue │ │ └── index.js │ ├── NumberInfo │ │ ├── NumberInfo.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── PageHeader │ │ ├── PageHeader.vue │ │ └── index.js │ ├── PageLoading │ │ └── index.jsx │ ├── Result │ │ ├── Result.vue │ │ └── index.js │ ├── SettingDrawer │ │ ├── SettingDrawer.vue │ │ ├── SettingItem.vue │ │ ├── index.js │ │ └── settingConfig.js │ ├── StandardFormRow │ │ ├── StandardFormRow.vue │ │ └── index.js │ ├── Table │ │ ├── README.md │ │ └── index.js │ ├── TagSelect │ │ ├── TagSelectOption.jsx │ │ └── index.jsx │ ├── Tree │ │ └── Tree.jsx │ ├── Trend │ │ ├── Trend.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── _util │ │ └── util.js │ ├── global.less │ ├── index.js │ ├── index.less │ └── tools │ │ ├── Breadcrumb.vue │ │ ├── DetailList.vue │ │ ├── HeadInfo.vue │ │ ├── Logo.vue │ │ ├── TwoStepCaptcha.vue │ │ ├── UserMenu.vue │ │ └── index.js ├── config │ ├── defaultSettings.js │ └── router.config.js ├── core │ ├── bootstrap.js │ ├── directives │ │ └── action.js │ ├── icons.js │ ├── lazy_lib │ │ └── components_use.js │ ├── lazy_use.js │ └── use.js ├── layouts │ ├── BasicLayout.vue │ ├── BlankLayout.vue │ ├── PageView.vue │ ├── RouteView.vue │ ├── UserLayout.vue │ └── index.js ├── main.js ├── permission.js ├── router │ ├── README.md │ └── index.js ├── store │ ├── getters.js │ ├── index.js │ ├── modules │ │ ├── app.js │ │ ├── permission.js │ │ └── user.js │ └── mutation-types.js ├── utils │ ├── axios.js │ ├── device.js │ ├── domUtil.js │ ├── filter.js │ ├── helper │ │ └── permission.js │ ├── mixin.js │ ├── permissions.js │ ├── request.js │ ├── storage.js │ ├── util.js │ └── utils.less └── views │ ├── 404.vue │ ├── Home.vue │ ├── account │ └── settings │ │ ├── AvatarModal.vue │ │ ├── BaseSetting.vue │ │ ├── Custom.vue │ │ └── Index.vue │ ├── dashboard │ └── Workplace.vue │ ├── exception │ ├── 403.vue │ ├── 404.vue │ └── 500.vue │ ├── home │ ├── Banner.vue │ ├── List.vue │ ├── ListItem.vue │ ├── Page1.vue │ ├── default.less │ └── home.less │ ├── list │ ├── ExamCardList.vue │ ├── ExamDetail.vue │ ├── ExamRecordDetail.vue │ ├── ExamRecordList.vue │ ├── ExamTableList.vue │ ├── QuestionTableList.vue │ └── modules │ │ ├── CreateForm.vue │ │ ├── QuestionEditModal.vue │ │ ├── QuestionViewModal.vue │ │ ├── StepByStepExamModal.vue │ │ ├── StepByStepModal.vue │ │ └── StepByStepQuestionModal.vue │ └── user │ ├── Login.vue │ ├── Register.vue │ └── RegisterResult.vue ├── tests └── unit │ └── .eslintrc.js ├── vue.config.js ├── webstorm.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Liang Shan Guang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # spring-boot-online-exam 2 | 3 | #### Description 4 | 基于springboot的在线考试系统 5 | 6 | #### Software Architecture 7 | Software architecture description 8 | 9 | #### Installation 10 | 11 | 1. xxxx 12 | 2. xxxx 13 | 3. xxxx 14 | 15 | #### Instructions 16 | 17 | 1. xxxx 18 | 2. xxxx 19 | 3. xxxx 20 | 21 | #### Contribution 22 | 23 | 1. Fork the repository 24 | 2. Create Feat_xxx branch 25 | 3. Commit your code 26 | 4. Create Pull Request 27 | 28 | 29 | #### Gitee Feature 30 | 31 | 1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md 32 | 2. Gitee blog [blog.gitee.com](https://blog.gitee.com) 33 | 3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) 34 | 4. The most valuable open source project [GVP](https://gitee.com/gvp) 35 | 5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) 36 | 6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-boot-online-exam 2 | 3 | > 在线Demo预览,http://129.211.88.191 ,账户分别是admin、teacher、student,密码是admin123 4 | 5 | #### 介绍 6 | 基于springboot的在线考试系统 7 | 8 | #### 功能简介 9 | 10 | + 支持单选题、多选题、判断题 11 | + 支持学生(student)、教师(teacher)、管理员(admin)三种角色 12 | + 学生:参加考试和查看我的考试 13 | + 教师:学生的所有权限+创建/编辑题目+创建/编辑考试 14 | + 管理员:教师的所有权限+管理用户 15 | 16 | #### 软件架构 17 | 18 | > 前后端分离,前段组件化,方便二次开发;后端 19 | 20 | + 后端采用SpringBoot+JPA++Swagger2+JWT校验,根据不同用户的权限返回给用户不同的数据 21 | + 后端采用Vue+AntDesign,组件化拆分,封装了很多年公共组件,方便维护和二次开发 22 | 23 | #### 使用教程 24 | 25 | + 1.下载代码 26 | ```shell 27 | git clone https://github.com/19920625lsg/spring-boot-online-exam.git 28 | ``` 29 | + 2.初始化数据库 30 | > 安装mysql的步骤这里省略,网上的教程很多。安装好mysql后,新建exam数据库,密码和`spring-boot-online-exam/backend/exam/src/main/resources/application.yml`的`password: xxxxxx`保持一致,然后导入`spring-boot-online-exam/doc/sql/exam.sql` 31 | + 3.启动后端 32 | > 打开`spring-boot-online-exam/backend/exam`这个Maven项目,可以在IDE里启动或者执行`mvn install`生成jar包启动 33 | + 4.启动前端 34 | + 进入到前端代码路径 `cd spring-boot-online-exam/frontend/exam/` 35 | + 安装依赖 `npm install` 36 | + 启动前端 `npm run serve` 37 | + 5.部署完毕,查看效果 38 | > 打开 http://localhost:8000 或者 http://本机ip:8000 即可查看演示效果 39 | 40 | #### 功能图示 41 | 42 | + 1.管理题目 43 | + 1.1 题目列表 44 | > ![题目查看](doc/images/question_list.png) 45 | + 1.2 题目创建 46 | > ![题目创建](doc/images/question_create.png) 47 | + 1.3 题目更新 48 | > ![题目更新](doc/images/question_update.png) 49 | + 2.考试管理 50 | + 2.1 考试列表 51 | > ![考试查看](doc/images/exam_list.png) 52 | + 2.2 考试创建 53 | > ![考试创建](doc/images/exam_create.png) 54 | + 2.3 考试更新(`还有点小bug,开发中`) 55 | > ![考试更新](doc/images/exam_update.png) 56 | + 3.我的考试 57 | + 3.1 参加考试 58 | > 在"考试列表"模块点击自己想参加的考试卡片即可 59 | > ![参加考试1](doc/images/exam_join.png) 60 | > ![参加考试2](doc/images/exam_join2.png) 61 | + 3.2 考试记录查看 62 | > ![考试记录查看](doc/images/exam_detail.png) 63 | 64 | #### 参与贡献 65 | 66 | 1. Fork 本仓库 67 | 2. 新建 exam_xxx 分支 68 | 3. 提交代码 69 | 4. 新建 Pull Request 70 | 71 | -------------------------------------------------------------------------------- /backend/exam/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | src/main/resources/public 4 | target/ -------------------------------------------------------------------------------- /backend/exam/README.md: -------------------------------------------------------------------------------- 1 | # 在线考试系统的后端实现 2 | 3 | ## 拦截器注入application.yml中的属性 4 | 5 | > 拦截器执行在自动bean初始化之前会导致拦截器中无法注入配置属性,按照下面的步骤即可正常注入 6 | 7 | + 给拦截器加`@Component`注解 8 | 9 | ![拦截器注入配置文件属性](images/拦截器注入配置文件属性.png) 10 | 11 | + 在配置类中用`@Autowired`注入 12 | 13 | ![拦截器注入配置文件属性2](images/拦截器注入配置文件属性2.png) 14 | 15 | ## 代码调试的时候如何打印完整的SQL语句和参数 16 | 17 | > 主要是指application.yml的配置 18 | 19 | 参考博客[JPA打印SQL参数](https://mp.weixin.qq.com/s/zyTOdTwFhi2CwxCI9P1kQw) 20 | 21 | 22 | + 打印SQL语句 23 | ```yaml 24 | spring: 25 | jpa: 26 | # 调试的时候用,用于打印完成SQL语句(但是不打印参数),联合下面的logging.level一同打印最完整的SQL信息(语句+参数) 27 | show-sql: true 28 | ``` 29 | + 不打印SQL语句 30 | ```yaml 31 | spring: 32 | jpa: 33 | # 调试的时候用,用于打印完成SQL语句(但是不打印参数),联合下面的logging.level一同打印最完整的SQL信息(语句+参数) 34 | show-sql: false 35 | ``` 36 | + 打印SQL参数 37 | ```yaml 38 | # SQL语句打印(能打印参数,设置为trace是打印完整语句,默认我们就关掉吧) 39 | logging: 40 | level: 41 | org.hibernate.type.descriptor.sql.BasicBinder: trace 42 | ``` 43 | + 不打印SQL参数 44 | ```yaml 45 | # SQL语句打印(能打印参数,设置为trace是打印完整语句,默认我们就关掉吧) 46 | logging: 47 | level: 48 | org.hibernate.type.descriptor.sql.BasicBinder: off 49 | ``` -------------------------------------------------------------------------------- /backend/exam/images/拦截器注入配置文件属性.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/backend/exam/images/拦截器注入配置文件属性.png -------------------------------------------------------------------------------- /backend/exam/images/拦截器注入配置文件属性2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/backend/exam/images/拦截器注入配置文件属性2.png -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/ExamApplication.java: -------------------------------------------------------------------------------- 1 | package com.huawei.l00379880.exam; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ExamApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ExamApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/config/CORSConf.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 允许跨域访问 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-17 00:11 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.config; 8 | 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 14 | 15 | @Configuration 16 | @Slf4j 17 | public class CORSConf { 18 | 19 | @Bean 20 | public WebMvcConfigurer corsConfigurer() { 21 | return new WebMvcConfigurer() { 22 | @Override 23 | public void addCorsMappings(CorsRegistry registry) { 24 | log.info("初始化 CORSConfiguration 配置"); 25 | registry.addMapping("/**") 26 | .allowedHeaders("*") 27 | .allowedMethods("*") 28 | .allowedOrigins("*"); 29 | } 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/config/IntercepterConfig.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 拦截器配置 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-22 08:21 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.config; 8 | 9 | import com.huawei.l00379880.exam.interceptor.LoginInterceptor; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 14 | 15 | @Configuration 16 | public class IntercepterConfig implements WebMvcConfigurer { 17 | 18 | @Autowired 19 | private LoginInterceptor loginInterceptor; 20 | 21 | @Override 22 | public void addInterceptors(InterceptorRegistry registry) { 23 | // 拦截user下的api 24 | registry.addInterceptor(loginInterceptor).addPathPatterns("/user/**").addPathPatterns("/exam/**"); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/config/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created By Liang Shan Guang at 2019-05-14 08:20 3 | * Description : 项目自定义配置 4 | */ 5 | package com.huawei.l00379880.exam.config; -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/controller/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created By Liang Shan Guang at 2019-05-14 08:20 3 | * Description : 对外REST接口 4 | */ 5 | package com.huawei.l00379880.exam.controller; -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/dto/RegisterDTO.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 注册接口参数 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-16 23:40 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.dto; 8 | 9 | import lombok.Data; 10 | 11 | @Data 12 | public class RegisterDTO { 13 | private String email; 14 | private String password; 15 | private String password2; 16 | private String mobile; 17 | /** 18 | * 验证码 19 | */ 20 | private String captcha; 21 | } 22 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/dto/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created By Liang Shan Guang at 2019-05-16 23:38 3 | * Description : 用于接口数据传输 4 | */ 5 | package com.huawei.l00379880.exam.dto; -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/Action.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 前端的操作 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-26 12:31 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | import lombok.Data; 10 | 11 | import javax.persistence.Entity; 12 | import javax.persistence.GeneratedValue; 13 | import javax.persistence.Id; 14 | 15 | @Data 16 | @Entity 17 | public class Action { 18 | @Id 19 | @GeneratedValue 20 | private Integer actionId; 21 | 22 | private String actionName; 23 | 24 | private String actionDescription; 25 | 26 | private Boolean defaultCheck; 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/Exam.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试表,要有题目、总分数、时间限制、有效日期、创建者等字段 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:42 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | 8 | package com.huawei.l00379880.exam.entity; 9 | 10 | 11 | import com.fasterxml.jackson.annotation.JsonFormat; 12 | import lombok.Data; 13 | import org.hibernate.annotations.DynamicUpdate; 14 | 15 | import javax.persistence.Entity; 16 | import javax.persistence.Id; 17 | import java.util.Date; 18 | 19 | @Entity 20 | @Data 21 | @DynamicUpdate 22 | public class Exam { 23 | @Id 24 | private String examId; 25 | private String examName; 26 | private String examAvatar; 27 | private String examDescription; 28 | private String examQuestionIds; 29 | private String examQuestionIdsRadio; 30 | private String examQuestionIdsCheck; 31 | private String examQuestionIdsJudge; 32 | private Integer examScore; 33 | private Integer examScoreRadio; 34 | private Integer examScoreCheck; 35 | private Integer examScoreJudge; 36 | private String examCreatorId; 37 | private Integer examTimeLimit; 38 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 39 | private Date examStartDate; 40 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 41 | private Date examEndDate; 42 | /** 43 | * 创建时间, 设计表时设置了自动插入当前时间,无需在Java代码中设置了 44 | */ 45 | 46 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 47 | private Date createTime; 48 | 49 | /** 50 | * 更新时间,设计表时设置了自动插入当前时间,无需在Java代码中设置了。 51 | * 同时@DynamicUpdate注解可以时间当数据库数据变化时自动更新,无需人工维护 52 | */ 53 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 54 | private Date updateTime; 55 | } 56 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/ExamRecord.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 参加考试的记录,要有考试记录的id、参与者、参与时间、耗时、得分、得分级别(另建表) 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:43 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import com.fasterxml.jackson.annotation.JsonFormat; 11 | import lombok.Data; 12 | 13 | import javax.persistence.Entity; 14 | import javax.persistence.Id; 15 | import java.util.Date; 16 | 17 | @Data 18 | @Entity 19 | public class ExamRecord { 20 | /** 21 | * 主键 22 | */ 23 | @Id 24 | private String examRecordId; 25 | /** 26 | * 参与的考试的id 27 | */ 28 | private String examId; 29 | 30 | /** 31 | * 考生作答地每个题目的选项(题目和题目之间用_分隔,题目有多个选项地话用-分隔),用于查看考试详情 32 | */ 33 | private String answerOptionIds; 34 | 35 | /** 36 | * 参与者,即user的id 37 | */ 38 | private String examJoinerId; 39 | /** 40 | * 参加考试的日期 41 | */ 42 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 43 | private Date examJoinDate; 44 | /** 45 | * 考试耗时(秒) 46 | */ 47 | private Integer examTimeCost; 48 | /** 49 | * 考试得分 50 | */ 51 | private Integer examJoinScore; 52 | /** 53 | * 考试得分水平 54 | */ 55 | private Integer examResultLevel; 56 | } 57 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/ExamRecordLevel.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试得分级别 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:44 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import lombok.Data; 11 | 12 | import javax.persistence.Entity; 13 | import javax.persistence.GeneratedValue; 14 | import javax.persistence.Id; 15 | 16 | @Data 17 | @Entity 18 | public class ExamRecordLevel { 19 | @Id 20 | @GeneratedValue 21 | private Integer examRecordLevelId; 22 | private String examRecordLevelName; 23 | private String examRecordLevelDescription; 24 | } 25 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/Page.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 前端页面实体类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-26 12:30 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | import lombok.Data; 10 | 11 | import javax.persistence.Entity; 12 | import javax.persistence.GeneratedValue; 13 | import javax.persistence.Id; 14 | 15 | @Data 16 | @Entity 17 | public class Page { 18 | @Id 19 | @GeneratedValue 20 | private Integer pageId; 21 | 22 | private String pageName; 23 | 24 | private String pageDescription; 25 | 26 | private String actionIds; 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/Question.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试题目表 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:46 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import com.fasterxml.jackson.annotation.JsonFormat; 11 | import lombok.Data; 12 | import org.hibernate.annotations.DynamicUpdate; 13 | 14 | import javax.persistence.Entity; 15 | import javax.persistence.Id; 16 | import java.util.Date; 17 | 18 | @Data 19 | @Entity 20 | @DynamicUpdate 21 | public class Question { 22 | @Id 23 | private String questionId; 24 | private String questionName; 25 | private Integer questionScore; 26 | private String questionCreatorId; 27 | private Integer questionLevelId; 28 | private Integer questionTypeId; 29 | private Integer questionCategoryId; 30 | private String questionDescription; 31 | private String questionOptionIds; 32 | private String questionAnswerOptionIds; 33 | /** 34 | * 创建时间, 设计表时设置了自动插入当前时间,无需在Java代码中设置了 35 | */ 36 | 37 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 38 | private Date createTime; 39 | 40 | /** 41 | * 更新时间,设计表时设置了自动插入当前时间,无需在Java代码中设置了。 42 | * 同时@DynamicUpdate注解可以时间当数据库数据变化时自动更新,无需人工维护 43 | */ 44 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 45 | private Date updateTime; 46 | } 47 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/QuestionCategory.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 题目的类别表,从内容角度划分,比如数学、语文、英语等 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:46 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import com.fasterxml.jackson.annotation.JsonProperty; 11 | import lombok.Data; 12 | 13 | import javax.persistence.Entity; 14 | import javax.persistence.GeneratedValue; 15 | import javax.persistence.Id; 16 | 17 | @Data 18 | @Entity 19 | public class QuestionCategory { 20 | 21 | @Id 22 | @GeneratedValue 23 | @JsonProperty("id") 24 | private Integer questionCategoryId; 25 | 26 | @JsonProperty("name") 27 | private String questionCategoryName; 28 | 29 | @JsonProperty("description") 30 | private String questionCategoryDescription; 31 | } 32 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/QuestionLevel.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 题目难度等级,比如难、中、易等 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:47 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import com.fasterxml.jackson.annotation.JsonProperty; 11 | import lombok.Data; 12 | 13 | import javax.persistence.Entity; 14 | import javax.persistence.GeneratedValue; 15 | import javax.persistence.Id; 16 | 17 | @Entity 18 | @Data 19 | public class QuestionLevel { 20 | @Id 21 | @GeneratedValue 22 | @JsonProperty("id") 23 | private Integer questionLevelId; 24 | 25 | @JsonProperty("name") 26 | private String questionLevelName; 27 | 28 | @JsonProperty("description") 29 | private String questionLevelDescription; 30 | } 31 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/QuestionOption.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 问题的选项,适用于单选、多选和判断 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:48 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import lombok.Data; 11 | 12 | import javax.persistence.Entity; 13 | import javax.persistence.Id; 14 | 15 | @Data 16 | @Entity 17 | public class QuestionOption { 18 | @Id 19 | private String questionOptionId; 20 | private String questionOptionContent; 21 | private String questionOptionDescription; 22 | } 23 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/QuestionType.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 题目类型表,从功能角度划分,比如单选、多选、判断等 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:48 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import com.fasterxml.jackson.annotation.JsonProperty; 11 | import lombok.Data; 12 | 13 | import javax.persistence.Entity; 14 | import javax.persistence.GeneratedValue; 15 | import javax.persistence.Id; 16 | 17 | @Data 18 | @Entity 19 | public class QuestionType { 20 | @Id 21 | @GeneratedValue 22 | @JsonProperty("id") 23 | private Integer questionTypeId; 24 | 25 | @JsonProperty("name") 26 | private String questionTypeName; 27 | 28 | @JsonProperty("description") 29 | private String questionTypeDescription; 30 | } 31 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/Role.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 用户角色表 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:49 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import lombok.Data; 11 | 12 | import javax.persistence.Entity; 13 | import javax.persistence.GeneratedValue; 14 | import javax.persistence.Id; 15 | 16 | @Data 17 | @Entity 18 | public class Role { 19 | @Id 20 | @GeneratedValue 21 | private Integer roleId; 22 | private String roleName; 23 | private String roleDescription; 24 | private String roleDetail; 25 | /** 26 | * 角色所能访问的页面的主键集合(用-连接起来字符串) 27 | */ 28 | private String rolePageIds; 29 | } 30 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/User.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 用户表 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019/5/14 07:49 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.entity; 8 | 9 | 10 | import com.fasterxml.jackson.annotation.JsonFormat; 11 | import lombok.Data; 12 | import org.hibernate.annotations.DynamicUpdate; 13 | 14 | import javax.persistence.Entity; 15 | import javax.persistence.Id; 16 | import java.util.Date; 17 | 18 | @Data 19 | @Entity 20 | @DynamicUpdate 21 | public class User { 22 | @Id 23 | private String userId; 24 | private String userUsername; 25 | private String userNickname; 26 | private String userPassword; 27 | private Integer userRoleId; 28 | private String userAvatar; 29 | private String userDescription; 30 | private String userEmail; 31 | private String userPhone; 32 | /** 33 | * 创建时间, 设计表时设置了自动插入当前时间,无需在Java代码中设置了 34 | */ 35 | 36 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 37 | private Date createTime; 38 | 39 | /** 40 | * 更新时间,设计表时设置了自动插入当前时间,无需在Java代码中设置了。 41 | * 同时@DynamicUpdate注解可以时间当数据库数据变化时自动更新,无需人工维护 42 | */ 43 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 44 | private Date updateTime; 45 | } 46 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/entity/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created By Liang Shan Guang at 2019-05-14 00:34 3 | * Description : 数据库实体类 4 | */ 5 | package com.huawei.l00379880.exam.entity; -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/enums/LoginTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.huawei.l00379880.exam.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 用户账号类型的枚举 7 | * @author liangshanguang 8 | */ 9 | @Getter 10 | public enum LoginTypeEnum { 11 | /** 12 | * 用户的账号类型,1代表用户名,2代表邮箱 13 | */ 14 | USERNAME(1, "用户名"), 15 | EMAIL(2, "邮箱"); 16 | 17 | 18 | LoginTypeEnum(Integer type, String name) { 19 | this.type = type; 20 | this.name = name; 21 | } 22 | 23 | private Integer type; 24 | private String name; 25 | } 26 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/enums/QuestionEnum.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 问题类型的的枚举 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-18 12:00 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.enums; 8 | 9 | import lombok.Getter; 10 | 11 | @Getter 12 | public enum QuestionEnum { 13 | 14 | /** 15 | * 问题类型 16 | */ 17 | RADIO(1, "单选题"), 18 | CHECK(2, "多选题"), 19 | JUDGE(3, "判断题"); 20 | 21 | 22 | QuestionEnum(Integer id, String role) { 23 | this.id = id; 24 | this.role = role; 25 | } 26 | 27 | private Integer id; 28 | private String role; 29 | } 30 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/enums/ResultEnum.java: -------------------------------------------------------------------------------- 1 | package com.huawei.l00379880.exam.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum ResultEnum { 7 | // 下面是本项目用到的所有错误码 8 | REGISTER_SUCCESS(0, "注册成功"), 9 | REGISTER_FAILED(-2, "注册失败"), 10 | LOGIN_SUCCESS(0, "登录成功"), 11 | LOGIN_FAILED(-1, "用户名或者密码错误"), 12 | GET_INFO_SUCCESS(0, "获取用户信息成功"), 13 | PARAM_ERR(1, "参数不正确"), 14 | PRODUCT_NOT_EXIST(10, "用户不存在"), 15 | PRODUCT_STOCK_ERR(11, "考试信息异常"), 16 | ORDER_STATUS_ERR(14, "考试状态异常"), 17 | ORDER_UPDATE_ERR(15, "考试更新异常"), 18 | ORDER_DETAIL_EMPTY(16, "用户详情为空"); 19 | 20 | ResultEnum(Integer code, String message) { 21 | this.code = code; 22 | this.message = message; 23 | } 24 | 25 | private Integer code; 26 | private String message; 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/enums/RoleEnum.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 用户角色的枚举 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-18 12:00 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.enums; 8 | 9 | import lombok.Getter; 10 | 11 | @Getter 12 | public enum RoleEnum { 13 | 14 | /** 15 | * 用户角色,和数据库里面的role表相对应 16 | */ 17 | ADMIN(1, "管理员"), 18 | TEACHER(2, "教师"), 19 | STUDENT(3, "学生"); 20 | 21 | 22 | RoleEnum(Integer id, String role) { 23 | this.id = id; 24 | this.role = role; 25 | } 26 | 27 | private Integer id; 28 | private String role; 29 | } 30 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/exception/ExamException.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-17 07:50 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.exception; 8 | 9 | import com.huawei.l00379880.exam.enums.ResultEnum; 10 | import lombok.Getter; 11 | 12 | @Getter 13 | public class ExamException extends RuntimeException { 14 | private Integer code; 15 | 16 | public ExamException(ResultEnum resultEnum) { 17 | super(resultEnum.getMessage()); 18 | this.code = resultEnum.getCode(); 19 | } 20 | 21 | public ExamException( Integer code, String message) { 22 | super(message); 23 | this.code = code; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/exception/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created By Liang Shan Guang at 2019-05-17 07:51 3 | * Description : 自定义异常 4 | */ 5 | package com.huawei.l00379880.exam.exception; -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/qo/DownloadQo.java: -------------------------------------------------------------------------------- 1 | package com.huawei.l00379880.exam.qo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /*********************************************************** 8 | * @note : 要下载的文件的路径 9 | * @author : l00379880 梁山广 10 | * @version : V1.0 at 2019/5/19 20:10 11 | ***********************************************************/ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class DownloadQo { 16 | String path; 17 | } 18 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/qo/LoginQo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 登录的查询参数 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-19 20:18 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.qo; 8 | 9 | import lombok.AllArgsConstructor; 10 | import lombok.Data; 11 | import lombok.NoArgsConstructor; 12 | 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class LoginQo { 17 | /** 18 | * 1表示用户名,2表示邮箱 19 | */ 20 | private Integer loginType; 21 | /** 22 | * 用户名/邮箱的字符串 23 | */ 24 | private String userInfo; 25 | /** 26 | * 用户密码 27 | */ 28 | private String password; 29 | } 30 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/qo/UploadModel.java: -------------------------------------------------------------------------------- 1 | package com.huawei.l00379880.exam.qo; 2 | 3 | /*********************************************************** 4 | * @Description : 文件传输的实体类 5 | * @author : l00379880 梁山广 6 | * @date : 2017/8/19 15:51 7 | * @version : V1.0 8 | ***********************************************************/ 9 | 10 | import lombok.AllArgsConstructor; 11 | import lombok.Data; 12 | import lombok.NoArgsConstructor; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class UploadModel { 19 | /** 20 | * 要保存的文件 21 | */ 22 | private MultipartFile[] files; 23 | /** 24 | * 文件要存储的文件夹 25 | */ 26 | private String dir; 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/qo/UploadModel2.java: -------------------------------------------------------------------------------- 1 | package com.huawei.l00379880.exam.qo; 2 | 3 | /*********************************************************** 4 | * @Description : 文件传输的实体类,支持单个文件 5 | * @author : l00379880 梁山广 6 | * @date : 2018/5/19 15:51 7 | * @version : V1.0 8 | ***********************************************************/ 9 | 10 | import lombok.AllArgsConstructor; 11 | import lombok.Data; 12 | import lombok.NoArgsConstructor; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class UploadModel2 { 19 | /** 20 | * 要保存的文件列表 21 | */ 22 | private MultipartFile file; 23 | /** 24 | * 文件要存储的文件夹 25 | */ 26 | private String dir; 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/qo/package-info.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @note : 用于请求参数的实体类(Query Object) 3 | * @author : l00379880 梁山广 4 | * @version : V1.0 at 2018/7/18 17:32 5 | ***********************************************************/ 6 | package com.huawei.l00379880.exam.qo; 7 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/ActionRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : Action的数据库操作类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-26 12:39 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.Action; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface ActionRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/ExamRecordLevelRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:24 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.ExamRecordLevel; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface ExamRecordLevelRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/ExamRecordRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:23 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.ExamRecord; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | import java.util.List; 13 | 14 | public interface ExamRecordRepository extends JpaRepository { 15 | /** 16 | * 获取指定用户参加过的所有考试 17 | * 18 | * @param userId 用户id 19 | * @return 用户参加过的所有考试 20 | */ 21 | List findByExamJoinerId(String userId); 22 | } 23 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/ExamRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:22 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.Exam; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface ExamRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/PageRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 页面的数据库操作类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-26 12:41 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.Page; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface PageRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/QuestionCategoryRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:25 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.QuestionCategory; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface QuestionCategoryRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/QuestionLevelRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:26 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.QuestionLevel; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface QuestionLevelRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/QuestionOptionRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:27 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.QuestionOption; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface QuestionOptionRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/QuestionRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:25 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.Question; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | import java.util.List; 13 | 14 | public interface QuestionRepository extends JpaRepository { 15 | List findByQuestionTypeId(Integer id); 16 | } 17 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/QuestionTypeRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:28 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.QuestionType; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface QuestionTypeRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 角色的数据库操作类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:29 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.Role; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface RoleRepository extends JpaRepository { 13 | } 14 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 用户表的操作类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-14 08:30 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.repository; 8 | 9 | import com.huawei.l00379880.exam.entity.User; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface UserRepository extends JpaRepository { 13 | /** 14 | * 根据用户名查找到合适的用户 15 | * 16 | * @param username 用户名 17 | * @return 唯一符合的用户(实际用户名字段已经在数据库设置unique了 , 肯定只会返回1条) 18 | */ 19 | User findByUserUsername(String username); 20 | 21 | /** 22 | * 根据用户邮箱查找合适用户 23 | * 24 | * @param email 邮箱 25 | * @return 唯一符合的用户(实际邮箱字段已经在数据库设置unique了 , 肯定只会返回1条) 26 | */ 27 | User findByUserEmail(String email); 28 | } 29 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/repository/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created By Liang Shan Guang at 2019-05-14 08:20 3 | * Description : 数据库操作接口 4 | */ 5 | package com.huawei.l00379880.exam.repository; -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/service/UserService.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 用户接口 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-17 08:02 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.service; 8 | 9 | import com.huawei.l00379880.exam.dto.RegisterDTO; 10 | import com.huawei.l00379880.exam.entity.User; 11 | import com.huawei.l00379880.exam.qo.LoginQo; 12 | import com.huawei.l00379880.exam.vo.UserInfoVo; 13 | import com.huawei.l00379880.exam.vo.UserVo; 14 | 15 | public interface UserService { 16 | /** 17 | * 注册 18 | * 19 | * @param registerDTO 注册参数 20 | * @return 注册成功后的用户信息 21 | */ 22 | User register(RegisterDTO registerDTO); 23 | 24 | /** 25 | * 登录接口,登录成功返回token 26 | * 27 | * @param loginQo 登录参数 28 | * @return 成功返回token,失败返回null 29 | */ 30 | String login(LoginQo loginQo); 31 | 32 | /** 33 | * 根据用户id获取用户信息 34 | * 35 | * @return 用户实体 36 | */ 37 | UserVo getUserInfo(String userId); 38 | 39 | /** 40 | * 获取用户详细信息(主要是权限相关的) 41 | * @param userId 用户的id 42 | * @return 用户信息组装的实体 43 | */ 44 | UserInfoVo getInfo(String userId); 45 | } 46 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/service/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created By Liang Shan Guang at 2019-05-14 08:20 3 | * Description : 服务接口与实现 4 | */ 5 | package com.huawei.l00379880.exam.service; -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/utils/JwtUtils.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : JWT工具类:JWT生产token和校验token的方法 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-21 08:15 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.utils; 8 | 9 | import com.huawei.l00379880.exam.entity.User; 10 | import io.jsonwebtoken.Claims; 11 | import io.jsonwebtoken.Jwts; 12 | import io.jsonwebtoken.SignatureAlgorithm; 13 | 14 | import java.util.Date; 15 | 16 | public class JwtUtils { 17 | /** 18 | * 构建token的主题 19 | */ 20 | private static final String SUBJECT = "lsg_exam"; 21 | /** 22 | * 过期时间为1天 23 | */ 24 | private static final long EXPIRE = 1000 * 60 * 60 * 24; 25 | 26 | private static final String APP_SECRET = "liangshanguang"; 27 | 28 | public static String genJsonWebToken(User user) { 29 | if (user == null || user.getUserId() == null || user.getUserUsername() == null || user.getUserAvatar() == null) { 30 | return null; 31 | } 32 | return Jwts.builder().setSubject(SUBJECT) 33 | // 下面3行设置token中间字段,携带用户的信息 34 | .claim("id", user.getUserId()) 35 | .claim("username", user.getUserUsername()) 36 | .claim("avatar", user.getUserAvatar()) 37 | .setIssuedAt(new Date()) 38 | // 设置过期时间 39 | .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) 40 | .signWith(SignatureAlgorithm.HS256, APP_SECRET) 41 | // 生成的结果字符串太长,这里压缩下 42 | .compact(); 43 | } 44 | 45 | /** 46 | * 校验token 47 | * 48 | * @param token 生成的额token 49 | * @return 解析出的信息 50 | */ 51 | public static Claims checkJWT(String token) { 52 | try { 53 | return Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token).getBody(); 54 | } catch (Exception e) { 55 | // 篡改token会导致校验失败,走到异常分支,这里返回null 56 | return null; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/utils/ResultVOUtil.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 成功或失败时的消息返回 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-17 07:43 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.utils; 8 | 9 | import com.huawei.l00379880.exam.vo.ResultVO; 10 | 11 | public class ResultVOUtil { 12 | 13 | public static ResultVO success(Integer code, String msg, Object object) { 14 | return new ResultVO(code, msg, object); 15 | } 16 | 17 | public static ResultVO success(Object object) { 18 | return new ResultVO(0, "成功", object); 19 | } 20 | 21 | public static ResultVO success() { 22 | return new ResultVO(0, "成功", null); 23 | } 24 | 25 | 26 | public static ResultVO error(Integer code, String msg) { 27 | return new ResultVO(code, msg, null); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/utils/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created By Liang Shan Guang at 2019-05-14 08:19 3 | * Description : 工具类, 大部分工具都可以在Hutool中找到 4 | */ 5 | package com.huawei.l00379880.exam.utils; -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ActionVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : Action的前端展示类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-26 13:50 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class ActionVo { 14 | @JsonProperty("action") 15 | private String actionName; 16 | 17 | @JsonProperty("describe") 18 | private String actionDescription; 19 | 20 | @JsonProperty("defaultCheck") 21 | private Boolean defaultCheck; 22 | } 23 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ExamCardVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试卡片列表 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-23 19:30 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class ExamCardVo { 14 | @JsonProperty("id") 15 | private String examId; 16 | @JsonProperty("title") 17 | private String examName; 18 | @JsonProperty("avatar") 19 | private String examAvatar; 20 | @JsonProperty("content") 21 | private String examDescription; 22 | @JsonProperty("score") 23 | private Integer examScore; 24 | /** 25 | * 考试限制的时间,单位为分钟 26 | */ 27 | @JsonProperty("elapse") 28 | private Integer examTimeLimit; 29 | } 30 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ExamCreateVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试的前端展示类。examCreatorId可从token中获取 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-17 08:14 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | import java.util.List; 13 | 14 | @Data 15 | public class ExamCreateVo { 16 | 17 | @JsonProperty("name") 18 | private String examName; 19 | 20 | @JsonProperty("avatar") 21 | private String examAvatar; 22 | 23 | @JsonProperty("desc") 24 | private String examDescription; 25 | 26 | /** 27 | * 考试时长,单位分钟 28 | */ 29 | @JsonProperty("elapse") 30 | private Integer examTimeLimit; 31 | 32 | 33 | /** 34 | * 单选题 35 | */ 36 | private List radios; 37 | 38 | /** 39 | * 多选题 40 | */ 41 | private List checks; 42 | 43 | /** 44 | * 判断题 45 | */ 46 | private List judges; 47 | 48 | /** 49 | * 单选题的分数 50 | */ 51 | @JsonProperty("radioScore") 52 | private Integer examScoreRadio; 53 | 54 | /** 55 | * 多选题的分数 56 | */ 57 | @JsonProperty("checkScore") 58 | private Integer examScoreCheck; 59 | 60 | /** 61 | * 判断题每题的分数 62 | */ 63 | @JsonProperty("judgeScore") 64 | private Integer examScoreJudge; 65 | } 66 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ExamDetailVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试详情的实体类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-24 08:14 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.huawei.l00379880.exam.entity.Exam; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class ExamDetailVo { 14 | /** 15 | * 考试的基本信息对象 16 | */ 17 | private Exam exam; 18 | 19 | /** 20 | * 单选题的id数组 21 | */ 22 | private String[] radioIds; 23 | 24 | /** 25 | * 多选题的id数组 26 | */ 27 | private String[] checkIds; 28 | 29 | /** 30 | * 判断题的id数组 31 | */ 32 | private String[] judgeIds; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ExamPageVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试列表获取 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-22 17:00 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | import java.util.List; 13 | 14 | @Data 15 | public class ExamPageVo { 16 | /** 17 | * 分页时每个分页的大小 18 | */ 19 | private Integer pageSize; 20 | 21 | /** 22 | * 当前是在第几页,注意要比前端传过来地小1 23 | */ 24 | private Integer pageNo; 25 | 26 | /** 27 | * 一共有多少条符合条件的记录 28 | */ 29 | private Long totalCount; 30 | 31 | /** 32 | * 一共有多少页 33 | */ 34 | private Integer totalPage; 35 | 36 | /** 37 | * 当前页的详细数据 38 | */ 39 | @JsonProperty("data") 40 | private List examVoList; 41 | } 42 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ExamQuestionSelectVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试中的题目 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-17 23:10 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class ExamQuestionSelectVo { 14 | @JsonProperty("id") 15 | private String questionId; 16 | 17 | @JsonProperty("name") 18 | private String questionName; 19 | 20 | /** 21 | * 这个问题是否被选为了考试中的题目.默认是false,经过前端修改后可能会变成true, 22 | * 传回来用于创建问题 23 | */ 24 | @JsonProperty("checked") 25 | private Boolean checked = false; 26 | } 27 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ExamQuestionTypeVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 按照单选、多选和判断题返回对应的问题列表 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-23 11:00 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | import java.util.List; 13 | 14 | @Data 15 | public class ExamQuestionTypeVo { 16 | @JsonProperty("radios") 17 | private List examQuestionSelectVoRadioList; 18 | 19 | @JsonProperty("checks") 20 | private List examQuestionSelectVoCheckList; 21 | 22 | @JsonProperty("judges") 23 | private List examQuestionSelectVoJudgeList; 24 | } 25 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ExamRecordVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试记录VO 3 | * @author : 梁山广(Liang Shan Guang) 4 | * @date : 2019/10/25 7:42 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.huawei.l00379880.exam.entity.Exam; 10 | import com.huawei.l00379880.exam.entity.ExamRecord; 11 | import com.huawei.l00379880.exam.entity.User; 12 | import lombok.Data; 13 | 14 | @Data 15 | public class ExamRecordVo { 16 | /** 17 | * 当前考试记录对应的考试 18 | */ 19 | private Exam exam; 20 | 21 | /** 22 | * 当前考试对应的内容 23 | */ 24 | private ExamRecord examRecord; 25 | 26 | /** 27 | * 参加考试的用户信息 28 | */ 29 | private User user; 30 | } 31 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/JsonData.java: -------------------------------------------------------------------------------- 1 | package com.huawei.l00379880.exam.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 功能描述:工具类 11 | * 12 | * @author liangshanguang 13 | * @date 2019-05-22 08:11 14 | */ 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class JsonData implements Serializable { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | /** 23 | * 状态码 0 表示成功,1表示处理中,-1表示失败 24 | */ 25 | private Integer code; 26 | /** 27 | * 数据 28 | */ 29 | private Object data; 30 | /** 31 | * 描述 32 | */ 33 | private String msg; 34 | 35 | /** 36 | * 成功,传入数据 37 | */ 38 | public static JsonData buildSuccess() { 39 | return new JsonData(0, null, null); 40 | } 41 | 42 | /** 43 | * 成功,传入数据 44 | */ 45 | public static JsonData buildSuccess(Object data) { 46 | return new JsonData(0, data, null); 47 | } 48 | 49 | /** 50 | * 失败,传入描述信息 51 | */ 52 | public static JsonData buildError(String msg) { 53 | return new JsonData(-1, null, msg); 54 | } 55 | 56 | /** 57 | * 失败,传入描述信息,状态码 58 | */ 59 | public static JsonData buildError(String msg, Integer code) { 60 | return new JsonData(code, null, msg); 61 | } 62 | 63 | /** 64 | * 成功,传入数据,及描述信息 65 | * 66 | * @param data 自定义的数据 67 | * @param msg 返回给前端的消息 68 | * @return 给前端的消息体 69 | */ 70 | public static JsonData buildSuccess(Object data, String msg) { 71 | return new JsonData(0, data, msg); 72 | } 73 | 74 | /** 75 | * 成功,传入数据,及状态码 76 | */ 77 | public static JsonData buildSuccess(Object data, int code) { 78 | return new JsonData(code, data, null); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/PageVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : Action的前端展示类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-26 13:46 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | import java.util.List; 13 | 14 | @Data 15 | public class PageVo { 16 | @JsonProperty("actionEntitySet") 17 | private List actionVoList; 18 | 19 | @JsonProperty("permissionId") 20 | private String pageName; 21 | 22 | @JsonProperty("permissionName") 23 | private String pageDescription; 24 | } 25 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/QuestionCreateSimplifyVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 问题创建的实体类,简化了下 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-02 13:26 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | import java.util.List; 13 | 14 | @Data 15 | public class QuestionCreateSimplifyVo { 16 | /** 17 | * 题目名称 18 | */ 19 | @JsonProperty("name") 20 | private String questionName; 21 | 22 | @JsonProperty("desc") 23 | private String questionDescription; 24 | 25 | /** 26 | * 问题的难度的id 27 | */ 28 | @JsonProperty("level") 29 | private Integer questionLevelId; 30 | 31 | /** 32 | * 问题的类型(单选、多选、判断等) 33 | */ 34 | @JsonProperty("type") 35 | private Integer questionTypeId; 36 | 37 | /** 38 | * 题目的类别表,从内容角度划分,比如数学、语文、英语等 39 | */ 40 | @JsonProperty("category") 41 | private Integer questionCategoryId; 42 | 43 | /** 44 | * 创建选项 里添加的内容 45 | */ 46 | @JsonProperty("option") 47 | private String option; 48 | 49 | /** 50 | * 问题的选项列表,带上了是否是答案的true和false 51 | */ 52 | @JsonProperty("options") 53 | private List questionOptionCreateVoList; 54 | } 55 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/QuestionCreateVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 问题创建的实体类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-02 13:26 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | import java.util.List; 13 | 14 | @Data 15 | public class QuestionCreateVo { 16 | /** 17 | * 题目名称 18 | */ 19 | @JsonProperty("name") 20 | private String questionName; 21 | 22 | @JsonProperty("desc") 23 | private String questionDescription; 24 | 25 | /** 26 | * 题目的分数,默认值是5 27 | */ 28 | @JsonProperty("score") 29 | private Integer questionScore = 5; 30 | 31 | /** 32 | * 题目的创建者的id,从token中解析得到 33 | */ 34 | @JsonProperty("creator") 35 | private String questionCreatorId; 36 | 37 | /** 38 | * 问题的难度的id 39 | */ 40 | @JsonProperty("level") 41 | private Integer questionLevelId; 42 | 43 | /** 44 | * 问题的类型(单选、多选、判断等) 45 | */ 46 | @JsonProperty("type") 47 | private Integer questionTypeId; 48 | 49 | /** 50 | * 题目的类别表,从内容角度划分,比如数学、语文、英语等 51 | */ 52 | @JsonProperty("category") 53 | private Integer questionCategoryId; 54 | 55 | /** 56 | * 问题的选项列表,带上了是否是答案的true和false 57 | */ 58 | @JsonProperty("options") 59 | private List questionOptionCreateVoList; 60 | } 61 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/QuestionDetailVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 问题详情的实体类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-10-20 09:51 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.huawei.l00379880.exam.entity.QuestionOption; 10 | import lombok.Data; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | @Data 16 | public class QuestionDetailVo { 17 | /** 18 | * 问题的id 19 | */ 20 | private String id; 21 | 22 | /** 23 | * 考试题目 24 | */ 25 | private String name; 26 | 27 | /** 28 | * 考试描述 29 | */ 30 | private String description; 31 | /** 32 | * 问题的类型 33 | */ 34 | private String type; 35 | /** 36 | * 问题的选项 37 | */ 38 | private List options; 39 | /** 40 | * 问题的答案,选项的id组成的数组 41 | */ 42 | private List answers = new ArrayList<>(); 43 | } 44 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/QuestionOptionCreateVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 问题选项的外层对象 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-02 20:23 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class QuestionOptionCreateVo { 14 | 15 | /** 16 | * 问题的内容 17 | */ 18 | @JsonProperty("content") 19 | private String questionOptionContent; 20 | 21 | /** 22 | * 当前的选项是否是问题大答案 23 | */ 24 | @JsonProperty("answer") 25 | private Boolean answer = false; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/QuestionOptionVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 问题选项的自定义实体类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-01 09:45 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class QuestionOptionVo { 14 | @JsonProperty("id") 15 | private String questionOptionId; 16 | 17 | @JsonProperty("content") 18 | private String questionOptionContent; 19 | 20 | @JsonProperty("answer") 21 | private Boolean answer = false; 22 | 23 | @JsonProperty("description") 24 | private String questionOptionDescription; 25 | } 26 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/QuestionPageVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 问题列表反馈给前端的对象 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-28 22:09 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | import java.util.List; 13 | 14 | @Data 15 | public class QuestionPageVo { 16 | 17 | /** 18 | * 分页时每个分页的大小 19 | */ 20 | private Integer pageSize; 21 | 22 | /** 23 | * 当前是在第几页,注意要比前端传过来地小1 24 | */ 25 | private Integer pageNo; 26 | 27 | /** 28 | * 一共有多少条符合条件的记录 29 | */ 30 | private Long totalCount; 31 | 32 | /** 33 | * 一共有多少页 34 | */ 35 | private Integer totalPage; 36 | 37 | /** 38 | * 当前页的详细数据 39 | */ 40 | @JsonProperty("data") 41 | private List questionVoList; 42 | } 43 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/QuestionSelectionVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 前端创建问题时的下拉列表选择 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-06-03 07:35 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import com.huawei.l00379880.exam.entity.QuestionCategory; 11 | import com.huawei.l00379880.exam.entity.QuestionLevel; 12 | import com.huawei.l00379880.exam.entity.QuestionType; 13 | import lombok.Data; 14 | 15 | import java.util.List; 16 | 17 | @Data 18 | public class QuestionSelectionVo { 19 | @JsonProperty("types") 20 | private List questionTypeList; 21 | 22 | @JsonProperty("categories") 23 | private List questionCategoryList; 24 | 25 | @JsonProperty("levels") 26 | private List questionLevelList; 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/QuestionVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试问题的对外封装类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-28 08:17 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonFormat; 10 | import com.fasterxml.jackson.annotation.JsonProperty; 11 | import lombok.Data; 12 | 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | @Data 17 | public class QuestionVo { 18 | @JsonProperty("id") 19 | private String questionId; 20 | 21 | @JsonProperty("name") 22 | private String questionName; 23 | 24 | @JsonProperty("score") 25 | private Integer questionScore; 26 | 27 | /** 28 | * 根据questionCreatorId查询创建人 29 | */ 30 | @JsonProperty("creator") 31 | private String questionCreator; 32 | 33 | /** 34 | * 根据questionLevelId查询问题难度 35 | */ 36 | @JsonProperty("level") 37 | private String questionLevel; 38 | 39 | /** 40 | * 问题类型,根据questionTypeId获取 41 | */ 42 | @JsonProperty("type") 43 | private String questionType; 44 | 45 | /** 46 | * 问题分类,根据questionCategoryId获得 47 | */ 48 | @JsonProperty("category") 49 | private String questionCategory; 50 | 51 | 52 | @JsonProperty("description") 53 | private String questionDescription; 54 | 55 | /** 56 | * 问题选项列表,从questionOptionIds获得,副院长哦自己额外给isAnswer赋值 57 | */ 58 | @JsonProperty("options") 59 | private List questionOptionVoList; 60 | 61 | 62 | /** 63 | * 更新时间,设计表时设置了自动插入当前时间,无需在Java代码中设置了。 64 | * 同时@DynamicUpdate注解可以时间当数据库数据变化时自动更新,无需人工维护 65 | */ 66 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 67 | private Date updateTime; 68 | } 69 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/RecordDetailVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 考试记录详情的VO 3 | * @author : 梁山广(Liang Shan Guang) 4 | * @date : 2019/10/27 15:37 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.huawei.l00379880.exam.entity.ExamRecord; 10 | import lombok.Data; 11 | 12 | import java.util.HashMap; 13 | import java.util.List; 14 | 15 | @Data 16 | public class RecordDetailVo { 17 | /** 18 | * 含有考试记录原始信息的对象 19 | */ 20 | private ExamRecord examRecord; 21 | /** 22 | * 用户此次考试的作答信息, 键是题目的id,值是选项id的列表 23 | */ 24 | private HashMap> answersMap; 25 | 26 | /** 27 | * 用户每题作答结果的Map,键是问题的id,值是用户这题是否回答正确,True or False 28 | */ 29 | private HashMap resultsMap; 30 | 31 | /** 32 | * 正确答案,键是题目的id,值是正确答案的id组成的列表 33 | */ 34 | private HashMap> answersRightMap; 35 | } 36 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/ResultVO.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 响应结果的通用类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-17 07:42 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonInclude; 10 | import lombok.Data; 11 | 12 | @Data 13 | @JsonInclude(JsonInclude.Include.NON_NULL) // 避免返回NULL的字段 14 | public class ResultVO { 15 | 16 | 17 | public ResultVO(Integer code, String msg, T data) { 18 | this.code = code; 19 | this.msg = msg; 20 | this.data = data; 21 | } 22 | 23 | public ResultVO() { 24 | } 25 | 26 | /** 27 | * 错误码 28 | */ 29 | private Integer code; 30 | 31 | /** 32 | * 提示信息 33 | */ 34 | private String msg = ""; 35 | 36 | /** 37 | * 具体内容 38 | */ 39 | private T data; 40 | } 41 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/RoleVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 角色的实体类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-26 13:27 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | import java.util.List; 13 | 14 | @Data 15 | public class RoleVo { 16 | @JsonProperty("id") 17 | private String roleName; 18 | 19 | @JsonProperty("name") 20 | private String roleDescription; 21 | 22 | @JsonProperty("describe") 23 | private String roleDetail; 24 | 25 | @JsonProperty("permissions") 26 | private List pageVoList; 27 | } 28 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/UserInfoVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 用户信息的前端展示类 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-26 12:59 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class UserInfoVo { 14 | 15 | @JsonProperty("id") 16 | private String userId; 17 | 18 | @JsonProperty("avatar") 19 | private String userAvatar; 20 | 21 | @JsonProperty("name") 22 | private String userNickname; 23 | 24 | @JsonProperty("username") 25 | private String userUsername; 26 | 27 | /** 28 | * 密码不用拷贝,免得泄露信息 29 | */ 30 | private String password = ""; 31 | 32 | @JsonProperty("email") 33 | private String userEmail; 34 | 35 | @JsonProperty("telephone") 36 | private String userPhone; 37 | 38 | @JsonProperty("roleId") 39 | private String roleName; 40 | 41 | @JsonProperty("welcome") 42 | private String userDescription; 43 | 44 | @JsonProperty("role") 45 | private RoleVo roleVo; 46 | } 47 | -------------------------------------------------------------------------------- /backend/exam/src/main/java/com/huawei/l00379880/exam/vo/UserVo.java: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * @Description : 用户信息的展示类,通过JsonProperty来简化返回 3 | * @author : 梁山广(Laing Shan Guang) 4 | * @date : 2019-05-25 20:32 5 | * @email : liangshanguang2@gmail.com 6 | ***********************************************************/ 7 | package com.huawei.l00379880.exam.vo; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import lombok.Data; 11 | 12 | @Data 13 | public class UserVo { 14 | @JsonProperty("id") 15 | private String userId; 16 | 17 | @JsonProperty("username") 18 | private String userUsername; 19 | 20 | @JsonProperty("nickname") 21 | private String userNickname; 22 | 23 | @JsonProperty("role") 24 | private Integer userRoleId; 25 | 26 | @JsonProperty("avatar") 27 | private String userAvatar; 28 | 29 | @JsonProperty("description") 30 | private String userDescription; 31 | 32 | @JsonProperty("email") 33 | private String userEmail; 34 | 35 | @JsonProperty("phone") 36 | private String userPhone; 37 | } 38 | -------------------------------------------------------------------------------- /backend/exam/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | servlet: 3 | context-path: /api -------------------------------------------------------------------------------- /backend/exam/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | server: 2 | servlet: 3 | context-path: # 这里配置为空是因为生产环境法需要自己配置nginx的转发,nginx可以参考我的配置doc/deploy/nginx.conf -------------------------------------------------------------------------------- /backend/exam/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | ip: 2 | base: 127.0.0.1 3 | 4 | user: 5 | default: 6 | # 用户名的默认前缀 7 | username: user 8 | # 用户的默认头像 9 | avatar: http://d.lanrentuku.com/down/png/1904/business_avatar/8_avatar_2754583.png 10 | 11 | server: 12 | port: 9527 # 这个端口要和vue项目里vue.config.js里的devServer里面配置的相同 13 | 14 | spring: 15 | application: 16 | name: service-product 17 | datasource: 18 | driver-class-name: com.mysql.cj.jdbc.Driver 19 | username: root 20 | password: xxxxxxxx # 改成自己的密码 21 | url: jdbc:mysql://${ip.base}:3306/exam?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai 22 | jpa: 23 | # 调试的时候用,用于打印完成SQL语句(但是不打印参数),联合下面的logging.level一同打印最完整的SQL信息(语句+参数) 24 | show-sql: false 25 | hibernate: 26 | ddl-auto: update 27 | servlet: 28 | multipart: 29 | max-file-size: 100MB # 最大支持文件大小 30 | max-request-size: 100MB # 最大支持请求大小 31 | profiles: 32 | active: dev # 开发时配dev, 生产时配prod 33 | 34 | # 拦截器相关的配置 35 | interceptors: 36 | # 不需要进行鉴权的接口地址,用逗号隔开 37 | auth-ignore-uris: ${server.servlet.context-path}/user/register,${server.servlet.context-path}/user/login 38 | 39 | # SQL语句打印(能打印参数,设置为trace是打印完整语句,默认我们就关掉吧) 40 | logging: 41 | level: 42 | org.hibernate.type.descriptor.sql.BasicBinder: off 43 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # 在线考试系统功能开发 2 | 3 | ## 用户基本功能 4 | 5 | + 注册 6 | + 登录 7 | + 个人信息(查看和修改,头像头像修改需要用到FastDFS相关的接口) 8 | 9 | ## 考试展示和参加 10 | 11 | + 单选、多选、判断都能正常展示和参加考试做题 12 | + 计时和计分的功能 13 | 14 | ## 管理 15 | 16 | + 试题管理(单选、多选、判断的录入) 17 | + 考试管理(设置有效时间和给试卷组题) 18 | -------------------------------------------------------------------------------- /doc/deploy/README.md: -------------------------------------------------------------------------------- 1 | # 在线考试系统的部署文档 -------------------------------------------------------------------------------- /doc/deploy/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | 6 | http { 7 | include mime.types; 8 | default_type application/octet-stream; 9 | sendfile on; 10 | 11 | keepalive_timeout 65; 12 | 13 | server { 14 | listen 80; 15 | server_name localhost; 16 | 17 | # 404页面跳转 18 | location / { 19 | try_files $uri /index.html; 20 | } 21 | 22 | # 静态资源目录,即vue打包后的dist里的静态资源 23 | root /usr/share/nginx/html/; 24 | index index.html index.htm; 25 | 26 | # 后端服务的配置 27 | location /api/ { 28 | proxy_redirect off; 29 | proxy_set_header Host $host; 30 | proxy_set_header X-Real-IP $remote_addr; 31 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 32 | # 后端服务地址 33 | proxy_pass http://localhost:9527/; 34 | } 35 | 36 | 37 | error_page 500 502 503 504 /50x.html; 38 | location = /50x.html { 39 | root html; 40 | } 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /doc/images/exam_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/exam_create.png -------------------------------------------------------------------------------- /doc/images/exam_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/exam_detail.png -------------------------------------------------------------------------------- /doc/images/exam_join.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/exam_join.png -------------------------------------------------------------------------------- /doc/images/exam_join2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/exam_join2.png -------------------------------------------------------------------------------- /doc/images/exam_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/exam_list.png -------------------------------------------------------------------------------- /doc/images/exam_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/exam_record.png -------------------------------------------------------------------------------- /doc/images/exam_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/exam_update.png -------------------------------------------------------------------------------- /doc/images/question_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/question_create.png -------------------------------------------------------------------------------- /doc/images/question_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/question_list.png -------------------------------------------------------------------------------- /doc/images/question_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/images/question_update.png -------------------------------------------------------------------------------- /doc/references/images/JWT概要.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/references/images/JWT概要.png -------------------------------------------------------------------------------- /doc/references/images/tomcat开启session共享.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/references/images/tomcat开启session共享.png -------------------------------------------------------------------------------- /doc/references/images/单机tomcat应用登录检验.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/references/images/单机tomcat应用登录检验.png -------------------------------------------------------------------------------- /doc/references/images/登录校验方案.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/doc/references/images/登录校验方案.png -------------------------------------------------------------------------------- /frontend/exam/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=false 5 | indent_style=space 6 | indent_size=2 7 | 8 | [{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}] 9 | indent_style=space 10 | indent_size=2 11 | 12 | [{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}] 13 | indent_style=space 14 | indent_size=2 15 | 16 | [{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}] 17 | indent_style=space 18 | indent_size=2 19 | 20 | [*.svg] 21 | indent_style=space 22 | indent_size=2 23 | 24 | [*.js.map] 25 | indent_style=space 26 | indent_size=2 27 | 28 | [*.less] 29 | indent_style=space 30 | indent_size=2 31 | 32 | [*.vue] 33 | indent_style=space 34 | indent_size=2 35 | 36 | [{.analysis_options,*.yml,*.yaml}] 37 | indent_style=space 38 | indent_size=2 39 | 40 | -------------------------------------------------------------------------------- /frontend/exam/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=false 3 | -------------------------------------------------------------------------------- /frontend/exam/.gitattributes: -------------------------------------------------------------------------------- 1 | public/* linguist-vendored -------------------------------------------------------------------------------- /frontend/exam/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /frontend/exam/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": false, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /frontend/exam/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10.15.0 4 | cache: yarn 5 | script: 6 | - yarn 7 | - yarn run lint --no-fix && yarn run build 8 | -------------------------------------------------------------------------------- /frontend/exam/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anan Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /frontend/exam/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app', 4 | [ 5 | '@babel/preset-env', 6 | { 7 | 'useBuiltIns': 'entry' 8 | } 9 | ] 10 | ] 11 | // if your use import on Demand, Use this code 12 | // , 13 | // plugins: [ 14 | // [ 'import', { 15 | // 'libraryName': 'ant-design-vue', 16 | // 'libraryDirectory': 'es', 17 | // 'style': true // `style: true` 会加载 less 文件 18 | // } ] 19 | // ] 20 | } 21 | -------------------------------------------------------------------------------- /frontend/exam/docs/add-page-loading-animate.md: -------------------------------------------------------------------------------- 1 | 为首屏增加 加载动画 2 | ==== 3 | 4 | 5 | 6 | ## 需求 7 | 8 | > 为了缓解用户第一次访问时,加载 JS 过大所导致用户等待白屏时间过长导致的用户体验不好,进行的一个优化动效。 9 | 10 | 11 | 12 | ## 实现方案 13 | 14 | 1. 将 动画加载 dom 元素放在 #app 内,Vue 生命周期开始时,会自动清掉 #app 下的所有元素。 15 | 2. 将 动画加载 dom 元素放在 body 下,Vue 生命周期开始时 App.vue (created, mounted) 调用 `../utils/utll` 下的 removeLoadingAnimate(#id, timeout) 则会移除加载动画 16 | 17 | 最后一步: 18 | ​ 将样式插入到 `public/index.html` 文件的 `` 最好写成内联 `` 19 | 20 | 21 | 22 | ---- 23 | 24 | 目前提供有两个样式,均在 `public/loading` 文件夹内。且 pro 已经默认使用了一套 loading 动画方案,可以直接参考 `public/index.html` 25 | 26 | 27 | ## 写在最后 28 | 29 | 目前 pro 有页面 overflow 显示出浏览器滚动条时,页面会抖动一下的问题。 30 | 31 | 欢迎各位提供能解决的方案和实现 demo。如果在条件允许的情况下,建议请直接使用 pro 进行改造,也欢迎直接 PR 到 pro 的仓库 32 | -------------------------------------------------------------------------------- /frontend/exam/docs/load-on-demand.md: -------------------------------------------------------------------------------- 1 | 按需加载 减小打包 2 | ==== 3 | 4 | 5 | 6 | ## 按需引入组件依赖 7 | 8 | `Ant Design Pro Vue` 默认编码工作并不支持按需引入,不过可以通过以下操作结合 [Ant Design Of Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn/) 官方文档来进行按需引入。 9 | 10 | - 增加项目按需引入依赖 11 | - 修改引入组件方式 12 | 13 | 14 | 15 | 1. 增加按需引入所需依赖 `babel-plugin-import` 16 | 并且修改文件 `babel.config.js` 17 | ```ecmascript 6 18 | module.exports = { 19 | presets: [ 20 | '@vue/app' 21 | ], 22 | plugins: [ 23 | [ "import", { 24 | "libraryName": "ant-design-vue", 25 | "libraryDirectory": "es", 26 | "style": "css" 27 | } ] 28 | ] 29 | } 30 | ``` 31 | 32 | 33 | 2. 修改引入组件方式 (注意,这只是一个例子,请完整引入你所需要的组件) 34 | 35 | 文件 `../components/use.js` 36 | 37 | ```javascript 38 | import Vue from 'vue' 39 | import { 40 | Input, 41 | Button, 42 | Select, 43 | Card, 44 | Form, 45 | Row, 46 | Col, 47 | Modal, 48 | Table, 49 | notification 50 | } from 'ant-design-vue' 51 | 52 | Vue.use(Input) 53 | Vue.use(Button) 54 | Vue.use(Select) 55 | Vue.use(Card) 56 | Vue.use(Form) 57 | Vue.use(Row) 58 | Vue.use(Col) 59 | Vue.use(Modal) 60 | Vue.use(Table) 61 | Vue.use(notification) 62 | 63 | Vue.prototype.$notification = notification; 64 | ``` 65 | 66 | 67 | 3. 最后在 `main.js` 中引入 `../components/use.js` 文件即可,如下 68 | 69 | ```javascript 70 | 71 | import Vue from 'vue' 72 | import App from './App' 73 | 74 | // 引入 按需组件的统一引入文件 75 | import './components/use' 76 | 77 | import './style/index.less' 78 | 79 | 80 | Vue.config.productionTip = false 81 | 82 | new Vue({ 83 | render: h => h(App), 84 | }).$mount('#app') 85 | 86 | ``` 87 | 88 | **具体完整实现可参考分支 [feature/demand_load](https://github.com/sendya/ant-design-pro-vue/tree/feature/demand_load)** 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ## 其他 减少打包大小 97 | 98 | 99 | 100 | 1. Ant Design Vue 1.2.x 版本起,采用的 ant-design 官方方案 svg Icon 组件,整个项目打包会变大很多,图标进行按需加载可参考 https://github.com/HeskeyBaozi/reduce-antd-icons-bundle-demo 101 | 2. moment 按需加载 可参考 https://github.com/jmblog/how-to-optimize-momentjs-with-webpack -------------------------------------------------------------------------------- /frontend/exam/docs/multi-tabs.md: -------------------------------------------------------------------------------- 1 | 多(页签)标签 模式 2 | ==== 3 | 4 | 5 | ## 让框架支持打开的页面增加多标签,可随时切换 6 | 7 | ### 关于如何移除该功能 组件 8 | 1. 移除 `/src/components/layouts/BasicLayout.vue` L3, L12, L19 9 | ```vue 10 | 11 | ``` 12 | 2. 移除 `/src/config/defaultSettings.js` L25 13 | 14 | 3. 移除 `src/store/modules/app.js` L27, L76-L79, L118-L120 15 | 16 | 4. 移除 `src/utils/mixin.js` L21 17 | 18 | 5. 删除组件目录 `src/components/MultiTab` 19 | 20 | > 以上 `L x` 均代表行N ,如 L3 = 行3 -------------------------------------------------------------------------------- /frontend/exam/docs/webpack-bundle-analyzer.md: -------------------------------------------------------------------------------- 1 | 先增加依赖 2 | 3 | ```bash 4 | // npm 5 | $ npm install --save-dev webpack-bundle-analyzer 6 | 7 | // or yarn 8 | $ yarn add webpack-bundle-analyzer -D 9 | ``` 10 | 11 | 配置文件 `vue.config.js` 增加 `configureWebpack.plugins` 参数 12 | 13 | ``` 14 | const path = require('path') 15 | const webpack = require('webpack') 16 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 17 | 18 | function resolve (dir) { 19 | return path.join(__dirname, dir) 20 | } 21 | 22 | // vue.config.js 23 | module.exports = { 24 | configureWebpack: { 25 | plugins: [ 26 | // Ignore all locale files of moment.js 27 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 28 | // 依赖大小分析工具 29 | new BundleAnalyzerPlugin(), 30 | ] 31 | }, 32 | 33 | 34 | ... 35 | } 36 | ``` 37 | 38 | 39 | 40 | 启动 `cli` 的 `build` 命令进行项目编译,编译完成时,会自动运行一个 http://localhost:8888 的地址,完整显示了支持库依赖 -------------------------------------------------------------------------------- /frontend/exam/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^../(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | testMatch: [ 20 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 21 | ], 22 | testURL: 'http://localhost/' 23 | } 24 | -------------------------------------------------------------------------------- /frontend/exam/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "baseUrl": ".", 5 | "paths": { 6 | "../*": ["src/*"] 7 | } 8 | }, 9 | "exclude": ["node_modules", "dist"], 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /frontend/exam/public/avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/public/avatar2.jpg -------------------------------------------------------------------------------- /frontend/exam/public/home/cover1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/public/home/cover1.jpg -------------------------------------------------------------------------------- /frontend/exam/public/home/cover2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/public/home/cover2.jpg -------------------------------------------------------------------------------- /frontend/exam/public/home/cover3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/public/home/cover3.jpg -------------------------------------------------------------------------------- /frontend/exam/public/home/cover4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/public/home/cover4.jpg -------------------------------------------------------------------------------- /frontend/exam/public/home/cover5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/public/home/cover5.jpg -------------------------------------------------------------------------------- /frontend/exam/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 在线考试系统 9 | 10 | 11 | 12 | 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/exam/public/loading/loading.css: -------------------------------------------------------------------------------- 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);}} -------------------------------------------------------------------------------- /frontend/exam/public/loading/loading.html: -------------------------------------------------------------------------------- 1 |
Loading
-------------------------------------------------------------------------------- /frontend/exam/public/loading/option2/html_code_segment.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
-------------------------------------------------------------------------------- /frontend/exam/public/loading/option2/loading.css: -------------------------------------------------------------------------------- 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;} -------------------------------------------------------------------------------- /frontend/exam/public/loading/option2/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/exam/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/public/logo.png -------------------------------------------------------------------------------- /frontend/exam/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 30 | -------------------------------------------------------------------------------- /frontend/exam/src/api/index.js: -------------------------------------------------------------------------------- 1 | const api = { 2 | Login: '/auth/login', 3 | Logout: '/auth/logout', 4 | ForgePassword: '/auth/forge-password', 5 | Register: '/auth/register', 6 | twoStepCode: '/auth/2step-code', 7 | SendSms: '/account/sms', 8 | SendSmsErr: '/account/sms_err', 9 | // get my info 10 | UserInfo: '/user/info', 11 | 12 | // 下面是自己的用户认证的接口 13 | UserRegister: '/user/register', 14 | UserLogin: '/user/login', 15 | 16 | // 考试的接口 17 | ExamQuestionList: '/exam/question/list', 18 | ExamQuestionUpdate: '/exam/question/update', 19 | ExamQuestionSelection: '/exam/question/selection', 20 | ExamQuestionCreate: '/exam/question/create', 21 | ExamList: '/exam/list', 22 | // 获取问题列表,按照单选、多选和判断进行分类 23 | ExamQuestionTypeList: '/exam/question/type/list', 24 | ExamCreate: '/exam/create', 25 | ExamCardList: '/exam/card/list', 26 | // 获取考试详情 27 | ExamDetail: '/exam/detail/', 28 | // 获取考试详情 29 | QuestionDetail: '/exam/question/detail/', 30 | // 交卷 31 | FinishExam: '/exam/finish/', 32 | ExamRecordList: '/exam/record/list', 33 | recordDetail: '/exam/record/detail/' 34 | } 35 | export default api 36 | -------------------------------------------------------------------------------- /frontend/exam/src/api/login.js: -------------------------------------------------------------------------------- 1 | import api from './index' 2 | import { axios } from '../utils/request' 3 | 4 | /** 5 | * login func 6 | * parameter: { 7 | * username: '', 8 | * password: '', 9 | * remember_me: true, 10 | * captcha: '12345' 11 | * } 12 | * @param parameter 13 | * @returns {*} 14 | */ 15 | export function login (parameter) { 16 | return axios({ 17 | // 用户登录接口改成自己的 18 | url: api.UserLogin, 19 | method: 'post', 20 | data: parameter 21 | }) 22 | } 23 | 24 | export function getSmsCaptcha (parameter) { 25 | return axios({ 26 | url: api.SendSms, 27 | method: 'post', 28 | data: parameter 29 | }) 30 | } 31 | 32 | export function getInfo () { 33 | return axios({ 34 | url: api.UserInfo, 35 | method: 'get', 36 | headers: { 37 | 'Content-Type': 'application/json;charset=UTF-8' 38 | } 39 | }) 40 | } 41 | 42 | export function logout () { 43 | return axios({ 44 | url: api.Logout, 45 | method: 'post', 46 | headers: { 47 | 'Content-Type': 'application/json;charset=UTF-8' 48 | } 49 | }) 50 | } 51 | 52 | /** 53 | * get user 2step code open? 54 | * @param parameter {*} 55 | */ 56 | export function get2step (parameter) { 57 | return axios({ 58 | url: api.twoStepCode, 59 | method: 'post', 60 | data: parameter 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /frontend/exam/src/api/user.js: -------------------------------------------------------------------------------- 1 | // 自己的借口呀:用户的注册和登录等服务,注意所有的接口应该都现在index.js里面注册,方便统一管理 2 | 3 | import api from './index' 4 | import { axios } from '../utils/request' 5 | 6 | export function login (parameter) { 7 | return axios({ 8 | url: api.UserLogin, 9 | method: 'post', 10 | data: parameter 11 | }) 12 | } 13 | 14 | export function register (parameter) { 15 | return axios({ 16 | url: api.UserRegister, 17 | method: 'post', 18 | data: parameter 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /frontend/exam/src/assets/background.svg: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /frontend/exam/src/assets/icons/bx-analyse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/exam/src/assets/icons/exam-list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/exam/src/assets/icons/mine.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/exam/src/assets/icons/question-admin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/exam/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/exam/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/exam/src/components/ArticleListContent/ArticleListContent.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 47 | 48 | 90 | -------------------------------------------------------------------------------- /frontend/exam/src/components/ArticleListContent/index.js: -------------------------------------------------------------------------------- 1 | import ArticleListContent from './ArticleListContent' 2 | 3 | export default ArticleListContent 4 | -------------------------------------------------------------------------------- /frontend/exam/src/components/AvatarList/Item.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 47 | -------------------------------------------------------------------------------- /frontend/exam/src/components/AvatarList/index.js: -------------------------------------------------------------------------------- 1 | import AvatarList from './List' 2 | import './index.less' 3 | 4 | export default AvatarList 5 | -------------------------------------------------------------------------------- /frontend/exam/src/components/AvatarList/index.less: -------------------------------------------------------------------------------- 1 | @import "../index"; 2 | 3 | @avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list"; 4 | @avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item"; 5 | 6 | .@{avatar-list-prefix-cls} { 7 | display: inline-block; 8 | 9 | ul { 10 | list-style: none; 11 | display: inline-block; 12 | padding: 0; 13 | margin: 0 0 0 8px; 14 | font-size: 0; 15 | } 16 | } 17 | 18 | .@{avatar-list-item-prefix-cls} { 19 | display: inline-block; 20 | font-size: @font-size-base; 21 | margin-left: -8px; 22 | width: @avatar-size-base; 23 | height: @avatar-size-base; 24 | 25 | :global { 26 | .ant-avatar { 27 | border: 1px solid #fff; 28 | cursor: pointer; 29 | } 30 | } 31 | 32 | &.large { 33 | width: @avatar-size-lg; 34 | height: @avatar-size-lg; 35 | } 36 | 37 | &.small { 38 | width: @avatar-size-sm; 39 | height: @avatar-size-sm; 40 | } 41 | 42 | &.mini { 43 | width: 20px; 44 | height: 20px; 45 | 46 | :global { 47 | .ant-avatar { 48 | width: 20px; 49 | height: 20px; 50 | line-height: 20px; 51 | 52 | .ant-avatar-string { 53 | font-size: 12px; 54 | line-height: 18px; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/Bar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 58 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/Liquid.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/MiniArea.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 53 | 54 | 57 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/MiniBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 54 | 55 | 58 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/MiniProgress.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | 37 | 76 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/MiniSmoothArea.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/Radar.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/RankList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | 31 | 78 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/TransferBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 65 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/Trend.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 52 | 53 | 83 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/chart.less: -------------------------------------------------------------------------------- 1 | .antv-chart-mini { 2 | position: relative; 3 | width: 100%; 4 | 5 | .chart-wrapper { 6 | position: absolute; 7 | bottom: -28px; 8 | width: 100%; 9 | 10 | /* margin: 0 -5px; 11 | overflow: hidden;*/ 12 | } 13 | } -------------------------------------------------------------------------------- /frontend/exam/src/components/Charts/smooth.area.less: -------------------------------------------------------------------------------- 1 | @import "../index"; 2 | 3 | @smoothArea-prefix-cls: ~"@{ant-pro-prefix}-smooth-area"; 4 | 5 | .@{smoothArea-prefix-cls} { 6 | position: relative; 7 | width: 100%; 8 | 9 | .chart-wrapper { 10 | position: absolute; 11 | bottom: -28px; 12 | width: 100%; 13 | } 14 | } -------------------------------------------------------------------------------- /frontend/exam/src/components/CountDown/index.js: -------------------------------------------------------------------------------- 1 | import CountDown from './CountDown' 2 | 3 | export default CountDown 4 | -------------------------------------------------------------------------------- /frontend/exam/src/components/CountDown/index.md: -------------------------------------------------------------------------------- 1 | # CountDown 倒计时 2 | 3 | 倒计时组件。 4 | 5 | 6 | 7 | 引用方式: 8 | 9 | ```javascript 10 | import CountDown from '../../components/CountDown/CountDown' 11 | 12 | export default { 13 | components: { 14 | CountDown 15 | } 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | 28 | 29 | ## API 30 | 31 | | 参数 | 说明 | 类型 | 默认值 | 32 | |----------|------------------------------------------|-------------|-------| 33 | | target | 目标时间 | Date | - | 34 | | onEnd | 倒计时结束回调 | funtion | -| 35 | -------------------------------------------------------------------------------- /frontend/exam/src/components/DescriptionList/index.js: -------------------------------------------------------------------------------- 1 | import DescriptionList from './DescriptionList' 2 | export default DescriptionList 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Ellipsis/Ellipsis.vue: -------------------------------------------------------------------------------- 1 | 65 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Ellipsis/index.js: -------------------------------------------------------------------------------- 1 | import Ellipsis from './Ellipsis' 2 | 3 | export default Ellipsis 4 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Ellipsis/index.md: -------------------------------------------------------------------------------- 1 | # Ellipsis 文本自动省略号 2 | 3 | 文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。 4 | 5 | 6 | 7 | 引用方式: 8 | 9 | ```javascript 10 | import Ellipsis from '../../components/Ellipsis' 11 | 12 | export default { 13 | components: { 14 | Ellipsis 15 | } 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 22 | 23 | ```html 24 | 25 | There were injuries alleged in three cases in 2015, and a 26 | fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall. 27 | 28 | ``` 29 | 30 | 31 | 32 | ## API 33 | 34 | 35 | 参数 | 说明 | 类型 | 默认值 36 | ----|------|-----|------ 37 | tooltip | 移动到文本展示完整内容的提示 | boolean | - 38 | length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - -------------------------------------------------------------------------------- /frontend/exam/src/components/Exception/index.js: -------------------------------------------------------------------------------- 1 | import ExceptionPage from './ExceptionPage.vue' 2 | export default ExceptionPage 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Exception/type.js: -------------------------------------------------------------------------------- 1 | const types = { 2 | 403: { 3 | img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg', 4 | title: '403', 5 | desc: '抱歉,你无权访问该页面' 6 | }, 7 | 404: { 8 | img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg', 9 | title: '404', 10 | desc: '抱歉,你访问的页面不存在或仍在开发中' 11 | }, 12 | 500: { 13 | img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg', 14 | title: '500', 15 | desc: '抱歉,服务器出错了' 16 | } 17 | } 18 | 19 | export default types 20 | -------------------------------------------------------------------------------- /frontend/exam/src/components/FooterToolbar/FooterToolBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /frontend/exam/src/components/FooterToolbar/index.js: -------------------------------------------------------------------------------- 1 | import FooterToolBar from './FooterToolBar' 2 | import './index.less' 3 | 4 | export default FooterToolBar 5 | -------------------------------------------------------------------------------- /frontend/exam/src/components/FooterToolbar/index.less: -------------------------------------------------------------------------------- 1 | @import "../index"; 2 | 3 | @footer-toolbar-prefix-cls: ~"@{ant-pro-prefix}-footer-toolbar"; 4 | 5 | .@{footer-toolbar-prefix-cls} { 6 | position: fixed; 7 | width: 100%; 8 | bottom: 0; 9 | right: 0; 10 | height: 56px; 11 | line-height: 56px; 12 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03); 13 | background: #fff; 14 | border-top: 1px solid #e8e8e8; 15 | padding: 0 24px; 16 | z-index: 9; 17 | 18 | &:after { 19 | content: ""; 20 | display: block; 21 | clear: both; 22 | } 23 | } -------------------------------------------------------------------------------- /frontend/exam/src/components/FooterToolbar/index.md: -------------------------------------------------------------------------------- 1 | # FooterToolbar 底部工具栏 2 | 3 | 固定在底部的工具栏。 4 | 5 | 6 | 7 | ## 何时使用 8 | 9 | 固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。 10 | 11 | 12 | 13 | 引用方式: 14 | 15 | ```javascript 16 | import FooterToolBar from '../../components/FooterToolbar' 17 | 18 | export default { 19 | components: { 20 | FooterToolBar 21 | } 22 | } 23 | ``` 24 | 25 | 26 | 27 | ## 代码演示 28 | 29 | ```html 30 | 31 | 提交 32 | 33 | ``` 34 | 或 35 | ```html 36 | 37 | 提交 38 | 39 | ``` 40 | 41 | 42 | ## API 43 | 44 | 参数 | 说明 | 类型 | 默认值 45 | ----|------|-----|------ 46 | children (slot) | 工具栏内容,向右对齐 | - | - 47 | extra | 额外信息,向左对齐 | String, Object | - 48 | 49 | -------------------------------------------------------------------------------- /frontend/exam/src/components/GlobalFooter/GlobalFooter.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | 24 | 51 | -------------------------------------------------------------------------------- /frontend/exam/src/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import GlobalFooter from './GlobalFooter' 2 | export default GlobalFooter 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/GlobalHeader/index.js: -------------------------------------------------------------------------------- 1 | import GlobalHeader from './GlobalHeader' 2 | export default GlobalHeader 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/IconSelector/IconSelector.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 34 | 35 | 54 | -------------------------------------------------------------------------------- /frontend/exam/src/components/IconSelector/README.md: -------------------------------------------------------------------------------- 1 | IconSelector 2 | ==== 3 | 4 | > 图标选择组件,常用于为某一个数据设定一个图标时使用 5 | > eg: 设定菜单列表时,为每个菜单设定一个图标 6 | 7 | 该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装 8 | 9 | 10 | 11 | ### 使用方式 12 | 13 | ```vue 14 | 19 | 20 | 39 | ``` 40 | 41 | 42 | 43 | ### 事件 44 | 45 | 46 | | 名称 | 说明 | 类型 | 默认值 | 47 | | ------ | -------------------------- | ------ | ------ | 48 | | change | 当改变了 `icon` 选中项触发 | String | - | 49 | -------------------------------------------------------------------------------- /frontend/exam/src/components/IconSelector/index.js: -------------------------------------------------------------------------------- 1 | import IconSelector from './IconSelector' 2 | export default IconSelector 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Menu/SideMenu.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 62 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Menu/index.js: -------------------------------------------------------------------------------- 1 | import SMenu from './menu' 2 | export default SMenu 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/MultiTab/index.js: -------------------------------------------------------------------------------- 1 | import MultiTab from './MultiTab' 2 | import './index.less' 3 | 4 | export default MultiTab 5 | -------------------------------------------------------------------------------- /frontend/exam/src/components/MultiTab/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab"; 4 | @multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper"; 5 | 6 | /* 7 | .topmenu .@{multi-tab-prefix-cls} { 8 | max-width: 1200px; 9 | margin: -23px auto 24px auto; 10 | } 11 | */ 12 | .@{multi-tab-prefix-cls} { 13 | margin: -23px -24px 24px -24px; 14 | background: #fff; 15 | } 16 | 17 | .topmenu .@{multi-tab-wrapper-prefix-cls} { 18 | max-width: 1200px; 19 | margin: 0 auto; 20 | } 21 | 22 | .topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} { 23 | max-width: 100%; 24 | margin: 0 auto; 25 | } 26 | -------------------------------------------------------------------------------- /frontend/exam/src/components/NoticeIcon/index.js: -------------------------------------------------------------------------------- 1 | import NoticeIcon from './NoticeIcon' 2 | export default NoticeIcon 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/NumberInfo/NumberInfo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /frontend/exam/src/components/NumberInfo/index.js: -------------------------------------------------------------------------------- 1 | import NumberInfo from './NumberInfo' 2 | 3 | export default NumberInfo 4 | -------------------------------------------------------------------------------- /frontend/exam/src/components/NumberInfo/index.less: -------------------------------------------------------------------------------- 1 | @import "../index"; 2 | 3 | @numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info"; 4 | 5 | .@{numberInfo-prefix-cls} { 6 | 7 | .ant-pro-number-info-subtitle { 8 | color: @text-color-secondary; 9 | font-size: @font-size-base; 10 | height: 22px; 11 | line-height: 22px; 12 | overflow: hidden; 13 | text-overflow: ellipsis; 14 | word-break: break-all; 15 | white-space: nowrap; 16 | } 17 | 18 | .number-info-value { 19 | margin-top: 4px; 20 | font-size: 0; 21 | overflow: hidden; 22 | text-overflow: ellipsis; 23 | word-break: break-all; 24 | white-space: nowrap; 25 | 26 | & > span { 27 | color: @heading-color; 28 | display: inline-block; 29 | line-height: 32px; 30 | height: 32px; 31 | font-size: 24px; 32 | margin-right: 32px; 33 | } 34 | 35 | .sub-total { 36 | color: @text-color-secondary; 37 | font-size: @font-size-lg; 38 | vertical-align: top; 39 | margin-right: 0; 40 | i { 41 | font-size: 12px; 42 | transform: scale(0.82); 43 | margin-left: 4px; 44 | } 45 | :global { 46 | .anticon-caret-up { 47 | color: @red-6; 48 | } 49 | .anticon-caret-down { 50 | color: @green-6; 51 | } 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /frontend/exam/src/components/NumberInfo/index.md: -------------------------------------------------------------------------------- 1 | # NumberInfo 数据文本 2 | 3 | 常用在数据卡片中,用于突出展示某个业务数据。 4 | 5 | 6 | 7 | 引用方式: 8 | 9 | ```javascript 10 | import NumberInfo from '../../components/NumberInfo' 11 | 12 | export default { 13 | components: { 14 | NumberInfo 15 | } 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 22 | 23 | ```html 24 | 29 | ``` 30 | 31 | 32 | 33 | ## API 34 | 35 | 参数 | 说明 | 类型 | 默认值 36 | ----|------|-----|------ 37 | title | 标题 | ReactNode\|string | - 38 | subTitle | 子标题 | ReactNode\|string | - 39 | total | 总量 | ReactNode\|string | - 40 | subTotal | 子总量 | ReactNode\|string | - 41 | status | 增加状态 | 'up \| down' | - 42 | theme | 状态样式 | string | 'light' 43 | gap | 设置数字和描述之间的间距(像素)| number | 8 44 | -------------------------------------------------------------------------------- /frontend/exam/src/components/PageHeader/index.js: -------------------------------------------------------------------------------- 1 | import PageHeader from './PageHeader' 2 | export default PageHeader 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/PageLoading/index.jsx: -------------------------------------------------------------------------------- 1 | import { Spin } from 'ant-design-vue' 2 | 3 | export default { 4 | name: 'PageLoading', 5 | render () { 6 | return (
7 | 8 |
) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Result/index.js: -------------------------------------------------------------------------------- 1 | import Result from './Result.vue' 2 | export default Result 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/SettingDrawer/SettingItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 39 | -------------------------------------------------------------------------------- /frontend/exam/src/components/SettingDrawer/index.js: -------------------------------------------------------------------------------- 1 | import SettingDrawer from './SettingDrawer' 2 | export default SettingDrawer 3 | -------------------------------------------------------------------------------- /frontend/exam/src/components/StandardFormRow/index.js: -------------------------------------------------------------------------------- 1 | import StandardFormRow from './StandardFormRow' 2 | 3 | export default StandardFormRow 4 | -------------------------------------------------------------------------------- /frontend/exam/src/components/TagSelect/TagSelectOption.jsx: -------------------------------------------------------------------------------- 1 | import { Tag } from 'ant-design-vue' 2 | const { CheckableTag } = Tag 3 | 4 | export default { 5 | name: 'TagSelectOption', 6 | props: { 7 | prefixCls: { 8 | type: String, 9 | default: 'ant-pro-tag-select-option' 10 | }, 11 | value: { 12 | type: [String, Number, Object], 13 | default: '' 14 | }, 15 | checked: { 16 | type: Boolean, 17 | default: false 18 | } 19 | }, 20 | data () { 21 | return { 22 | localChecked: this.checked || false 23 | } 24 | }, 25 | watch: { 26 | 'checked' (val) { 27 | this.localChecked = val 28 | }, 29 | '$parent.items': { 30 | handler: function (val) { 31 | this.value && val.hasOwnProperty(this.value) && (this.localChecked = val[this.value]) 32 | }, 33 | deep: true 34 | } 35 | }, 36 | render () { 37 | const { $slots, value } = this 38 | const onChange = (checked) => { 39 | this.$emit('change', { value, checked }) 40 | } 41 | return ( 42 | {$slots.default} 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Trend/Trend.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Trend/index.js: -------------------------------------------------------------------------------- 1 | import Trend from './Trend.vue' 2 | 3 | export default Trend 4 | -------------------------------------------------------------------------------- /frontend/exam/src/components/Trend/index.less: -------------------------------------------------------------------------------- 1 | @import "../index"; 2 | 3 | @trend-prefix-cls: ~"@{ant-pro-prefix}-trend"; 4 | 5 | .@{trend-prefix-cls} { 6 | display: inline-block; 7 | font-size: @font-size-base; 8 | line-height: 22px; 9 | 10 | .up, 11 | .down { 12 | margin-left: 4px; 13 | position: relative; 14 | top: 1px; 15 | 16 | i { 17 | font-size: 12px; 18 | transform: scale(0.83); 19 | } 20 | } 21 | 22 | .item-text { 23 | display: inline-block; 24 | margin-left: 8px; 25 | color: rgba(0,0,0,.85); 26 | } 27 | 28 | .up { 29 | color: @red-6; 30 | } 31 | .down { 32 | color: @green-6; 33 | top: -1px; 34 | } 35 | 36 | &.reverse-color .up { 37 | color: @green-6; 38 | } 39 | &.reverse-color .down { 40 | color: @red-6; 41 | } 42 | } -------------------------------------------------------------------------------- /frontend/exam/src/components/Trend/index.md: -------------------------------------------------------------------------------- 1 | # Trend 趋势标记 2 | 3 | 趋势符号,标记上升和下降趋势。通常用绿色代表“好”,红色代表“不好”,股票涨跌场景除外。 4 | 5 | 6 | 7 | 引用方式: 8 | 9 | ```javascript 10 | import Trend from '../../components/Trend' 11 | 12 | export default { 13 | components: { 14 | Trend 15 | } 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 22 | 23 | ```html 24 | 5% 25 | ``` 26 | 或 27 | ```html 28 | 29 | 工资 30 | 5% 31 | 32 | ``` 33 | 或 34 | ```html 35 | 5% 36 | ``` 37 | 38 | 39 | ## API 40 | 41 | | 参数 | 说明 | 类型 | 默认值 | 42 | |----------|------------------------------------------|-------------|-------| 43 | | flag | 上升下降标识:`up|down` | string | - | 44 | | reverseColor | 颜色反转 | Boolean | false | 45 | 46 | -------------------------------------------------------------------------------- /frontend/exam/src/components/_util/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * components util 3 | */ 4 | 5 | /** 6 | * 清理空值,对象 7 | * @param children 8 | * @returns {*[]} 9 | */ 10 | export function filterEmpty (children = []) { 11 | return children.filter(c => c.tag || (c.text && c.text.trim() !== '')) 12 | } 13 | 14 | /** 15 | * 获取字符串长度,英文字符 长度1,中文字符长度2 16 | * @param {*} str 17 | */ 18 | export const getStrFullLength = (str = '') => 19 | str.split('').reduce((pre, cur) => { 20 | const charCode = cur.charCodeAt(0) 21 | if (charCode >= 0 && charCode <= 128) { 22 | return pre + 1 23 | } 24 | return pre + 2 25 | }, 0) 26 | 27 | /** 28 | * 截取字符串,根据 maxLength 截取后返回 29 | * @param {*} str 30 | * @param {*} maxLength 31 | */ 32 | export const cutStrByFullLength = (str = '', maxLength) => { 33 | let showLength = 0 34 | return str.split('').reduce((pre, cur) => { 35 | const charCode = cur.charCodeAt(0) 36 | if (charCode >= 0 && charCode <= 128) { 37 | showLength += 1 38 | } else { 39 | showLength += 2 40 | } 41 | if (showLength <= maxLength) { 42 | return pre + cur 43 | } 44 | return pre 45 | }, '') 46 | } 47 | -------------------------------------------------------------------------------- /frontend/exam/src/components/index.js: -------------------------------------------------------------------------------- 1 | // chart 2 | import Bar from '../components/Charts/Bar' 3 | import ChartCard from '../components/Charts/ChartCard' 4 | import Liquid from '../components/Charts/Liquid' 5 | import MiniArea from '../components/Charts/MiniArea' 6 | import MiniSmoothArea from '../components/Charts/MiniSmoothArea' 7 | import MiniBar from '../components/Charts/MiniBar' 8 | import MiniProgress from '../components/Charts/MiniProgress' 9 | import Radar from '../components/Charts/Radar' 10 | import RankList from '../components/Charts/RankList' 11 | import TransferBar from '../components/Charts/TransferBar' 12 | import TagCloud from '../components/Charts/TagCloud' 13 | 14 | // pro components 15 | import AvatarList from '../components/AvatarList' 16 | import CountDown from '../components/CountDown' 17 | import Ellipsis from '../components/Ellipsis' 18 | import FooterToolbar from '../components/FooterToolbar' 19 | import NumberInfo from '../components/NumberInfo' 20 | import DescriptionList from '../components/DescriptionList' 21 | import Tree from '../components/Tree/Tree' 22 | import Trend from '../components/Trend' 23 | import STable from '../components/Table' 24 | import MultiTab from '../components/MultiTab' 25 | import Result from '../components/Result' 26 | import IconSelector from '../components/IconSelector' 27 | import TagSelect from '../components/TagSelect' 28 | import ExceptionPage from '../components/Exception' 29 | import StandardFormRow from '../components/StandardFormRow' 30 | import ArticleListContent from '../components/ArticleListContent' 31 | 32 | export { 33 | AvatarList, 34 | Bar, 35 | ChartCard, 36 | Liquid, 37 | MiniArea, 38 | MiniSmoothArea, 39 | MiniBar, 40 | MiniProgress, 41 | Radar, 42 | TagCloud, 43 | RankList, 44 | TransferBar, 45 | Trend, 46 | CountDown, 47 | Ellipsis, 48 | FooterToolbar, 49 | NumberInfo, 50 | DescriptionList, 51 | // 兼容写法,请勿继续使用 52 | DescriptionList as DetailList, 53 | Tree, 54 | STable, 55 | MultiTab, 56 | Result, 57 | ExceptionPage, 58 | IconSelector, 59 | TagSelect, 60 | StandardFormRow, 61 | ArticleListContent 62 | } 63 | -------------------------------------------------------------------------------- /frontend/exam/src/components/index.less: -------------------------------------------------------------------------------- 1 | @import "~ant-design-vue/lib/style/index"; 2 | 3 | // The prefix to use on all css classes from ant-pro. 4 | @ant-pro-prefix : ant-pro; -------------------------------------------------------------------------------- /frontend/exam/src/components/tools/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /frontend/exam/src/components/tools/DetailList.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /frontend/exam/src/components/tools/HeadInfo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | 33 | 68 | -------------------------------------------------------------------------------- /frontend/exam/src/components/tools/Logo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | -------------------------------------------------------------------------------- /frontend/exam/src/components/tools/UserMenu.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 60 | -------------------------------------------------------------------------------- /frontend/exam/src/components/tools/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yimning/spring-boot-online-exam/554fc869ee3b2af2a9dec03f72713a27235f171f/frontend/exam/src/components/tools/index.js -------------------------------------------------------------------------------- /frontend/exam/src/config/defaultSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目默认配置项 3 | * primaryColor - 默认主题色 4 | * navTheme - sidebar theme ['dark', 'light'] 两种主题 5 | * colorWeak - 色盲模式 6 | * layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局 7 | * fixedHeader - 固定 Header : boolean 8 | * fixSiderbar - 固定左侧菜单栏 : boolean 9 | * autoHideHeader - 向下滚动时,隐藏 Header : boolean 10 | * contentWidth - 内容区布局: 流式 | 固定 11 | * 12 | * storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage) 13 | * 14 | */ 15 | 16 | export default { 17 | primaryColor: '#1890FF', // primary color of ant design 18 | navTheme: 'dark', // theme for nav menu 19 | layout: 'topmenu', // nav menu position: sidemenu or topmenu 20 | contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu 21 | fixedHeader: true, // sticky header 22 | fixSiderbar: false, // sticky siderbar 23 | autoHideHeader: false, // auto hide header 24 | colorWeak: false, 25 | multiTab: false, 26 | production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true', 27 | // vue-ls options, localStorage中默认的存储前缀 28 | storageOptions: { 29 | namespace: 'pro__', // key prefix 30 | name: 'ls', // name variable Vue.[ls] or this.[$ls], 31 | storage: 'local' // storage name session, local, memory 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /frontend/exam/src/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '../store/' 3 | import { 4 | ACCESS_TOKEN, 5 | DEFAULT_COLOR, 6 | DEFAULT_THEME, 7 | DEFAULT_LAYOUT_MODE, 8 | DEFAULT_COLOR_WEAK, 9 | SIDEBAR_TYPE, 10 | DEFAULT_FIXED_HEADER, 11 | DEFAULT_FIXED_HEADER_HIDDEN, 12 | DEFAULT_FIXED_SIDEMENU, 13 | DEFAULT_CONTENT_WIDTH_TYPE, 14 | DEFAULT_MULTI_TAB 15 | } from '../store/mutation-types' 16 | import config from '../config/defaultSettings' 17 | 18 | export default function Initializer () { 19 | store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true)) 20 | store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme)) 21 | store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout)) 22 | store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader)) 23 | store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar)) 24 | store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth)) 25 | store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader)) 26 | store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak)) 27 | store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor)) 28 | store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab)) 29 | store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN)) 30 | 31 | // last step 32 | } 33 | -------------------------------------------------------------------------------- /frontend/exam/src/core/directives/action.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '../../store' 3 | 4 | /** 5 | * Action 权限指令 6 | * 指令用法: 7 | * - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下: 8 | * 添加用户 9 | * 删除用户 10 | * 修改 11 | * 12 | * - 当前用户没有权限时,组件上使用了该指令则会被隐藏 13 | * - 当后台权限跟 pro 提供的模式不同时,只需要针对这里的权限过滤进行修改即可 14 | * 15 | * @see https://github.com/sendya/ant-design-pro-vue/pull/53 16 | */ 17 | const action = Vue.directive('action', { 18 | inserted: function (el, binding, vnode) { 19 | const actionName = binding.arg 20 | const roles = store.getters.roles 21 | const elVal = vnode.context.$route.meta.permission 22 | const permissionId = elVal instanceof String && [elVal] || elVal 23 | roles.permissions.forEach(p => { 24 | if (!permissionId.includes(p.permissionId)) { 25 | return 26 | } 27 | if (p.actionList && !p.actionList.includes(actionName)) { 28 | el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none') 29 | } 30 | }) 31 | } 32 | }) 33 | 34 | export default action 35 | -------------------------------------------------------------------------------- /frontend/exam/src/core/icons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom icon list 3 | * All icons are loaded here for easy management 4 | * @see https://vue.ant.design/components/icon/#Custom-Font-Icon 5 | * 6 | * 自定义图标加载表 7 | * 所有图标均从这里加载,方便管理 8 | */ 9 | import bxAnaalyse from '../assets/icons/bx-analyse.svg?inline' // path to your '*.svg?inline' file. 10 | import examList from '../assets/icons/exam-list.svg?inline' // path to your '*.svg?inline' file. 11 | import examAdmin from '../assets/icons/exam-admin.svg?inline' // path to your '*.svg?inline' file. 12 | import questionAdmin from '../assets/icons/question-admin.svg?inline' // path to your '*.svg?inline' file. 13 | import mine from '../assets/icons/mine.svg?inline' // path to your '*.svg?inline' file. 14 | 15 | export { bxAnaalyse, examList, examAdmin, questionAdmin, mine } 16 | -------------------------------------------------------------------------------- /frontend/exam/src/core/lazy_lib/components_use.js: -------------------------------------------------------------------------------- 1 | 2 | /* eslint-disable */ 3 | /** 4 | * 该文件是为了按需加载,剔除掉了一些不需要的框架组件。 5 | * 减少了编译支持库包大小 6 | * 7 | * 当需要更多组件依赖时,在该文件加入即可 8 | */ 9 | import Vue from 'vue' 10 | import { 11 | LocaleProvider, 12 | Layout, 13 | Input, 14 | InputNumber, 15 | Button, 16 | Switch, 17 | Radio, 18 | Checkbox, 19 | Select, 20 | Card, 21 | Form, 22 | Row, 23 | Col, 24 | Modal, 25 | Table, 26 | Tabs, 27 | Icon, 28 | Badge, 29 | Popover, 30 | Dropdown, 31 | List, 32 | Avatar, 33 | Breadcrumb, 34 | Steps, 35 | Spin, 36 | Menu, 37 | Drawer, 38 | Tooltip, 39 | Alert, 40 | Tag, 41 | Divider, 42 | DatePicker, 43 | TimePicker, 44 | Upload, 45 | Progress, 46 | Skeleton, 47 | Popconfirm, 48 | message, 49 | notification 50 | } from 'ant-design-vue' 51 | // import VueCropper from 'vue-cropper' 52 | 53 | Vue.use(LocaleProvider) 54 | Vue.use(Layout) 55 | Vue.use(Input) 56 | Vue.use(InputNumber) 57 | Vue.use(Button) 58 | Vue.use(Switch) 59 | Vue.use(Radio) 60 | Vue.use(Checkbox) 61 | Vue.use(Select) 62 | Vue.use(Card) 63 | Vue.use(Form) 64 | Vue.use(Row) 65 | Vue.use(Col) 66 | Vue.use(Modal) 67 | Vue.use(Table) 68 | Vue.use(Tabs) 69 | Vue.use(Icon) 70 | Vue.use(Badge) 71 | Vue.use(Popover) 72 | Vue.use(Dropdown) 73 | Vue.use(List) 74 | Vue.use(Avatar) 75 | Vue.use(Breadcrumb) 76 | Vue.use(Steps) 77 | Vue.use(Spin) 78 | Vue.use(Menu) 79 | Vue.use(Drawer) 80 | Vue.use(Tooltip) 81 | Vue.use(Alert) 82 | Vue.use(Tag) 83 | Vue.use(Divider) 84 | Vue.use(DatePicker) 85 | Vue.use(TimePicker) 86 | Vue.use(Upload) 87 | Vue.use(Progress) 88 | Vue.use(Skeleton) 89 | Vue.use(Popconfirm) 90 | // Vue.use(VueCropper) 91 | Vue.use(notification) 92 | 93 | Vue.prototype.$confirm = Modal.confirm 94 | Vue.prototype.$message = message 95 | Vue.prototype.$notification = notification 96 | Vue.prototype.$info = Modal.info 97 | Vue.prototype.$success = Modal.success 98 | Vue.prototype.$error = Modal.error 99 | Vue.prototype.$warning = Modal.warning -------------------------------------------------------------------------------- /frontend/exam/src/core/lazy_use.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueStorage from 'vue-ls' 3 | import config from '../config/defaultSettings' 4 | 5 | // base library 6 | import '../core/lazy_lib/components_use' 7 | import Viser from 'viser-vue' 8 | 9 | // ext library 10 | import VueClipboard from 'vue-clipboard2' 11 | import PermissionHelper from '../utils/helper/permission' 12 | import './directives/action' 13 | 14 | VueClipboard.config.autoSetContainer = true 15 | 16 | Vue.use(Viser) 17 | 18 | Vue.use(VueStorage, config.storageOptions) 19 | Vue.use(VueClipboard) 20 | Vue.use(PermissionHelper) 21 | -------------------------------------------------------------------------------- /frontend/exam/src/core/use.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueStorage from 'vue-ls' 3 | import config from '../config/defaultSettings' 4 | 5 | // base library 6 | import Antd from 'ant-design-vue' 7 | import Viser from 'viser-vue' 8 | import VueCropper from 'vue-cropper' 9 | import 'ant-design-vue/dist/antd.less' 10 | 11 | // ext library 12 | import VueClipboard from 'vue-clipboard2' 13 | import PermissionHelper from '../utils/helper/permission' 14 | // import '../components/use' 15 | import './directives/action' 16 | 17 | VueClipboard.config.autoSetContainer = true 18 | 19 | Vue.use(Antd) 20 | Vue.use(Viser) 21 | 22 | Vue.use(VueStorage, config.storageOptions) 23 | Vue.use(VueClipboard) 24 | Vue.use(PermissionHelper) 25 | Vue.use(VueCropper) 26 | -------------------------------------------------------------------------------- /frontend/exam/src/layouts/BlankLayout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /frontend/exam/src/layouts/RouteView.vue: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /frontend/exam/src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import UserLayout from './UserLayout' 2 | import BlankLayout from './BlankLayout' 3 | import BasicLayout from './BasicLayout' 4 | import RouteView from './RouteView' 5 | import PageView from './PageView' 6 | 7 | export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView } 8 | -------------------------------------------------------------------------------- /frontend/exam/src/main.js: -------------------------------------------------------------------------------- 1 | // ie polyfill 2 | import '@babel/polyfill' 3 | 4 | import Vue from 'vue' 5 | import App from './App.vue' 6 | import router from './router' 7 | import store from './store/' 8 | import { VueAxios } from './utils/request' 9 | 10 | import bootstrap from './core/bootstrap' 11 | import './core/use' 12 | import './permission' // permission control 13 | import './utils/filter' // global filter 14 | 15 | Vue.config.productionTip = false 16 | 17 | // mount axios Vue.$http and this.$http 18 | Vue.use(VueAxios) 19 | 20 | new Vue({ 21 | router, 22 | store, 23 | created () { 24 | bootstrap() 25 | }, 26 | render: h => h(App) 27 | }).$mount('#app') 28 | -------------------------------------------------------------------------------- /frontend/exam/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import { constantRouterMap } from '../config/router.config' 4 | 5 | Vue.use(Router) 6 | 7 | export default new Router({ 8 | mode: 'history', 9 | base: process.env.BASE_URL, 10 | scrollBehavior: () => ({ y: 0 }), 11 | routes: constantRouterMap 12 | }) 13 | -------------------------------------------------------------------------------- /frontend/exam/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | device: state => state.app.device, 3 | theme: state => state.app.theme, 4 | color: state => state.app.color, 5 | token: state => state.user.token, 6 | avatar: state => state.user.avatar, 7 | nickname: state => state.user.name, 8 | welcome: state => state.user.welcome, 9 | roles: state => state.user.roles, 10 | userInfo: state => state.user.info, 11 | addRouters: state => state.permission.addRouters, 12 | multiTab: state => state.app.multiTab 13 | } 14 | 15 | export default getters 16 | -------------------------------------------------------------------------------- /frontend/exam/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import app from './modules/app' 5 | import user from './modules/user' 6 | import permission from './modules/permission' 7 | import getters from './getters' 8 | 9 | Vue.use(Vuex) 10 | 11 | export default new Vuex.Store({ 12 | modules: { 13 | app, 14 | user, 15 | permission 16 | }, 17 | state: { 18 | 19 | }, 20 | mutations: { 21 | 22 | }, 23 | actions: { 24 | 25 | }, 26 | getters 27 | }) 28 | -------------------------------------------------------------------------------- /frontend/exam/src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { asyncRouterMap, constantRouterMap } from '../../config/router.config' 2 | 3 | /** 4 | * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除 5 | * 6 | * @param permission 7 | * @param route 8 | * @returns {boolean} 9 | */ 10 | function hasPermission (permission, route) { 11 | if (route.meta && route.meta.permission) { 12 | let flag = false 13 | for (let i = 0, len = permission.length; i < len; i++) { 14 | flag = route.meta.permission.includes(permission[i]) 15 | if (flag) { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | return true 22 | } 23 | 24 | /** 25 | * 单账户多角色时,使用该方法可过滤角色不存在的菜单 26 | * 27 | * @param roles 28 | * @param route 29 | * @returns {*} 30 | */ 31 | // eslint-disable-next-line 32 | function hasRole(roles, route) { 33 | if (route.meta && route.meta.roles) { 34 | return route.meta.roles.includes(roles.id) 35 | } else { 36 | return true 37 | } 38 | } 39 | 40 | function filterAsyncRouter (routerMap, roles) { 41 | const accessedRouters = routerMap.filter(route => { 42 | if (hasPermission(roles.permissionList, route)) { 43 | if (route.children && route.children.length) { 44 | route.children = filterAsyncRouter(route.children, roles) 45 | } 46 | return true 47 | } 48 | return false 49 | }) 50 | return accessedRouters 51 | } 52 | 53 | const permission = { 54 | state: { 55 | routers: constantRouterMap, 56 | addRouters: [] 57 | }, 58 | mutations: { 59 | SET_ROUTERS: (state, routers) => { 60 | state.addRouters = routers 61 | state.routers = constantRouterMap.concat(routers) 62 | } 63 | }, 64 | actions: { 65 | GenerateRoutes ({ commit }, data) { 66 | return new Promise(resolve => { 67 | const { roles } = data 68 | const accessedRouters = filterAsyncRouter(asyncRouterMap, roles) 69 | commit('SET_ROUTERS', accessedRouters) 70 | resolve() 71 | }) 72 | } 73 | } 74 | } 75 | 76 | export default permission 77 | -------------------------------------------------------------------------------- /frontend/exam/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'Access-Token' 2 | export const SIDEBAR_TYPE = 'SIDEBAR_TYPE' 3 | export const DEFAULT_THEME = 'DEFAULT_THEME' 4 | export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE' 5 | export const DEFAULT_COLOR = 'DEFAULT_COLOR' 6 | export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK' 7 | export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER' 8 | export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU' 9 | export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN' 10 | export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE' 11 | export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB' 12 | 13 | export const CONTENT_WIDTH_TYPE = { 14 | Fluid: 'Fluid', 15 | Fixed: 'Fixed' 16 | } 17 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/axios.js: -------------------------------------------------------------------------------- 1 | const VueAxios = { 2 | vm: {}, 3 | // eslint-disable-next-line no-unused-vars 4 | install (Vue, instance) { 5 | if (this.installed) { 6 | return 7 | } 8 | this.installed = true 9 | 10 | if (!instance) { 11 | // eslint-disable-next-line no-console 12 | console.error('You have to install axios') 13 | return 14 | } 15 | 16 | Vue.axios = instance 17 | 18 | Object.defineProperties(Vue.prototype, { 19 | axios: { 20 | get: function get () { 21 | return instance 22 | } 23 | }, 24 | $http: { 25 | get: function get () { 26 | return instance 27 | } 28 | } 29 | }) 30 | } 31 | } 32 | 33 | export { 34 | VueAxios 35 | } 36 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/device.js: -------------------------------------------------------------------------------- 1 | import enquireJs from 'enquire.js' 2 | 3 | export const DEVICE_TYPE = { 4 | DESKTOP: 'desktop', 5 | TABLET: 'tablet', 6 | MOBILE: 'mobile' 7 | } 8 | 9 | export const deviceEnquire = function (callback) { 10 | const matchDesktop = { 11 | match: () => { 12 | callback && callback(DEVICE_TYPE.DESKTOP) 13 | } 14 | } 15 | 16 | const matchLablet = { 17 | match: () => { 18 | callback && callback(DEVICE_TYPE.TABLET) 19 | } 20 | } 21 | 22 | const matchMobile = { 23 | match: () => { 24 | callback && callback(DEVICE_TYPE.MOBILE) 25 | } 26 | } 27 | 28 | // screen and (max-width: 1087.99px) 29 | enquireJs 30 | .register('screen and (max-width: 576px)', matchMobile) 31 | .register('screen and (min-width: 576px) and (max-width: 1199px)', matchLablet) 32 | .register('screen and (min-width: 1200px)', matchDesktop) 33 | } 34 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/domUtil.js: -------------------------------------------------------------------------------- 1 | export const setDocumentTitle = function (title) { 2 | document.title = title 3 | const ua = navigator.userAgent 4 | // eslint-disable-next-line 5 | const regex = /\bMicroMessenger\/([\d\.]+)/ 6 | if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) { 7 | const i = document.createElement('iframe') 8 | i.src = '/favicon.ico' 9 | i.style.display = 'none' 10 | i.onload = function () { 11 | setTimeout(function () { 12 | i.remove() 13 | }, 9) 14 | } 15 | document.body.appendChild(i) 16 | } 17 | } 18 | 19 | export const domTitle = 'Online Exam' 20 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/filter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import moment from 'moment' 3 | import 'moment/locale/zh-cn' 4 | moment.locale('zh-cn') 5 | 6 | Vue.filter('NumberFormat', function (value) { 7 | if (!value) { 8 | return '0' 9 | } 10 | const intPartFormat = value.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') // 将整数部分逢三一断 11 | return intPartFormat 12 | }) 13 | 14 | Vue.filter('dayjs', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { 15 | return moment(dataStr).format(pattern) 16 | }) 17 | 18 | Vue.filter('moment', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { 19 | return moment(dataStr).format(pattern) 20 | }) 21 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/helper/permission.js: -------------------------------------------------------------------------------- 1 | const PERMISSION_ENUM = { 2 | 'add': { key: 'add', label: '新增' }, 3 | 'delete': { key: 'delete', label: '删除' }, 4 | 'edit': { key: 'edit', label: '修改' }, 5 | 'query': { key: 'query', label: '查询' }, 6 | 'get': { key: 'get', label: '详情' }, 7 | 'enable': { key: 'enable', label: '启用' }, 8 | 'disable': { key: 'disable', label: '禁用' }, 9 | 'import': { key: 'import', label: '导入' }, 10 | 'export': { key: 'export', label: '导出' } 11 | } 12 | 13 | function plugin (Vue) { 14 | if (plugin.installed) { 15 | return 16 | } 17 | 18 | !Vue.prototype.$auth && Object.defineProperties(Vue.prototype, { 19 | $auth: { 20 | get () { 21 | const _this = this 22 | return (permissions) => { 23 | const [permission, action] = permissions.split('.') 24 | const permissionList = _this.$store.getters.roles.permissions 25 | return permissionList.find((val) => { 26 | return val.permissionId === permission 27 | }).actionList.findIndex((val) => { 28 | return val === action 29 | }) > -1 30 | } 31 | } 32 | } 33 | }) 34 | 35 | !Vue.prototype.$enum && Object.defineProperties(Vue.prototype, { 36 | $enum: { 37 | get () { 38 | // const _this = this; 39 | return (val) => { 40 | let result = PERMISSION_ENUM 41 | val && val.split('.').forEach(v => { 42 | result = result && result[v] || null 43 | }) 44 | return result 45 | } 46 | } 47 | } 48 | }) 49 | } 50 | 51 | export default plugin 52 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/mixin.js: -------------------------------------------------------------------------------- 1 | // import Vue from 'vue' 2 | import { deviceEnquire, DEVICE_TYPE } from '../utils/device' 3 | import { mapState } from 'vuex' 4 | 5 | // const mixinsComputed = Vue.config.optionMergeStrategies.computed 6 | // const mixinsMethods = Vue.config.optionMergeStrategies.methods 7 | 8 | const mixin = { 9 | computed: { 10 | ...mapState({ 11 | layoutMode: state => state.app.layout, 12 | navTheme: state => state.app.theme, 13 | primaryColor: state => state.app.color, 14 | colorWeak: state => state.app.weak, 15 | fixedHeader: state => state.app.fixedHeader, 16 | fixSiderbar: state => state.app.fixSiderbar, 17 | fixSidebar: state => state.app.fixSiderbar, 18 | contentWidth: state => state.app.contentWidth, 19 | autoHideHeader: state => state.app.autoHideHeader, 20 | sidebarOpened: state => state.app.sidebar, 21 | multiTab: state => state.app.multiTab 22 | }) 23 | }, 24 | methods: { 25 | isTopMenu () { 26 | return this.layoutMode === 'topmenu' 27 | }, 28 | isSideMenu () { 29 | return !this.isTopMenu() 30 | } 31 | } 32 | } 33 | 34 | const mixinDevice = { 35 | computed: { 36 | ...mapState({ 37 | device: state => state.app.device 38 | }) 39 | }, 40 | methods: { 41 | isMobile () { 42 | return this.device === DEVICE_TYPE.MOBILE 43 | }, 44 | isDesktop () { 45 | return this.device === DEVICE_TYPE.DESKTOP 46 | }, 47 | isTablet () { 48 | return this.device === DEVICE_TYPE.TABLET 49 | } 50 | } 51 | } 52 | 53 | const AppDeviceEnquire = { 54 | mounted () { 55 | const { $store } = this 56 | deviceEnquire(deviceType => { 57 | switch (deviceType) { 58 | case DEVICE_TYPE.DESKTOP: 59 | $store.commit('TOGGLE_DEVICE', 'desktop') 60 | $store.dispatch('setSidebar', true) 61 | break 62 | case DEVICE_TYPE.TABLET: 63 | $store.commit('TOGGLE_DEVICE', 'tablet') 64 | $store.dispatch('setSidebar', false) 65 | break 66 | case DEVICE_TYPE.MOBILE: 67 | default: 68 | $store.commit('TOGGLE_DEVICE', 'mobile') 69 | $store.dispatch('setSidebar', true) 70 | break 71 | } 72 | }) 73 | } 74 | } 75 | 76 | export { mixin, AppDeviceEnquire, mixinDevice } 77 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/permissions.js: -------------------------------------------------------------------------------- 1 | export function actionToObject (json) { 2 | try { 3 | return JSON.parse(json) 4 | } catch (e) { 5 | console.log('err', e.message) 6 | } 7 | return [] 8 | } 9 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import store from '../store' 4 | import { 5 | VueAxios 6 | } from './axios' 7 | import notification from 'ant-design-vue/es/notification' 8 | import { 9 | ACCESS_TOKEN 10 | } from '../store/mutation-types' 11 | 12 | // 创建 axios 实例 13 | const service = axios.create({ 14 | baseURL: '/api', // api base_url 15 | timeout: 6000 // 请求超时时间 16 | }) 17 | 18 | const err = (error) => { 19 | if (error.response) { 20 | const data = error.response.data 21 | const token = Vue.ls.get(ACCESS_TOKEN) 22 | if (error.response.status === 403) { 23 | notification.error({ 24 | message: 'Forbidden', 25 | description: data.message 26 | }) 27 | } 28 | if (error.response.status === 401 && !(data.result && data.result.isLogin)) { 29 | notification.error({ 30 | message: 'Unauthorized', 31 | description: 'Authorization verification failed' 32 | }) 33 | if (token) { 34 | store.dispatch('Logout').then(() => { 35 | setTimeout(() => { 36 | window.location.reload() 37 | }, 1500) 38 | }) 39 | } 40 | } 41 | } 42 | return Promise.reject(error) 43 | } 44 | 45 | // request interceptor 46 | service.interceptors.request.use(config => { 47 | const token = Vue.ls.get(ACCESS_TOKEN) 48 | if (token) { // 如果localStorage中有"Access-Token"属性,就在请求头里加上 49 | config.headers['Access-Token'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改 50 | } 51 | return config 52 | }, err) 53 | 54 | // response interceptor 55 | service.interceptors.response.use((response) => { 56 | return response.data 57 | }, err) 58 | 59 | const installer = { 60 | vm: {}, 61 | install (Vue) { 62 | Vue.use(VueAxios, service) 63 | } 64 | } 65 | 66 | export { 67 | installer as VueAxios, 68 | service as axios 69 | } 70 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set storage 3 | * 4 | * @param name 5 | * @param content 6 | * @param maxAge 7 | */ 8 | export const setStore = (name, content, maxAge = null) => { 9 | if (!global.window || !name) { 10 | return 11 | } 12 | 13 | if (typeof content !== 'string') { 14 | content = JSON.stringify(content) 15 | } 16 | 17 | const storage = global.window.localStorage 18 | 19 | storage.setItem(name, content) 20 | if (maxAge && !isNaN(parseInt(maxAge))) { 21 | const timeout = parseInt(new Date().getTime() / 1000) 22 | storage.setItem(`${name}_expire`, timeout + maxAge) 23 | } 24 | } 25 | 26 | /** 27 | * Get storage 28 | * 29 | * @param name 30 | * @returns {*} 31 | */ 32 | export const getStore = name => { 33 | if (!global.window || !name) { 34 | return 35 | } 36 | 37 | const content = window.localStorage.getItem(name) 38 | const _expire = window.localStorage.getItem(`${name}_expire`) 39 | 40 | if (_expire) { 41 | const now = parseInt(new Date().getTime() / 1000) 42 | if (now > _expire) { 43 | return 44 | } 45 | } 46 | 47 | try { 48 | return JSON.parse(content) 49 | } catch (e) { 50 | return content 51 | } 52 | } 53 | 54 | /** 55 | * Clear storage 56 | * 57 | * @param name 58 | */ 59 | export const clearStore = name => { 60 | if (!global.window || !name) { 61 | return 62 | } 63 | 64 | window.localStorage.removeItem(name) 65 | window.localStorage.removeItem(`${name}_expire`) 66 | } 67 | 68 | /** 69 | * Clear all storage 70 | */ 71 | export const clearAll = () => { 72 | if (!global.window || !name) { 73 | return 74 | } 75 | 76 | window.localStorage.clear() 77 | } 78 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/util.js: -------------------------------------------------------------------------------- 1 | export function timeFix () { 2 | const time = new Date() 3 | const hour = time.getHours() 4 | return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好' 5 | } 6 | 7 | export function welcome () { 8 | const arr = ['休息一会儿吧', '准备吃什么呢?', '要不要打一把 DOTA', '我猜你可能累了'] 9 | const index = Math.floor(Math.random() * arr.length) 10 | return arr[index] 11 | } 12 | 13 | /** 14 | * 触发 window.resize 15 | */ 16 | export function triggerWindowResizeEvent () { 17 | const event = document.createEvent('HTMLEvents') 18 | event.initEvent('resize', true, true) 19 | event.eventType = 'message' 20 | window.dispatchEvent(event) 21 | } 22 | 23 | export function handleScrollHeader (callback) { 24 | let timer = 0 25 | 26 | let beforeScrollTop = window.pageYOffset 27 | callback = callback || function () {} 28 | window.addEventListener( 29 | 'scroll', 30 | event => { 31 | clearTimeout(timer) 32 | timer = setTimeout(() => { 33 | let direction = 'up' 34 | const afterScrollTop = window.pageYOffset 35 | const delta = afterScrollTop - beforeScrollTop 36 | if (delta === 0) { 37 | return false 38 | } 39 | direction = delta > 0 ? 'down' : 'up' 40 | callback(direction) 41 | beforeScrollTop = afterScrollTop 42 | }, 50) 43 | }, 44 | false 45 | ) 46 | } 47 | 48 | /** 49 | * Remove loading animate 50 | * @param id parent element id or class 51 | * @param timeout 52 | */ 53 | export function removeLoadingAnimate (id = '', timeout = 1500) { 54 | if (id === '') { 55 | return 56 | } 57 | setTimeout(() => { 58 | document.body.removeChild(document.getElementById(id)) 59 | }, timeout) 60 | } 61 | -------------------------------------------------------------------------------- /frontend/exam/src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | word-break: break-all; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | margin-right: -1em; 12 | padding-right: 1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | &::before { 17 | position: absolute; 18 | right: 14px; 19 | bottom: 0; 20 | padding: 0 1px; 21 | background: @bg; 22 | content: '...'; 23 | } 24 | &::after { 25 | position: absolute; 26 | right: 14px; 27 | width: 1em; 28 | height: 1em; 29 | margin-top: 0.2em; 30 | background: white; 31 | content: ''; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &::before, 40 | &::after { 41 | display: table; 42 | content: ' '; 43 | } 44 | &::after { 45 | clear: both; 46 | height: 0; 47 | font-size: 0; 48 | visibility: hidden; 49 | } 50 | } -------------------------------------------------------------------------------- /frontend/exam/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /frontend/exam/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | -------------------------------------------------------------------------------- /frontend/exam/src/views/account/settings/Custom.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /frontend/exam/src/views/exception/403.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /frontend/exam/src/views/exception/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /frontend/exam/src/views/exception/500.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /frontend/exam/src/views/home/Banner.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /frontend/exam/src/views/home/List.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /frontend/exam/src/views/home/ListItem.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /frontend/exam/src/views/home/default.less: -------------------------------------------------------------------------------- 1 | @import "~ant-design-vue/lib/style/themes/default"; 2 | @site-heading-color: #0d1a26; 3 | @site-text-color: #314659; 4 | @site-text-color-secondary: #697b8c; 5 | @site-border-color-split: #ebedf0; 6 | @border-color: rgba(229, 231, 235, 100); 7 | 8 | article, aside, blockquote, body, button, code, dd, details, div, dl, dt, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, input, legend, li, menu, nav, ol, p, pre, section, td, textarea, th, ul { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/exam/src/views/list/modules/CreateForm.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 68 | -------------------------------------------------------------------------------- /frontend/exam/src/views/list/modules/QuestionViewModal.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 62 | -------------------------------------------------------------------------------- /frontend/exam/src/views/user/RegisterResult.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 47 | 48 | 51 | -------------------------------------------------------------------------------- /frontend/exam/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /frontend/exam/webstorm.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | 4 | function resolve (dir) { 5 | return path.join(__dirname, '.', dir) 6 | } 7 | 8 | module.exports = { 9 | context: path.resolve(__dirname, './'), 10 | resolve: { 11 | extensions: ['.js', '.vue', '.json'], 12 | alias: { 13 | '@': resolve('src') 14 | } 15 | } 16 | } 17 | --------------------------------------------------------------------------------