├── .gitignore ├── LICENSE ├── README.md ├── exam-api ├── docs │ ├── 安装资源 │ │ └── 数据库初始化.sql │ ├── 源码说明.pdf │ ├── 运行包 │ │ ├── application.properties │ │ ├── exam-api.jar │ │ ├── start.bat │ │ ├── start.sh │ │ └── 运行说明.txt │ └── 部署手册.pdf ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── yf │ │ │ └── exam │ │ │ ├── ExamApplication.java │ │ │ ├── aspect │ │ │ ├── DictAspect.java │ │ │ ├── mybatis │ │ │ │ ├── QueryInterceptor.java │ │ │ │ └── UpdateInterceptor.java │ │ │ └── utils │ │ │ │ └── InjectUtils.java │ │ │ ├── config │ │ │ ├── CorsConfig.java │ │ │ ├── MybatisConfig.java │ │ │ └── ShiroConfig.java │ │ │ ├── core │ │ │ ├── annon │ │ │ │ ├── Dict.java │ │ │ │ ├── LogInject.java │ │ │ │ └── UserInject.java │ │ │ ├── api │ │ │ │ ├── ApiError.java │ │ │ │ ├── ApiRest.java │ │ │ │ ├── controller │ │ │ │ │ └── BaseController.java │ │ │ │ ├── dto │ │ │ │ │ ├── BaseDTO.java │ │ │ │ │ ├── BaseIdReqDTO.java │ │ │ │ │ ├── BaseIdRespDTO.java │ │ │ │ │ ├── BaseIdsReqDTO.java │ │ │ │ │ ├── BaseIfReqDTO.java │ │ │ │ │ ├── BaseListDTO.java │ │ │ │ │ ├── BaseQueryReqDTO.java │ │ │ │ │ ├── BaseStateReqDTO.java │ │ │ │ │ ├── BaseUserReqDTO.java │ │ │ │ │ ├── BooleanRespDTO.java │ │ │ │ │ ├── PagingReqDTO.java │ │ │ │ │ ├── PagingRespDTO.java │ │ │ │ │ └── PayReqDTO.java │ │ │ │ ├── enums │ │ │ │ │ └── CommonState.java │ │ │ │ └── utils │ │ │ │ │ ├── JsonConverter.java │ │ │ │ │ ├── MsgUtils.java │ │ │ │ │ └── SignUtils.java │ │ │ ├── exception │ │ │ │ ├── ServiceException.java │ │ │ │ └── ServiceExceptionHandler.java │ │ │ └── utils │ │ │ │ ├── BeanMapper.java │ │ │ │ ├── DateUtils.java │ │ │ │ ├── DecimalUtils.java │ │ │ │ ├── IpUtils.java │ │ │ │ ├── Md5Util.java │ │ │ │ ├── Reflections.java │ │ │ │ ├── SpringUtils.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── excel │ │ │ │ ├── ExportExcel.java │ │ │ │ ├── ImportExcel.java │ │ │ │ ├── annotation │ │ │ │ │ └── ExcelField.java │ │ │ │ └── fieldtype │ │ │ │ │ └── ListType.java │ │ │ │ ├── file │ │ │ │ ├── MD5Util.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── TextFileUtils.java │ │ │ │ └── ZipUtils.java │ │ │ │ └── passwd │ │ │ │ ├── PassHandler.java │ │ │ │ └── PassInfo.java │ │ │ └── modules │ │ │ ├── Constant.java │ │ │ ├── enums │ │ │ ├── JoinType.java │ │ │ └── OpenType.java │ │ │ ├── exam │ │ │ ├── controller │ │ │ │ └── ExamController.java │ │ │ ├── dto │ │ │ │ ├── ExamDTO.java │ │ │ │ ├── ExamDepartDTO.java │ │ │ │ ├── ExamRepoDTO.java │ │ │ │ ├── request │ │ │ │ │ └── ExamSaveReqDTO.java │ │ │ │ └── response │ │ │ │ │ ├── ExamOnlineRespDTO.java │ │ │ │ │ └── ExamReviewRespDTO.java │ │ │ ├── entity │ │ │ │ ├── Exam.java │ │ │ │ ├── ExamDepart.java │ │ │ │ └── ExamRepo.java │ │ │ ├── mapper │ │ │ │ ├── ExamDepartMapper.java │ │ │ │ ├── ExamMapper.java │ │ │ │ └── ExamRepoMapper.java │ │ │ └── service │ │ │ │ ├── ExamDepartService.java │ │ │ │ ├── ExamRepoService.java │ │ │ │ ├── ExamService.java │ │ │ │ └── impl │ │ │ │ ├── ExamDepartServiceImpl.java │ │ │ │ ├── ExamRepoServiceImpl.java │ │ │ │ └── ExamServiceImpl.java │ │ │ ├── paper │ │ │ ├── controller │ │ │ │ └── PaperController.java │ │ │ ├── dto │ │ │ │ ├── PaperDTO.java │ │ │ │ ├── PaperQuAnswerDTO.java │ │ │ │ ├── PaperQuDTO.java │ │ │ │ ├── ext │ │ │ │ │ ├── PaperQuAnswerExtDTO.java │ │ │ │ │ └── PaperQuDetailDTO.java │ │ │ │ ├── request │ │ │ │ │ ├── PaperAnswerDTO.java │ │ │ │ │ ├── PaperCreateReqDTO.java │ │ │ │ │ ├── PaperListReqDTO.java │ │ │ │ │ └── PaperQuQueryDTO.java │ │ │ │ └── response │ │ │ │ │ ├── ExamDetailRespDTO.java │ │ │ │ │ ├── ExamResultRespDTO.java │ │ │ │ │ └── PaperListRespDTO.java │ │ │ ├── entity │ │ │ │ ├── Paper.java │ │ │ │ ├── PaperQu.java │ │ │ │ └── PaperQuAnswer.java │ │ │ ├── enums │ │ │ │ ├── ExamState.java │ │ │ │ └── PaperState.java │ │ │ ├── mapper │ │ │ │ ├── PaperMapper.java │ │ │ │ ├── PaperQuAnswerMapper.java │ │ │ │ └── PaperQuMapper.java │ │ │ └── service │ │ │ │ ├── PaperQuAnswerService.java │ │ │ │ ├── PaperQuService.java │ │ │ │ ├── PaperService.java │ │ │ │ └── impl │ │ │ │ ├── PaperQuAnswerServiceImpl.java │ │ │ │ ├── PaperQuServiceImpl.java │ │ │ │ └── PaperServiceImpl.java │ │ │ ├── qu │ │ │ ├── controller │ │ │ │ └── QuController.java │ │ │ ├── dto │ │ │ │ ├── QuAnswerDTO.java │ │ │ │ ├── QuDTO.java │ │ │ │ ├── QuRepoDTO.java │ │ │ │ ├── export │ │ │ │ │ ├── QuExportDTO.java │ │ │ │ │ └── QuImportDTO.java │ │ │ │ ├── ext │ │ │ │ │ └── QuDetailDTO.java │ │ │ │ └── request │ │ │ │ │ ├── QuQueryReqDTO.java │ │ │ │ │ └── QuRepoBatchReqDTO.java │ │ │ ├── entity │ │ │ │ ├── Qu.java │ │ │ │ ├── QuAnswer.java │ │ │ │ └── QuRepo.java │ │ │ ├── enums │ │ │ │ └── QuType.java │ │ │ ├── mapper │ │ │ │ ├── QuAnswerMapper.java │ │ │ │ ├── QuMapper.java │ │ │ │ └── QuRepoMapper.java │ │ │ └── service │ │ │ │ ├── QuAnswerService.java │ │ │ │ ├── QuRepoService.java │ │ │ │ ├── QuService.java │ │ │ │ └── impl │ │ │ │ ├── QuAnswerServiceImpl.java │ │ │ │ ├── QuRepoServiceImpl.java │ │ │ │ └── QuServiceImpl.java │ │ │ ├── repo │ │ │ ├── controller │ │ │ │ └── RepoController.java │ │ │ ├── dto │ │ │ │ ├── RepoDTO.java │ │ │ │ └── response │ │ │ │ │ └── RepoRespDTO.java │ │ │ ├── entity │ │ │ │ └── Repo.java │ │ │ ├── mapper │ │ │ │ └── RepoMapper.java │ │ │ └── service │ │ │ │ ├── RepoService.java │ │ │ │ └── impl │ │ │ │ └── RepoServiceImpl.java │ │ │ ├── shiro │ │ │ ├── ShiroRealm.java │ │ │ ├── aop │ │ │ │ └── JwtFilter.java │ │ │ └── jwt │ │ │ │ ├── JwtToken.java │ │ │ │ └── JwtUtils.java │ │ │ ├── sys │ │ │ ├── config │ │ │ │ ├── controller │ │ │ │ │ └── SysConfigController.java │ │ │ │ ├── dto │ │ │ │ │ └── SysConfigDTO.java │ │ │ │ ├── entity │ │ │ │ │ └── SysConfig.java │ │ │ │ ├── mapper │ │ │ │ │ └── SysConfigMapper.java │ │ │ │ └── service │ │ │ │ │ ├── SysConfigService.java │ │ │ │ │ └── impl │ │ │ │ │ └── SysConfigServiceImpl.java │ │ │ ├── depart │ │ │ │ ├── controller │ │ │ │ │ └── SysDepartController.java │ │ │ │ ├── dto │ │ │ │ │ ├── SysDepartDTO.java │ │ │ │ │ ├── request │ │ │ │ │ │ └── DepartSortReqDTO.java │ │ │ │ │ └── response │ │ │ │ │ │ └── SysDepartTreeDTO.java │ │ │ │ ├── entity │ │ │ │ │ └── SysDepart.java │ │ │ │ ├── mapper │ │ │ │ │ └── SysDepartMapper.java │ │ │ │ └── service │ │ │ │ │ ├── SysDepartService.java │ │ │ │ │ └── impl │ │ │ │ │ └── SysDepartServiceImpl.java │ │ │ ├── system │ │ │ │ ├── mapper │ │ │ │ │ └── SysDictMapper.java │ │ │ │ └── service │ │ │ │ │ ├── SysDictService.java │ │ │ │ │ └── impl │ │ │ │ │ └── SysDictServiceImpl.java │ │ │ └── user │ │ │ │ ├── controller │ │ │ │ ├── SysRoleController.java │ │ │ │ └── SysUserController.java │ │ │ │ ├── dto │ │ │ │ ├── SysRoleDTO.java │ │ │ │ ├── SysUserDTO.java │ │ │ │ ├── SysUserRoleDTO.java │ │ │ │ ├── request │ │ │ │ │ ├── SysUserLoginReqDTO.java │ │ │ │ │ ├── SysUserSaveReqDTO.java │ │ │ │ │ └── SysUserTokenReqDTO.java │ │ │ │ └── response │ │ │ │ │ └── SysUserLoginDTO.java │ │ │ │ ├── entity │ │ │ │ ├── SysRole.java │ │ │ │ ├── SysUser.java │ │ │ │ └── SysUserRole.java │ │ │ │ ├── mapper │ │ │ │ ├── SysRoleMapper.java │ │ │ │ ├── SysUserMapper.java │ │ │ │ └── SysUserRoleMapper.java │ │ │ │ └── service │ │ │ │ ├── SysRoleService.java │ │ │ │ ├── SysUserRoleService.java │ │ │ │ ├── SysUserService.java │ │ │ │ └── impl │ │ │ │ ├── SysRoleServiceImpl.java │ │ │ │ ├── SysUserRoleServiceImpl.java │ │ │ │ └── SysUserServiceImpl.java │ │ │ └── user │ │ │ ├── UserUtils.java │ │ │ ├── book │ │ │ ├── controller │ │ │ │ └── UserBookController.java │ │ │ ├── dto │ │ │ │ └── UserBookDTO.java │ │ │ ├── entity │ │ │ │ └── UserBook.java │ │ │ ├── mapper │ │ │ │ └── UserBookMapper.java │ │ │ └── service │ │ │ │ ├── UserBookService.java │ │ │ │ └── impl │ │ │ │ └── UserBookServiceImpl.java │ │ │ └── exam │ │ │ ├── controller │ │ │ └── UserExamController.java │ │ │ ├── dto │ │ │ ├── UserExamDTO.java │ │ │ ├── request │ │ │ │ └── UserExamReqDTO.java │ │ │ └── response │ │ │ │ └── UserExamRespDTO.java │ │ │ ├── entity │ │ │ └── UserExam.java │ │ │ ├── mapper │ │ │ └── UserExamMapper.java │ │ │ └── service │ │ │ ├── UserExamService.java │ │ │ └── impl │ │ │ └── UserExamServiceImpl.java │ └── resources │ │ ├── application.properties │ │ ├── mapper │ │ ├── exam │ │ │ ├── ExamDepartMapper.xml │ │ │ ├── ExamMapper.xml │ │ │ └── ExamRepoMapper.xml │ │ ├── paper │ │ │ ├── PaperMapper.xml │ │ │ ├── PaperQuAnswerMapper.xml │ │ │ └── PaperQuMapper.xml │ │ ├── qu │ │ │ ├── QuAnswerMapper.xml │ │ │ ├── QuMapper.xml │ │ │ └── QuRepoMapper.xml │ │ ├── repo │ │ │ └── RepoMapper.xml │ │ ├── sys │ │ │ ├── depart │ │ │ │ └── SysDepartMapper.xml │ │ │ ├── system │ │ │ │ └── SysDictMapper.xml │ │ │ └── user │ │ │ │ ├── SysRoleMapper.xml │ │ │ │ ├── SysUserMapper.xml │ │ │ │ └── SysUserRoleMapper.xml │ │ └── user │ │ │ ├── UserBookMapper.xml │ │ │ └── UserExamMapper.xml │ │ └── static │ │ ├── favicon.ico │ │ ├── index.html │ │ └── static │ │ ├── css │ │ ├── app.29f9c8e9.css │ │ ├── chunk-11aeece2.df2997bb.css │ │ ├── chunk-1fc6e879.0e8d89f6.css │ │ ├── chunk-29be97b1.0b2c7de9.css │ │ ├── chunk-30ca8599.d0296c60.css │ │ ├── chunk-3f05a485.72886611.css │ │ ├── chunk-4e97554c.2016041e.css │ │ ├── chunk-527df50c.5be46457.css │ │ ├── chunk-5fc77a43.29e7bbce.css │ │ ├── chunk-7c80eb59.905a47aa.css │ │ ├── chunk-88ffa5a0.25d4b7fe.css │ │ ├── chunk-ab13d2fa.b043920c.css │ │ ├── chunk-c09eb5c6.fb7634dd.css │ │ ├── chunk-commons.3cbb8a09.css │ │ ├── chunk-d0da5b72.5be46457.css │ │ ├── chunk-elementUI.6e808e7d.css │ │ ├── chunk-fd454600.5464b619.css │ │ └── chunk-libs.3dfb7769.css │ │ ├── fonts │ │ ├── element-icons.535877f5.woff │ │ └── element-icons.732389de.ttf │ │ ├── img │ │ ├── 401.089007e7.gif │ │ ├── 404.a57b6f31.png │ │ ├── 404_cloud.0f4bc32b.png │ │ └── bg2.7dfac08d.jpg │ │ └── js │ │ ├── app.67d6e28c.js │ │ ├── chunk-01e7d4a8.fe2a0a47.js │ │ ├── chunk-11aeece2.ab5d890f.js │ │ ├── chunk-121dc2b3.7097969c.js │ │ ├── chunk-16a41be2.1173a7b4.js │ │ ├── chunk-1fc6e879.7e9954d2.js │ │ ├── chunk-29be97b1.4e4bdf54.js │ │ ├── chunk-2a3c5153.594695fa.js │ │ ├── chunk-2d0cf932.92df6ba2.js │ │ ├── chunk-2d0e5357.eca512cc.js │ │ ├── chunk-2d2105d3.68fb450b.js │ │ ├── chunk-2d230fe7.193e48a7.js │ │ ├── chunk-30ca8599.1ee478b5.js │ │ ├── chunk-3f05a485.9f32a365.js │ │ ├── chunk-41e2f83a.36dd8c4b.js │ │ ├── chunk-4e97554c.3fff0dfc.js │ │ ├── chunk-527df50c.03beceee.js │ │ ├── chunk-56cbff0a.07acd1fc.js │ │ ├── chunk-5c98060f.bcecbcbc.js │ │ ├── chunk-5fc77a43.59b3141c.js │ │ ├── chunk-7128370c.e5400f3e.js │ │ ├── chunk-7c80eb59.d1d72560.js │ │ ├── chunk-88ffa5a0.073ff7ac.js │ │ ├── chunk-ab13d2fa.1e915cdf.js │ │ ├── chunk-c09eb5c6.ecac29c2.js │ │ ├── chunk-ce278c24.9e9265bd.js │ │ ├── chunk-ce6546f8.bb148440.js │ │ ├── chunk-commons.83515f74.js │ │ ├── chunk-d0da5b72.cb727916.js │ │ ├── chunk-elementUI.a46bb98f.js │ │ ├── chunk-fd454600.0fdecd58.js │ │ └── chunk-libs.270d4e39.js │ └── test │ └── java │ └── com │ └── yf │ └── exam │ ├── ExamTest.java │ └── PaperTest.java └── exam-vue ├── .editorconfig ├── .env.development ├── .env.production ├── .env.staging ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── babel.config.js ├── build └── index.js ├── jest.config.js ├── jsconfig.json ├── package.json ├── plop-templates ├── component │ ├── index.hbs │ └── prompt.js ├── store │ ├── index.hbs │ └── prompt.js ├── utils.js └── view │ ├── index.hbs │ └── prompt.js ├── plopfile.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── common.js │ ├── exam │ │ └── exam.js │ ├── paper │ │ ├── exam.js │ │ └── paper.js │ ├── qu │ │ ├── qu.js │ │ └── repo.js │ ├── sys │ │ ├── config │ │ │ └── config.js │ │ ├── depart │ │ │ └── depart.js │ │ ├── role │ │ │ └── role.js │ │ └── user │ │ │ └── user.js │ ├── user.js │ └── user │ │ ├── book.js │ │ └── repo.js ├── assets │ ├── 401_images │ │ └── 401.gif │ ├── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png │ ├── bg2.jpg │ ├── custom-theme │ │ ├── fonts │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ │ └── index.css │ ├── guide.pdf │ ├── logo.png │ └── system.png ├── components │ ├── BackToTop │ │ └── index.vue │ ├── Breadcrumb │ │ └── index.vue │ ├── DataTable │ │ └── index.vue │ ├── DepartTreeSelect │ │ └── index.vue │ ├── DndList │ │ └── index.vue │ ├── ErrorLog │ │ └── index.vue │ ├── ExamSelect │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ ├── HeaderSearch │ │ └── index.vue │ ├── MeetRole │ │ └── index.vue │ ├── Pagination │ │ └── index.vue │ ├── PanThumb │ │ └── index.vue │ ├── RepoSelect │ │ └── index.vue │ ├── RightPanel │ │ └── index.vue │ ├── RuleSelect │ │ └── index.vue │ ├── Screenfull │ │ └── index.vue │ ├── Share │ │ └── DropdownMenu.vue │ ├── SizeSelect │ │ └── index.vue │ ├── Sticky │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── TextHoverEffect │ │ └── Mallki.vue │ └── ThemePicker │ │ └── index.vue ├── directive │ ├── clipboard │ │ ├── clipboard.js │ │ └── index.js │ ├── el-drag-dialog │ │ ├── drag.js │ │ └── index.js │ ├── el-table │ │ ├── adaptive.js │ │ └── index.js │ ├── permission │ │ ├── index.js │ │ └── permission.js │ ├── sticky.js │ └── waves │ │ ├── index.js │ │ ├── waves.css │ │ └── waves.js ├── filters │ └── index.js ├── icons │ ├── index.js │ ├── svg │ │ ├── 404.svg │ │ ├── admin.svg │ │ ├── agreement.svg │ │ ├── bug.svg │ │ ├── chart.svg │ │ ├── clipboard.svg │ │ ├── component.svg │ │ ├── configure.svg │ │ ├── dashboard.svg │ │ ├── documentation.svg │ │ ├── drag.svg │ │ ├── edit.svg │ │ ├── education.svg │ │ ├── email.svg │ │ ├── example.svg │ │ ├── excel.svg │ │ ├── exit-fullscreen.svg │ │ ├── eye-open.svg │ │ ├── eye.svg │ │ ├── fire.svg │ │ ├── form.svg │ │ ├── fullscreen.svg │ │ ├── guide.svg │ │ ├── hot.svg │ │ ├── icon.svg │ │ ├── international.svg │ │ ├── language.svg │ │ ├── link.svg │ │ ├── list.svg │ │ ├── lock.svg │ │ ├── log.svg │ │ ├── map.svg │ │ ├── message.svg │ │ ├── money.svg │ │ ├── nested.svg │ │ ├── notify.svg │ │ ├── paper.svg │ │ ├── password.svg │ │ ├── pdf.svg │ │ ├── people.svg │ │ ├── peoples.svg │ │ ├── qq.svg │ │ ├── repo.svg │ │ ├── results.svg │ │ ├── review.svg │ │ ├── role.svg │ │ ├── search.svg │ │ ├── settings .svg │ │ ├── shopping.svg │ │ ├── size.svg │ │ ├── skill.svg │ │ ├── star.svg │ │ ├── statis.svg │ │ ├── stats-dots.svg │ │ ├── stats2.svg │ │ ├── study.svg │ │ ├── study1.svg │ │ ├── support.svg │ │ ├── tab.svg │ │ ├── table.svg │ │ ├── test.svg │ │ ├── theme.svg │ │ ├── topic.svg │ │ ├── training.svg │ │ ├── tree-table.svg │ │ ├── tree.svg │ │ ├── user.svg │ │ ├── water.svg │ │ ├── wechat.svg │ │ └── zip.svg │ └── svgo.yml ├── layout │ ├── components │ │ ├── AppMain.vue │ │ ├── Navbar.vue │ │ ├── Settings │ │ │ └── index.vue │ │ ├── Sidebar │ │ │ ├── FixiOSBug.js │ │ │ ├── Item.vue │ │ │ ├── Link.vue │ │ │ ├── Logo.vue │ │ │ ├── SidebarItem.vue │ │ │ └── index.vue │ │ ├── TagsView │ │ │ ├── ScrollPane.vue │ │ │ └── index.vue │ │ └── index.js │ ├── index.vue │ └── mixin │ │ └── ResizeHandler.js ├── main.js ├── permission.js ├── router │ └── index.js ├── settings.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── app.js │ │ ├── errorLog.js │ │ ├── permission.js │ │ ├── settings.js │ │ ├── tagsView.js │ │ └── user.js ├── styles │ ├── btn.scss │ ├── element-ui.scss │ ├── element-variables.scss │ ├── index.scss │ ├── meetboxs.scss │ ├── mixin.scss │ ├── sidebar.scss │ ├── transition.scss │ └── variables.scss ├── utils │ ├── auth.js │ ├── clipboard.js │ ├── error-log.js │ ├── get-page-title.js │ ├── index.js │ ├── open-window.js │ ├── permission.js │ ├── request.js │ ├── scroll-to.js │ └── validate.js └── views │ ├── dashboard │ └── index.vue │ ├── error-log │ ├── components │ │ ├── ErrorTestA.vue │ │ └── ErrorTestB.vue │ └── index.vue │ ├── error-page │ ├── 401.vue │ └── 404.vue │ ├── exam │ └── exam │ │ ├── form.vue │ │ └── index.vue │ ├── login │ ├── auth-redirect.vue │ ├── components │ │ └── SocialSignin.vue │ ├── index.vue │ └── register.vue │ ├── paper │ ├── exam │ │ ├── exam.vue │ │ ├── list.vue │ │ ├── preview.vue │ │ └── result.vue │ └── paper │ │ └── index.vue │ ├── profile │ ├── components │ │ ├── Account.vue │ │ └── UserCard.vue │ └── index.vue │ ├── qu │ ├── qu │ │ ├── form.vue │ │ ├── index.vue │ │ └── view.vue │ └── repo │ │ ├── form.vue │ │ └── index.vue │ ├── redirect │ └── index.vue │ ├── sys │ ├── config │ │ └── index.vue │ ├── depart │ │ └── index.vue │ ├── log │ │ └── index.vue │ ├── role │ │ └── index.vue │ └── user │ │ └── index.vue │ └── user │ ├── book │ ├── index.vue │ └── train.vue │ └── exam │ ├── index.vue │ ├── my.vue │ └── paper.vue ├── tests └── unit │ ├── .eslintrc.js │ ├── components │ ├── Hamburger.spec.js │ └── SvgIcon.spec.js │ └── utils │ ├── formatTime.spec.js │ ├── parseTime.spec.js │ └── validate.spec.js └── vue.config.js /.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 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | -------------------------------------------------------------------------------- /exam-api/docs/源码说明.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/docs/源码说明.pdf -------------------------------------------------------------------------------- /exam-api/docs/运行包/application.properties: -------------------------------------------------------------------------------- 1 | # 端口基础配置 2 | server.port=8101 3 | spring.application.name=yf_exam 4 | 5 | # 数据库配置 6 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 7 | spring.datasource.url=jdbc:mysql://localhost:3306/yf_exam_lite?useUnicode=true&characterEncoding=UTF-8&useSSL=false\&serverTimezone=Asia/Shanghai 8 | spring.datasource.username=root 9 | spring.datasource.password=root 10 | 11 | -------------------------------------------------------------------------------- /exam-api/docs/运行包/exam-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/docs/运行包/exam-api.jar -------------------------------------------------------------------------------- /exam-api/docs/运行包/start.bat: -------------------------------------------------------------------------------- 1 | java -jar exam-api.jar -Dspring.config.location=application.properties -------------------------------------------------------------------------------- /exam-api/docs/运行包/start.sh: -------------------------------------------------------------------------------- 1 | java -jar exam-api.jar -Dspring.config.location=application.properties -------------------------------------------------------------------------------- /exam-api/docs/运行包/运行说明.txt: -------------------------------------------------------------------------------- 1 | 1、自行安装MySQL数据库(版本最好大于5.7),将`安装资源中`的`数据库初始化.sql`导入到安装好的数据库 2 | 2、安装Java环境,要求JDK版本大于1.7 3 | 3、请修改外置配置文件:application.properties 改成您自己的MySQL配置 4 | 4、Windows通过start.bat运行,Linux运行start.sh运行 5 | 5、如果无意外,可通过:http://localhost:8101 访问到项目了 6 | 6、管理员账号密码:admin/admin 学员账号:person/person -------------------------------------------------------------------------------- /exam-api/docs/部署手册.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/docs/部署手册.pdf -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/ExamApplication.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam; 2 | 3 | import com.yf.exam.core.api.utils.JsonConverter; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.http.converter.HttpMessageConverter; 7 | import org.springframework.scheduling.annotation.EnableAsync; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * 云帆在线考试系统 15 | * @author bool 16 | * @date 2020-03-04 19:41 17 | */ 18 | @SpringBootApplication 19 | @EnableScheduling 20 | @EnableAsync 21 | public class ExamApplication implements WebMvcConfigurer { 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(ExamApplication.class, args); 25 | } 26 | 27 | @Override 28 | public void extendMessageConverters(List> converters) { 29 | //保留原有converter,把新增fastConverter插入集合头,保证优先级 30 | converters.add(0, JsonConverter.fastConverter()); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.cors.CorsConfiguration; 6 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 7 | import org.springframework.web.filter.CorsFilter; 8 | 9 | 10 | /** 11 | * 网关全局设置,允许跨域 12 | * @author bool 13 | * @date 2019-08-13 17:28 14 | */ 15 | @Configuration 16 | public class CorsConfig { 17 | 18 | private CorsConfiguration buildConfig() { 19 | CorsConfiguration corsConfiguration = new CorsConfiguration(); 20 | corsConfiguration.addAllowedOrigin("*"); 21 | corsConfiguration.addAllowedHeader("*"); 22 | corsConfiguration.addAllowedMethod("*"); 23 | return corsConfiguration; 24 | } 25 | 26 | @Bean 27 | public CorsFilter corsFilter() { 28 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 29 | source.registerCorsConfiguration("/**", buildConfig()); 30 | return new CorsFilter(source); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/config/MybatisConfig.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.config; 2 | 3 | import com.yf.exam.aspect.mybatis.QueryInterceptor; 4 | import com.yf.exam.aspect.mybatis.UpdateInterceptor; 5 | import org.mybatis.spring.annotation.MapperScan; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * Mybatis过滤器配置 11 | * 注意:必须按顺序进行配置,否则容易出现业务异常 12 | * @author bool 13 | */ 14 | @Configuration 15 | @MapperScan("com.yf.exam.modules.**.mapper") 16 | public class MybatisConfig { 17 | 18 | /** 19 | * 数据查询过滤器 20 | */ 21 | @Bean 22 | public QueryInterceptor queryInterceptor() { 23 | return new QueryInterceptor(); 24 | } 25 | 26 | /** 27 | * 插入数据过滤器 28 | */ 29 | @Bean 30 | public UpdateInterceptor updateInterceptor() { 31 | return new UpdateInterceptor(); 32 | } 33 | 34 | 35 | } -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/annon/Dict.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.annon; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 数据字典注解 10 | * @author bool 11 | */ 12 | @Target(ElementType.FIELD) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface Dict { 15 | 16 | String dicCode(); 17 | 18 | String dicText() default ""; 19 | 20 | String dictTable() default ""; 21 | } 22 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/annon/LogInject.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.annon; 2 | 3 | 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * 系统日志注入类 12 | */ 13 | @Documented 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ElementType.TYPE, ElementType.METHOD}) 16 | public @interface LogInject { 17 | 18 | /** 19 | * 日志标题 20 | * @return 21 | */ 22 | String title() default "系统日志"; 23 | } 24 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/annon/UserInject.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.annon; 2 | 3 | 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * 注入用户ID 12 | * @author bool 13 | * @date 2019-05-09 14:31 14 | */ 15 | @Documented 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.TYPE, ElementType.METHOD}) 18 | public @interface UserInject { 19 | 20 | /** 21 | * 要注入的参数索引,一般为第一个,极端情况下会使用多个 22 | * @return 23 | */ 24 | int index() default 0; 25 | 26 | /** 27 | * 是否必须的 28 | * @return 29 | */ 30 | boolean required() default true; 31 | 32 | /** 33 | * 需要注入的字段名称,可以是多个 34 | * @return 35 | */ 36 | String [] ids() default {"userId"}; 37 | 38 | /** 39 | * 需要注入的字段名称,可以是多个 40 | * @return 41 | */ 42 | String [] names() default {"userName"}; 43 | } 44 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 请求和响应的基础类,用于处理序列化 9 | * @author dav 10 | * @date 2019/3/16 15:56 11 | */ 12 | @Data 13 | public class BaseDTO implements Serializable { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseIdReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | /** 10 | *

11 | * 主键通用请求类,用于根据ID查询 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2019-04-20 12:15 16 | */ 17 | @Data 18 | @ApiModel(value="主键通用请求类", description="主键通用请求类") 19 | public class BaseIdReqDTO extends BaseDTO { 20 | 21 | 22 | @ApiModelProperty(value = "主键ID", required=true) 23 | private String id; 24 | 25 | @JsonIgnore 26 | private String userId; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseIdRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | /** 11 | *

12 | * 主键通用响应类,用于添加后返回内容 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2019-04-20 12:15 17 | */ 18 | @Data 19 | @ApiModel(value="主键通用响应类", description="主键通用响应类") 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | public class BaseIdRespDTO extends BaseDTO { 23 | 24 | @ApiModelProperty(value = "主键ID", required=true) 25 | private String id; 26 | } 27 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseIdsReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * 通用ID列表类操作,用于批量删除、修改状态等 13 | * @author bool 14 | * @date 2019-08-01 19:07 15 | */ 16 | @Data 17 | @ApiModel(value="删除参数", description="删除参数") 18 | public class BaseIdsReqDTO extends BaseDTO { 19 | 20 | 21 | @JsonIgnore 22 | private String userId; 23 | 24 | @ApiModelProperty(value = "要删除的ID列表", required = true) 25 | private List ids; 26 | } 27 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseIfReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | *

14 | * 是否状态通用请求 15 | *

16 | * 17 | * @author 聪明笨狗 18 | * @since 2019-04-20 12:15 19 | */ 20 | @Data 21 | @ApiModel(value="是否状态通用请求", description="是否状态通用请求") 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | public class BaseIfReqDTO extends BaseDTO { 25 | 26 | 27 | @ApiModelProperty(value = "要修改对象的ID列表", required=true) 28 | private List ids; 29 | 30 | @ApiModelProperty(value = "启用状态,true/false", required=true) 31 | private Boolean enabled; 32 | } 33 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseListDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | /** 10 | * 请求和响应的基础类,用于处理序列化 11 | * @author dav 12 | * @date 2019/3/16 15:56 13 | */ 14 | @Data 15 | public class BaseListDTO implements Serializable { 16 | 17 | @JsonIgnore 18 | private String userId; 19 | 20 | /** 21 | * 数据列表 22 | */ 23 | private List items; 24 | } 25 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseQueryReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.Date; 11 | 12 | /** 13 | *

14 | * 按关键字查询请求通用类 15 | *

16 | * 17 | * @author 聪明笨狗 18 | * @since 2019-04-20 12:15 19 | */ 20 | @Data 21 | @ApiModel(value="按关键字查询请求通用类", description="按关键字查询请求通用类") 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | public class BaseQueryReqDTO extends BaseDTO { 25 | 26 | 27 | @ApiModelProperty(value = "日期开始", required=true) 28 | private Date statDateL; 29 | 30 | @ApiModelProperty(value = "日期结束", required=true) 31 | private Date statDateR; 32 | 33 | @ApiModelProperty(value = "关键字查询", required=true) 34 | private String q; 35 | } 36 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseStateReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | *

14 | * 通用状态请求类,用于修改状态什么的 15 | *

16 | * 17 | * @author 聪明笨狗 18 | * @since 2019-04-20 12:15 19 | */ 20 | @Data 21 | @ApiModel(value="通用状态请求类", description="通用状态请求类") 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | public class BaseStateReqDTO extends BaseDTO { 25 | 26 | 27 | @ApiModelProperty(value = "要修改对象的ID列表", required=true) 28 | private List ids; 29 | 30 | @ApiModelProperty(value = "通用状态,0为正常,1为禁用", required=true) 31 | private Integer state; 32 | } 33 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BaseUserReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | /** 12 | *

13 | * 用户通用请求类 14 | *

15 | * 16 | * @author 聪明笨狗 17 | * @since 2019-04-20 12:15 18 | */ 19 | @Data 20 | @ApiModel(value="用户通用请求类", description="用户通用请求类") 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | public class BaseUserReqDTO extends BaseDTO { 24 | 25 | 26 | @JsonIgnore 27 | @ApiModelProperty(value = "用户ID用来注入用的,可以不传") 28 | private String userId; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/BooleanRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | /** 10 | *

11 | * 主键通用响应类,用于添加后返回内容 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2019-04-20 12:15 16 | */ 17 | @Data 18 | @ApiModel(value="主键通用响应类", description="主键通用响应类") 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | public class BooleanRespDTO extends BaseDTO { 22 | 23 | @ApiModelProperty(value = "主键ID", required=true) 24 | private Boolean effect; 25 | } 26 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/PagingReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | /** 10 | * 分页查询类 11 | * @param 12 | * @author bool 13 | */ 14 | @ApiModel(value="分页参数", description="分页参数") 15 | @Data 16 | public class PagingReqDTO { 17 | 18 | 19 | @ApiModelProperty(value = "当前页码", required = true, example = "1") 20 | private Integer current; 21 | 22 | @ApiModelProperty(value = "每页数量", required = true, example = "10") 23 | private Integer size; 24 | 25 | @ApiModelProperty(value = "查询参数") 26 | private T params; 27 | 28 | @ApiModelProperty(value = "排序字符") 29 | private String orderBy; 30 | 31 | @JsonIgnore 32 | @ApiModelProperty(value = "当前用户的ID") 33 | private String userId; 34 | 35 | /** 36 | * 转换成MyBatis的简单分页对象 37 | * @return 38 | */ 39 | public Page toPage(){ 40 | Page page = new Page(); 41 | page.setCurrent(this.current); 42 | page.setSize(this.size); 43 | return page; 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/PagingRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | 5 | /** 6 | * 分页响应类 7 | * @author bool 8 | * @date 2019-07-20 15:17 9 | * @param 10 | */ 11 | public class PagingRespDTO extends Page { 12 | 13 | /** 14 | * 获取页面总数量 15 | * @return 16 | */ 17 | @Override 18 | public long getPages() { 19 | if (this.getSize() == 0L) { 20 | return 0L; 21 | } else { 22 | long pages = this.getTotal() / this.getSize(); 23 | if (this.getTotal() % this.getSize() != 0L) { 24 | ++pages; 25 | } 26 | return pages; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/dto/PayReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.dto; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | /** 9 | *

10 | * 支付通用请求类 11 | *

12 | * 13 | * @author 聪明笨狗 14 | * @since 2019-04-20 12:15 15 | */ 16 | @Data 17 | @ApiModel(value="支付通用请求类", description="支付通用请求类") 18 | public class PayReqDTO extends BaseDTO { 19 | 20 | @ApiModelProperty(value = "下单成功的订单号", required=true) 21 | private String orderId; 22 | } 23 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/enums/CommonState.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.enums; 2 | 3 | /** 4 | * 通用的状态枚举信息 5 | * 6 | * @author bool 7 | * @date 2019-09-17 17:57 8 | */ 9 | public interface CommonState { 10 | 11 | /** 12 | * 普通状态,正常的 13 | */ 14 | Integer NORMAL = 0; 15 | /** 16 | * 非正常状态,禁用,下架等 17 | */ 18 | Integer ABNORMAL = 1; 19 | } 20 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/api/utils/MsgUtils.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.api.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | import javax.servlet.ServletResponse; 6 | import java.io.IOException; 7 | import java.io.PrintWriter; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * HTTP消息工具类,用于在接口错误时,回写一些数据 13 | * @author bool 14 | * @date 2018/11/22 10:58 15 | */ 16 | public class MsgUtils { 17 | 18 | /** 19 | * 写错误消息 20 | * @param response 21 | * @param code 22 | * @param msg 23 | */ 24 | public static void writeMessage(ServletResponse response, int code, String msg){ 25 | Map map = new HashMap<>(16); 26 | map.put("msg", msg); 27 | map.put("code", code); 28 | 29 | String res = JSON.toJSONString(map); 30 | PrintWriter writer; 31 | 32 | try { 33 | writer = response.getWriter(); 34 | writer.write(res); 35 | writer.close(); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/exception/ServiceException.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.exception; 2 | 3 | import com.yf.exam.core.api.ApiError; 4 | import com.yf.exam.core.api.ApiRest; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class ServiceException extends RuntimeException{ 13 | 14 | /** 15 | * 错误码 16 | */ 17 | private Integer code; 18 | 19 | /** 20 | * 错误消息 21 | */ 22 | private String msg; 23 | 24 | /** 25 | * 从结果初始化 26 | * @param apiRest 27 | */ 28 | public ServiceException(ApiRest apiRest){ 29 | this.code = apiRest.getCode(); 30 | this.msg = apiRest.getMsg(); 31 | } 32 | 33 | /** 34 | * 从枚举中获取参数 35 | * @param apiError 36 | */ 37 | public ServiceException(ApiError apiError){ 38 | this.code = apiError.getCode(); 39 | this.msg = apiError.msg; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/utils/SpringUtils.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.utils; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Spring获取工具 10 | * 11 | * @author bool 12 | * @date 2019-12-09 15:55 13 | */ 14 | @Component 15 | public class SpringUtils implements ApplicationContextAware { 16 | 17 | private static ApplicationContext applicationContext; 18 | 19 | @Override 20 | public void setApplicationContext(ApplicationContext context) throws BeansException { 21 | applicationContext = context; 22 | } 23 | 24 | public static T getBean(Class tClass) { 25 | return applicationContext.getBean(tClass); 26 | } 27 | 28 | public static T getBean(String name, Class type) { 29 | return applicationContext.getBean(name, type); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.utils; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 字符串常用工具类 7 | * @author bool 8 | * @date 2019-05-15 11:40 9 | */ 10 | public class StringUtils { 11 | 12 | /** 13 | * 判断是否为空字符 14 | * @param str 15 | * @return 16 | */ 17 | public static boolean isBlank(String str){ 18 | return str==null || "".equals(str); 19 | } 20 | 21 | 22 | /** 23 | * 将MAP转换成一个xml格式,格式为value... 24 | * @param params 25 | * @return 26 | */ 27 | public static String mapToXml(Map params){ 28 | StringBuffer sb = new StringBuffer(""); 29 | for(String key:params.keySet()){ 30 | sb.append("<") 31 | .append(key).append(">") 32 | .append(params.get(key)) 33 | .append(""); 34 | } 35 | 36 | sb.append(""); 37 | return sb.toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/utils/excel/fieldtype/ListType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2015-2020 JeePlus All rights reserved. 3 | */ 4 | package com.yf.exam.core.utils.excel.fieldtype; 5 | 6 | import com.google.common.collect.Lists; 7 | import com.yf.exam.core.utils.StringUtils; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * 字段类型转换 13 | * @author jeeplus 14 | * @version 2016-5-29 15 | */ 16 | public class ListType { 17 | 18 | /** 19 | * 获取对象值(导入) 20 | */ 21 | public static Object getValue(String val) { 22 | List list = Lists.newArrayList(); 23 | if(!StringUtils.isBlank(val)) { 24 | for (String s : val.split(",")) { 25 | list.add(s); 26 | } 27 | } 28 | return list; 29 | } 30 | 31 | /** 32 | * 设置对象值(导出) 33 | */ 34 | public static String setValue(Object val) { 35 | if (val != null){ 36 | List list = (List)val; 37 | StringBuffer sb = null; 38 | for (String item: list){ 39 | if(StringUtils.isBlank(item)){ 40 | continue; 41 | } 42 | if(sb == null){ 43 | sb = new StringBuffer(item); 44 | }else{ 45 | sb.append(",").append(item); 46 | } 47 | } 48 | 49 | if(sb!=null) { 50 | return sb.toString().replace("[]", ""); 51 | } 52 | } 53 | return ""; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/core/utils/passwd/PassInfo.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.core.utils.passwd; 2 | 3 | /** 4 | * 密码实体 5 | * ClassName: PassInfo
6 | * date: 2018年2月13日 下午7:13:50
7 | * 8 | * @author Bool 9 | * @version 10 | */ 11 | public class PassInfo { 12 | 13 | //密码随机串码 14 | private String salt; 15 | 16 | //MD5后的密码 17 | private String password; 18 | 19 | public PassInfo(String salt, String password) { 20 | super(); 21 | this.salt = salt; 22 | this.password = password; 23 | } 24 | 25 | public String getSalt() { 26 | return salt; 27 | } 28 | public void setSalt(String salt) { 29 | this.salt = salt; 30 | } 31 | public String getPassword() { 32 | return password; 33 | } 34 | public void setPassword(String password) { 35 | this.password = password; 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/Constant.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules; 2 | 3 | 4 | /** 5 | * 通用常量 6 | * @author bool 7 | */ 8 | public class Constant { 9 | 10 | /** 11 | * 会话 12 | */ 13 | public static final String TOKEN = "token"; 14 | } 15 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/enums/JoinType.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.enums; 2 | 3 | /** 4 | * 组卷方式 5 | * @author bool 6 | */ 7 | public interface JoinType { 8 | 9 | 10 | /** 11 | * 题库组题 12 | */ 13 | Integer REPO_JOIN = 1; 14 | } 15 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/enums/OpenType.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.enums; 2 | 3 | /** 4 | * 开放方式 5 | * @author bool 6 | */ 7 | public interface OpenType { 8 | 9 | /** 10 | * 完全开放 11 | */ 12 | Integer OPEN = 1; 13 | 14 | /** 15 | * 部门开放 16 | */ 17 | Integer DEPT_OPEN = 2; 18 | } 19 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 考试部门数据传输类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-09-03 17:24 16 | */ 17 | @Data 18 | @ApiModel(value="考试部门", description="考试部门") 19 | public class ExamDepartDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | @ApiModelProperty(value = "ID", required=true) 25 | private String id; 26 | 27 | @ApiModelProperty(value = "考试ID", required=true) 28 | private String examId; 29 | 30 | @ApiModelProperty(value = "部门ID", required=true) 31 | private String departId; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.dto.request; 2 | 3 | import com.yf.exam.modules.exam.dto.ExamDTO; 4 | import com.yf.exam.modules.exam.dto.ExamRepoDTO; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | *

13 | * 考试保存请求类 14 | *

15 | * 16 | * @author 聪明笨狗 17 | * @since 2020-07-25 16:18 18 | */ 19 | @Data 20 | @ApiModel(value="考试保存请求类", description="考试保存请求类") 21 | public class ExamSaveReqDTO extends ExamDTO { 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | 26 | @ApiModelProperty(value = "题库列表", required=true) 27 | private List repoList; 28 | 29 | @ApiModelProperty(value = "考试部门列表", required=true) 30 | private List departIds; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.dto.response; 2 | 3 | import com.yf.exam.modules.exam.dto.ExamDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import lombok.Data; 6 | 7 | /** 8 | *

9 | * 考试分页响应类 10 | *

11 | * 12 | * @author 聪明笨狗 13 | * @since 2020-07-25 16:18 14 | */ 15 | @Data 16 | @ApiModel(value="在线考试分页响应类", description="在线考试分页响应类") 17 | public class ExamOnlineRespDTO extends ExamDTO { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.dto.response; 2 | 3 | import com.yf.exam.modules.exam.dto.ExamDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | /** 9 | *

10 | * 考试分页响应类 11 | *

12 | * 13 | * @author 聪明笨狗 14 | * @since 2020-07-25 16:18 15 | */ 16 | @Data 17 | @ApiModel(value="阅卷分页响应类", description="阅卷分页响应类") 18 | public class ExamReviewRespDTO extends ExamDTO { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | 23 | @ApiModelProperty(value = "考试人数", required=true) 24 | private Integer examUser; 25 | 26 | @ApiModelProperty(value = "待阅试卷", required=true) 27 | private Integer unreadPaper; 28 | 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.baomidou.mybatisplus.extension.activerecord.Model; 8 | import lombok.Data; 9 | 10 | /** 11 | *

12 | * 考试部门实体类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-09-03 17:24 17 | */ 18 | @Data 19 | @TableName("el_exam_depart") 20 | public class ExamDepart extends Model { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | /** 25 | * ID 26 | */ 27 | @TableId(value = "id", type = IdType.ASSIGN_ID) 28 | private String id; 29 | 30 | /** 31 | * 考试ID 32 | */ 33 | @TableField("exam_id") 34 | private String examId; 35 | 36 | /** 37 | * 部门ID 38 | */ 39 | @TableField("depart_id") 40 | private String departId; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.exam.entity.ExamDepart; 5 | /** 6 | *

7 | * 考试部门Mapper 8 | *

9 | * 10 | * @author 聪明笨狗 11 | * @since 2020-09-03 17:24 12 | */ 13 | public interface ExamDepartMapper extends BaseMapper { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.yf.exam.modules.exam.dto.ExamDTO; 7 | import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO; 8 | import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; 9 | import com.yf.exam.modules.exam.entity.Exam; 10 | import org.apache.ibatis.annotations.Param; 11 | 12 | /** 13 | *

14 | * 考试Mapper 15 | *

16 | * 17 | * @author 聪明笨狗 18 | * @since 2020-07-25 16:18 19 | */ 20 | public interface ExamMapper extends BaseMapper { 21 | 22 | /** 23 | * 查找分页内容 24 | * @param page 25 | * @param query 26 | * @return 27 | */ 28 | IPage paging(Page page, @Param("query") ExamDTO query); 29 | 30 | /** 31 | * 查找分页内容 32 | * @param page 33 | * @param query 34 | * @return 35 | */ 36 | IPage reviewPaging(Page page, @Param("query") ExamDTO query); 37 | 38 | /** 39 | * 在线考试分页响应类-考生视角 40 | * @param page 41 | * @param query 42 | * @return 43 | */ 44 | IPage online(Page page, @Param("query") ExamDTO query); 45 | } 46 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.exam.entity.ExamRepo; 5 | /** 6 | *

7 | * 考试题库Mapper 8 | *

9 | * 10 | * @author 聪明笨狗 11 | * @since 2020-09-05 11:14 12 | */ 13 | public interface ExamRepoMapper extends BaseMapper { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.yf.exam.modules.exam.entity.ExamDepart; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | *

10 | * 考试部门业务类 11 | *

12 | * 13 | * @author 聪明笨狗 14 | * @since 2020-09-03 17:24 15 | */ 16 | public interface ExamDepartService extends IService { 17 | 18 | /** 19 | * 保存全部 20 | * @param examId 21 | * @param departs 22 | */ 23 | void saveAll(String examId, List departs); 24 | 25 | 26 | /** 27 | * 根据考试查找对应的部门 28 | * @param examId 29 | * @return 30 | */ 31 | List listByExam(String examId); 32 | } 33 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.exam.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.yf.exam.modules.exam.dto.ExamRepoDTO; 5 | import com.yf.exam.modules.exam.entity.ExamRepo; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | *

11 | * 考试题库业务类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-09-05 11:14 16 | */ 17 | public interface ExamRepoService extends IService { 18 | 19 | 20 | /** 21 | * 保存全部 22 | * @param examId 23 | * @param list 24 | */ 25 | void saveAll(String examId, List list); 26 | 27 | /** 28 | * 查找考试题库列表 29 | * @param examId 30 | * @return 31 | */ 32 | List listByExam(String examId); 33 | 34 | /** 35 | * 清理脏数据 36 | * @param examId 37 | */ 38 | void clear(String examId); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 试卷考题备选答案请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-05-25 17:31 16 | */ 17 | @Data 18 | @ApiModel(value="试卷考题备选答案", description="试卷考题备选答案") 19 | public class PaperQuAnswerDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | @ApiModelProperty(value = "自增ID", required=true) 25 | private String id; 26 | 27 | @ApiModelProperty(value = "试卷ID", required=true) 28 | private String paperId; 29 | 30 | @ApiModelProperty(value = "回答项ID", required=true) 31 | private String answerId; 32 | 33 | @ApiModelProperty(value = "题目ID", required=true) 34 | private String quId; 35 | 36 | @ApiModelProperty(value = "是否正确项", required=true) 37 | private Boolean isRight; 38 | 39 | @ApiModelProperty(value = "是否选中", required=true) 40 | private Boolean checked; 41 | 42 | @ApiModelProperty(value = "排序", required=true) 43 | private Integer sort; 44 | 45 | @ApiModelProperty(value = "选项标签", required=true) 46 | private String abc; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.ext; 2 | 3 | import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | /** 9 | *

10 | * 试卷考题备选答案请求类 11 | *

12 | * 13 | * @author 聪明笨狗 14 | * @since 2020-05-25 17:31 15 | */ 16 | @Data 17 | @ApiModel(value="试卷考题备选答案", description="试卷考题备选答案") 18 | public class PaperQuAnswerExtDTO extends PaperQuAnswerDTO { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | @ApiModelProperty(value = "答案内容", required=true) 23 | private String content; 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.ext; 2 | 3 | import com.yf.exam.modules.paper.dto.PaperQuDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 试卷考题请求类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 17:31 17 | */ 18 | @Data 19 | @ApiModel(value="试卷题目详情类", description="试卷题目详情类") 20 | public class PaperQuDetailDTO extends PaperQuDTO { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | @ApiModelProperty(value = "题目内容", required=true) 25 | private String content; 26 | 27 | @ApiModelProperty(value = "答案内容", required=true) 28 | List answerList; 29 | } 30 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author bool 11 | */ 12 | @Data 13 | @ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类") 14 | public class PaperAnswerDTO extends PaperQuQueryDTO { 15 | 16 | @ApiModelProperty(value = "回答列表", required=true) 17 | private List answers; 18 | 19 | @ApiModelProperty(value = "主观答案", required=true) 20 | private String answer; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.request; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | /** 10 | * @author bool 11 | */ 12 | @Data 13 | @ApiModel(value="试卷创建请求类", description="试卷创建请求类") 14 | public class PaperCreateReqDTO extends BaseDTO { 15 | 16 | @JsonIgnore 17 | private String userId; 18 | 19 | @ApiModelProperty(value = "考试ID", required=true) 20 | private String examId; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 试卷请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-05-25 17:31 16 | */ 17 | @Data 18 | @ApiModel(value="试卷", description="试卷") 19 | public class PaperListReqDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | @ApiModelProperty(value = "用户ID", required=true) 24 | private String userId; 25 | 26 | @ApiModelProperty(value = "部门ID", required=true) 27 | private String departId; 28 | 29 | @ApiModelProperty(value = "规则ID", required=true) 30 | private String examId; 31 | 32 | @ApiModelProperty(value = "用户昵称", required=true) 33 | private String realName; 34 | 35 | @ApiModelProperty(value = "试卷状态", required=true) 36 | private Integer state; 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.request; 2 | 3 | import com.yf.exam.core.api.dto.BaseDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | /** 9 | * @author bool 10 | */ 11 | @Data 12 | @ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类") 13 | public class PaperQuQueryDTO extends BaseDTO { 14 | 15 | @ApiModelProperty(value = "试卷ID", required=true) 16 | private String paperId; 17 | 18 | @ApiModelProperty(value = "题目ID", required=true) 19 | private String quId; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.response; 2 | 3 | import com.yf.exam.modules.paper.dto.PaperDTO; 4 | import com.yf.exam.modules.paper.dto.PaperQuDTO; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | import java.util.Calendar; 10 | import java.util.List; 11 | 12 | @Data 13 | @ApiModel(value="考试详情", description="考试详情") 14 | public class ExamDetailRespDTO extends PaperDTO { 15 | 16 | 17 | @ApiModelProperty(value = "单选题列表", required=true) 18 | private List radioList; 19 | 20 | @ApiModelProperty(value = "多选题列表", required=true) 21 | private List multiList; 22 | 23 | @ApiModelProperty(value = "判断题", required=true) 24 | private List judgeList; 25 | 26 | 27 | @ApiModelProperty(value = "剩余结束秒数", required=true) 28 | public Long getLeftSeconds(){ 29 | 30 | // 结束时间 31 | Calendar cl = Calendar.getInstance(); 32 | cl.setTime(this.getCreateTime()); 33 | cl.add(Calendar.MINUTE, getTotalTime()); 34 | 35 | return (cl.getTimeInMillis() - System.currentTimeMillis()) / 1000; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.response; 2 | 3 | import com.yf.exam.modules.paper.dto.PaperDTO; 4 | import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @ApiModel(value="考试结果展示响应类", description="考试结果展示响应类") 13 | public class ExamResultRespDTO extends PaperDTO { 14 | 15 | @ApiModelProperty(value = "问题列表", required=true) 16 | private List quList; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.dto.response; 2 | 3 | import com.yf.exam.modules.paper.dto.PaperDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | /** 9 | *

10 | * 试卷请求类 11 | *

12 | * 13 | * @author 聪明笨狗 14 | * @since 2020-05-25 17:31 15 | */ 16 | @Data 17 | @ApiModel(value="试卷列表响应类", description="试卷列表响应类") 18 | public class PaperListRespDTO extends PaperDTO { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | @ApiModelProperty(value = "人员", required=true) 23 | private String realName; 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.enums; 2 | 3 | 4 | /** 5 | * 考试状态 6 | * @author bool 7 | * @date 2019-10-30 13:11 8 | */ 9 | public interface ExamState { 10 | 11 | 12 | /** 13 | * 考试中 14 | */ 15 | Integer ENABLE = 0; 16 | 17 | /** 18 | * 待阅卷 19 | */ 20 | Integer DISABLED = 1; 21 | 22 | /** 23 | * 已完成 24 | */ 25 | Integer READY_START = 2; 26 | 27 | /** 28 | * 已结束 29 | */ 30 | Integer OVERDUE = 3; 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.enums; 2 | 3 | 4 | /** 5 | * 试卷状态 6 | * @author bool 7 | * @date 2019-10-30 13:11 8 | */ 9 | public interface PaperState { 10 | 11 | 12 | /** 13 | * 考试中 14 | */ 15 | Integer ING = 0; 16 | 17 | /** 18 | * 待阅卷 19 | */ 20 | Integer WAIT_OPT = 1; 21 | 22 | /** 23 | * 已完成 24 | */ 25 | Integer FINISHED = 2; 26 | 27 | /** 28 | * 弃考 29 | */ 30 | Integer BREAK = 3; 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.yf.exam.modules.paper.dto.PaperDTO; 7 | import com.yf.exam.modules.paper.dto.request.PaperListReqDTO; 8 | import com.yf.exam.modules.paper.dto.response.PaperListRespDTO; 9 | import com.yf.exam.modules.paper.entity.Paper; 10 | import org.apache.ibatis.annotations.Param; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | *

16 | * 试卷Mapper 17 | *

18 | * 19 | * @author 聪明笨狗 20 | * @since 2020-05-25 16:33 21 | */ 22 | public interface PaperMapper extends BaseMapper { 23 | 24 | /** 25 | * 查找试卷分页 26 | * @param page 27 | * @param query 28 | * @return 29 | */ 30 | IPage paging(Page page, @Param("query") PaperListReqDTO query); 31 | 32 | 33 | /** 34 | * 试卷列表响应类 35 | * @param query 36 | * @return 37 | */ 38 | List list(@Param("query") PaperDTO query); 39 | } 40 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; 5 | import com.yf.exam.modules.paper.entity.PaperQuAnswer; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 试卷考题备选答案Mapper 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 16:33 17 | */ 18 | public interface PaperQuAnswerMapper extends BaseMapper { 19 | 20 | /** 21 | * 查找试卷试题答案列表 22 | * @param paperId 23 | * @param quId 24 | * @return 25 | */ 26 | List list(@Param("paperId") String paperId, @Param("quId") String quId); 27 | } 28 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO; 5 | import com.yf.exam.modules.paper.entity.PaperQu; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 试卷考题Mapper 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 16:33 17 | */ 18 | public interface PaperQuMapper extends BaseMapper { 19 | 20 | /** 21 | * 统计客观分 22 | * @param paperId 23 | * @return 24 | */ 25 | int sumObjective(@Param("paperId") String paperId); 26 | 27 | /** 28 | * 统计主观分 29 | * @param paperId 30 | * @return 31 | */ 32 | int sumSubjective(@Param("paperId") String paperId); 33 | 34 | /** 35 | * 找出全部试题列表 36 | * @param paperId 37 | * @return 38 | */ 39 | List listByPaper(@Param("paperId") String paperId); 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.paper.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.yf.exam.core.api.dto.PagingReqDTO; 6 | import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO; 7 | import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO; 8 | import com.yf.exam.modules.paper.entity.PaperQuAnswer; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | *

14 | * 试卷考题备选答案业务类 15 | *

16 | * 17 | * @author 聪明笨狗 18 | * @since 2020-05-25 16:33 19 | */ 20 | public interface PaperQuAnswerService extends IService { 21 | 22 | /** 23 | * 分页查询数据 24 | * @param reqDTO 25 | * @return 26 | */ 27 | IPage paging(PagingReqDTO reqDTO); 28 | 29 | /** 30 | * 查找试卷试题答案列表 31 | * @param paperId 32 | * @param quId 33 | * @return 34 | */ 35 | List listForExam(String paperId, String quId); 36 | 37 | /** 38 | * 查找答案列表,用来填充 39 | * @param paperId 40 | * @param quId 41 | * @return 42 | */ 43 | List listForFill(String paperId, String quId); 44 | } 45 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/dto/QuAnswerDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 候选答案请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-05-25 13:23 16 | */ 17 | @Data 18 | @ApiModel(value="候选答案", description="候选答案") 19 | public class QuAnswerDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | @ApiModelProperty(value = "答案ID", required=true) 25 | private String id; 26 | 27 | @ApiModelProperty(value = "问题ID", required=true) 28 | private String quId; 29 | 30 | @ApiModelProperty(value = "是否正确", required=true) 31 | private Boolean isRight; 32 | 33 | @ApiModelProperty(value = "答案内容", required=true) 34 | private String content; 35 | 36 | @ApiModelProperty(value = "答案分析", required=true) 37 | private String analysis; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/dto/QuDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | /** 11 | *

12 | * 问题题目请求类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 13:23 17 | */ 18 | @Data 19 | @ApiModel(value="问题题目", description="问题题目") 20 | public class QuDTO implements Serializable { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | 25 | @ApiModelProperty(value = "题目ID", required=true) 26 | private String id; 27 | 28 | @ApiModelProperty(value = "题目类型", required=true) 29 | private Integer quType; 30 | 31 | @ApiModelProperty(value = "1普通,2较难", required=true) 32 | private Integer level; 33 | 34 | @ApiModelProperty(value = "题目内容", required=true) 35 | private String content; 36 | 37 | 38 | @ApiModelProperty(value = "创建时间", required=true) 39 | private Date createTime; 40 | 41 | @ApiModelProperty(value = "更新时间", required=true) 42 | private Date updateTime; 43 | 44 | @ApiModelProperty(value = "题目备注", required=true) 45 | private String remark; 46 | 47 | @ApiModelProperty(value = "整题解析", required=true) 48 | private String analysis; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/dto/QuRepoDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 试题题库请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-05-25 13:23 16 | */ 17 | @Data 18 | @ApiModel(value="试题题库", description="试题题库") 19 | public class QuRepoDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | private String id; 25 | 26 | @ApiModelProperty(value = "试题", required=true) 27 | private String quId; 28 | 29 | @ApiModelProperty(value = "归属题库", required=true) 30 | private String repoId; 31 | 32 | @ApiModelProperty(value = "题目类型", required=true) 33 | private Integer quType; 34 | 35 | @ApiModelProperty(value = "排序", required=true) 36 | private Integer sort; 37 | 38 | } -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/dto/export/QuImportDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.dto.export; 2 | 3 | import com.yf.exam.modules.qu.dto.QuAnswerDTO; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 用于导出的数据结构 10 | * @author bool 11 | */ 12 | @Data 13 | public class QuImportDTO { 14 | 15 | private static final long serialVersionUID = 1L; 16 | 17 | private String quType; 18 | private String qContent; 19 | private String qAnalysis; 20 | private String qImage; 21 | private String repoName; 22 | private List answerList; 23 | } 24 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/dto/ext/QuDetailDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.dto.ext; 2 | 3 | import com.yf.exam.modules.qu.dto.QuAnswerDTO; 4 | import com.yf.exam.modules.qu.dto.QuDTO; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.Data; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | *

13 | * 问题题目请求类 14 | *

15 | * 16 | * @author 聪明笨狗 17 | * @since 2020-05-25 13:23 18 | */ 19 | @Data 20 | @ApiModel(value="问题题目详情", description="问题题目详情") 21 | public class QuDetailDTO extends QuDTO { 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | 26 | @ApiModelProperty(value = "备选项列表", required=true) 27 | private List answerList; 28 | 29 | @ApiModelProperty(value = "题库列表", required=true) 30 | private List repoIds; 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/dto/request/QuQueryReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.dto.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 问题题目请求类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 13:23 17 | */ 18 | @Data 19 | @ApiModel(value="题目查询请求类", description="题目查询请求类") 20 | public class QuQueryReqDTO implements Serializable { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | 25 | @ApiModelProperty(value = "题目类型") 26 | private Integer quType; 27 | 28 | @ApiModelProperty(value = "归属题库") 29 | private List repoIds; 30 | 31 | @ApiModelProperty(value = "题目内容") 32 | private String content; 33 | 34 | @ApiModelProperty(value = "排除ID列表") 35 | private List excludes; 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/dto/request/QuRepoBatchReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.dto.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 问题题目请求类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 13:23 17 | */ 18 | @Data 19 | @ApiModel(value="试题题库批量操作类", description="试题题库批量操作类") 20 | public class QuRepoBatchReqDTO implements Serializable { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | 25 | @ApiModelProperty(value = "题目ID", required=true) 26 | private List quIds; 27 | 28 | @ApiModelProperty(value = "题目类型", required=true) 29 | private List repoIds; 30 | 31 | @ApiModelProperty(value = "是否移除,否就新增;是就移除", required=true) 32 | private Boolean remove; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/entity/QuAnswer.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.baomidou.mybatisplus.extension.activerecord.Model; 8 | import lombok.Data; 9 | 10 | /** 11 | *

12 | * 候选答案实体类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 13:23 17 | */ 18 | @Data 19 | @TableName("el_qu_answer") 20 | public class QuAnswer extends Model { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | /** 25 | * 答案ID 26 | */ 27 | @TableId(value = "id", type = IdType.ID_WORKER_STR) 28 | private String id; 29 | 30 | /** 31 | * 问题ID 32 | */ 33 | @TableField("qu_id") 34 | private String quId; 35 | 36 | /** 37 | * 是否正确 38 | */ 39 | @TableField("is_right") 40 | private Boolean isRight; 41 | 42 | /** 43 | * 答案内容 44 | */ 45 | private String content; 46 | 47 | 48 | /** 49 | * 答案分析 50 | */ 51 | private String analysis; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/entity/QuRepo.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.baomidou.mybatisplus.extension.activerecord.Model; 8 | import lombok.Data; 9 | 10 | /** 11 | *

12 | * 试题题库实体类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 13:23 17 | */ 18 | @Data 19 | @TableName("el_qu_repo") 20 | public class QuRepo extends Model { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | @TableId(value = "id", type = IdType.ID_WORKER_STR) 25 | private String id; 26 | 27 | /** 28 | * 试题 29 | */ 30 | @TableField("qu_id") 31 | private String quId; 32 | 33 | /** 34 | * 归属题库 35 | */ 36 | @TableField("repo_id") 37 | private String repoId; 38 | 39 | /** 40 | * 题目类型 41 | */ 42 | @TableField("qu_type") 43 | private Integer quType; 44 | 45 | /** 46 | * 排序 47 | */ 48 | private Integer sort; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/enums/QuType.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.enums; 2 | 3 | 4 | /** 5 | * 题目类型 6 | * @author bool 7 | * @date 2019-10-30 13:11 8 | */ 9 | public interface QuType { 10 | 11 | /** 12 | * 单选题 13 | */ 14 | Integer RADIO = 1; 15 | 16 | /** 17 | * 多选题 18 | */ 19 | Integer MULTI = 2; 20 | 21 | /** 22 | * 判断题 23 | */ 24 | Integer JUDGE = 3; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/mapper/QuAnswerMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.qu.entity.QuAnswer; 5 | 6 | /** 7 | *

8 | * 候选答案Mapper 9 | *

10 | * 11 | * @author 聪明笨狗 12 | * @since 2020-05-25 13:23 13 | */ 14 | public interface QuAnswerMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/mapper/QuRepoMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.qu.entity.QuRepo; 5 | 6 | /** 7 | *

8 | * 试题题库Mapper 9 | *

10 | * 11 | * @author 聪明笨狗 12 | * @since 2020-05-25 13:23 13 | */ 14 | public interface QuRepoMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/qu/service/QuAnswerService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.qu.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.yf.exam.core.api.dto.PagingReqDTO; 6 | import com.yf.exam.modules.qu.dto.QuAnswerDTO; 7 | import com.yf.exam.modules.qu.entity.QuAnswer; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | *

13 | * 候选答案业务类 14 | *

15 | * 16 | * @author 聪明笨狗 17 | * @since 2020-05-25 13:23 18 | */ 19 | public interface QuAnswerService extends IService { 20 | 21 | /** 22 | * 分页查询数据 23 | * @param reqDTO 24 | * @return 25 | */ 26 | IPage paging(PagingReqDTO reqDTO); 27 | 28 | /** 29 | * 根据题目ID查询答案并随机 30 | * @param quId 31 | * @return 32 | */ 33 | List listAnswerByRandom(String quId); 34 | 35 | /** 36 | * 根据问题查找答案 37 | * @param quId 38 | * @return 39 | */ 40 | List listByQu(String quId); 41 | 42 | /** 43 | * 保存试题 44 | * @param quId 45 | * @param list 46 | */ 47 | void saveAll(String quId, List list); 48 | } 49 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/repo/dto/response/RepoRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.repo.dto.response; 2 | 3 | import com.yf.exam.modules.repo.dto.RepoDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | /** 9 | *

10 | * 题库请求类 11 | *

12 | * 13 | * @author 聪明笨狗 14 | * @since 2020-05-25 13:23 15 | */ 16 | @Data 17 | @ApiModel(value="题库分页请求类", description="题库分页请求类") 18 | public class RepoRespDTO extends RepoDTO { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | @ApiModelProperty(value = "多选题数量", required=true) 23 | private Integer multiCount; 24 | 25 | @ApiModelProperty(value = "单选题数量", required=true) 26 | private Integer radioCount; 27 | 28 | @ApiModelProperty(value = "判断题数量", required=true) 29 | private Integer judgeCount; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/repo/mapper/RepoMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.repo.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.yf.exam.modules.repo.dto.RepoDTO; 7 | import com.yf.exam.modules.repo.dto.response.RepoRespDTO; 8 | import com.yf.exam.modules.repo.entity.Repo; 9 | import org.apache.ibatis.annotations.Param; 10 | 11 | /** 12 | *

13 | * 题库Mapper 14 | *

15 | * 16 | * @author 聪明笨狗 17 | * @since 2020-05-25 13:23 18 | */ 19 | public interface RepoMapper extends BaseMapper { 20 | 21 | /** 22 | * 分页查询题库 23 | * @param page 24 | * @param query 25 | * @return 26 | */ 27 | IPage paging(Page page, @Param("query") RepoDTO query); 28 | 29 | /** 30 | * 更新统计数量 31 | * @param repoId 32 | */ 33 | void refreshStat(@Param("repoId") String repoId); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/repo/service/RepoService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.repo.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.yf.exam.core.api.dto.PagingReqDTO; 6 | import com.yf.exam.modules.repo.dto.RepoDTO; 7 | import com.yf.exam.modules.repo.dto.response.RepoRespDTO; 8 | import com.yf.exam.modules.repo.entity.Repo; 9 | 10 | /** 11 | *

12 | * 题库业务类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-05-25 13:23 17 | */ 18 | public interface RepoService extends IService { 19 | 20 | /** 21 | * 分页查询数据 22 | * @param reqDTO 23 | * @return 24 | */ 25 | IPage paging(PagingReqDTO reqDTO); 26 | 27 | 28 | /** 29 | * 保存 30 | * @param reqDTO 31 | */ 32 | void save(RepoDTO reqDTO); 33 | 34 | /** 35 | * 更新统计数量 36 | * @param repoId 37 | */ 38 | void refreshStat(String repoId); 39 | 40 | 41 | /** 42 | * 根据名称查找题库 43 | * @param name 44 | * @return 45 | */ 46 | String findByName(String name); 47 | } 48 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/shiro/jwt/JwtToken.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.shiro.jwt; 2 | 3 | import lombok.Data; 4 | import org.apache.shiro.authc.AuthenticationToken; 5 | 6 | /** 7 | * @author bool 8 | */ 9 | @Data 10 | public class JwtToken implements AuthenticationToken { 11 | 12 | private static final long serialVersionUID = 1L; 13 | 14 | /** 15 | * JWT的字符token 16 | */ 17 | private String token; 18 | 19 | 20 | public JwtToken(String token) { 21 | this.token = token; 22 | } 23 | 24 | @Override 25 | public Object getPrincipal() { 26 | return token; 27 | } 28 | 29 | @Override 30 | public Object getCredentials() { 31 | return token; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/config/dto/SysConfigDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.config.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 通用配置请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-04-17 09:12 16 | */ 17 | @Data 18 | @ApiModel(value="通用配置", description="通用配置") 19 | public class SysConfigDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | @ApiModelProperty(value = "ID", required=true) 25 | private String id; 26 | 27 | @ApiModelProperty(value = "系统名称") 28 | private String siteName; 29 | 30 | @ApiModelProperty(value = "前端LOGO") 31 | private String frontLogo; 32 | 33 | @ApiModelProperty(value = "后台LOGO") 34 | private String backLogo; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/config/entity/SysConfig.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.config.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.baomidou.mybatisplus.extension.activerecord.Model; 8 | import lombok.Data; 9 | 10 | /** 11 | *

12 | * 通用配置实体类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-04-17 09:12 17 | */ 18 | @Data 19 | @TableName("sys_config") 20 | public class SysConfig extends Model { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | /** 25 | * ID 26 | */ 27 | @TableId(value = "id", type = IdType.ID_WORKER_STR) 28 | private String id; 29 | 30 | /** 31 | * 系统名称 32 | */ 33 | @TableField("site_name") 34 | private String siteName; 35 | 36 | /** 37 | * 前端LOGO 38 | */ 39 | @TableField("front_logo") 40 | private String frontLogo; 41 | 42 | /** 43 | * 后台LOGO 44 | */ 45 | @TableField("back_logo") 46 | private String backLogo; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/config/mapper/SysConfigMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.config.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.sys.config.entity.SysConfig; 5 | 6 | /** 7 | *

8 | * 通用配置Mapper 9 | *

10 | * 11 | * @author 聪明笨狗 12 | * @since 2020-04-17 09:12 13 | */ 14 | public interface SysConfigMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/config/service/SysConfigService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.config.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.yf.exam.modules.sys.config.dto.SysConfigDTO; 5 | import com.yf.exam.modules.sys.config.entity.SysConfig; 6 | 7 | /** 8 | *

9 | * 通用配置业务类 10 | *

11 | * 12 | * @author 聪明笨狗 13 | * @since 2020-04-17 09:12 14 | */ 15 | public interface SysConfigService extends IService { 16 | 17 | /** 18 | * 查找配置信息 19 | * @return 20 | */ 21 | SysConfigDTO find(); 22 | } 23 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/config/service/impl/SysConfigServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.config.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.yf.exam.core.utils.BeanMapper; 6 | import com.yf.exam.modules.sys.config.dto.SysConfigDTO; 7 | import com.yf.exam.modules.sys.config.entity.SysConfig; 8 | import com.yf.exam.modules.sys.config.mapper.SysConfigMapper; 9 | import com.yf.exam.modules.sys.config.service.SysConfigService; 10 | import org.springframework.stereotype.Service; 11 | 12 | /** 13 | *

14 | * 语言设置 服务实现类 15 | *

16 | * 17 | * @author 聪明笨狗 18 | * @since 2020-04-17 09:12 19 | */ 20 | @Service 21 | public class SysConfigServiceImpl extends ServiceImpl implements SysConfigService { 22 | 23 | @Override 24 | public SysConfigDTO find() { 25 | 26 | QueryWrapper wrapper = new QueryWrapper<>(); 27 | wrapper.last(" LIMIT 1"); 28 | 29 | SysConfig entity = this.getOne(wrapper, false); 30 | SysConfigDTO dto = new SysConfigDTO(); 31 | BeanMapper.copy(entity, dto); 32 | return dto; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/depart/dto/SysDepartDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.depart.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 部门信息数据传输类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-09-02 17:25 16 | */ 17 | @Data 18 | @ApiModel(value="部门信息", description="部门信息") 19 | public class SysDepartDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | @ApiModelProperty(value = "ID", required=true) 25 | private String id; 26 | 27 | @ApiModelProperty(value = "1公司2部门", required=true) 28 | private Integer deptType; 29 | 30 | @ApiModelProperty(value = "所属上级", required=true) 31 | private String parentId; 32 | 33 | @ApiModelProperty(value = "部门名称", required=true) 34 | private String deptName; 35 | 36 | @ApiModelProperty(value = "部门编码", required=true) 37 | private String deptCode; 38 | 39 | @ApiModelProperty(value = "排序", required=true) 40 | private Integer sort; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/depart/dto/request/DepartSortReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.depart.dto.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 部门排序请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-03-14 10:37 16 | */ 17 | @Data 18 | @ApiModel(value="部门排序请求类", description="部门排序请求类") 19 | public class DepartSortReqDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | @ApiModelProperty(value = "分类ID") 24 | private String id; 25 | 26 | @ApiModelProperty(value = "排序,0下降,1上升") 27 | private Integer sort; 28 | } 29 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/depart/dto/response/SysDepartTreeDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.depart.dto.response; 2 | 3 | import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 部门树结构响应类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-09-02 17:25 17 | */ 18 | @Data 19 | @ApiModel(value="部门树结构响应类", description="部门树结构响应类") 20 | public class SysDepartTreeDTO extends SysDepartDTO { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | @ApiModelProperty(value = "子列表", required=true) 25 | private List children; 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/depart/mapper/SysDepartMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.depart.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.yf.exam.modules.sys.depart.dto.SysDepartDTO; 7 | import com.yf.exam.modules.sys.depart.dto.response.SysDepartTreeDTO; 8 | import com.yf.exam.modules.sys.depart.entity.SysDepart; 9 | import org.apache.ibatis.annotations.Param; 10 | 11 | /** 12 | *

13 | * 部门信息Mapper 14 | *

15 | * 16 | * @author 聪明笨狗 17 | * @since 2020-09-02 17:25 18 | */ 19 | public interface SysDepartMapper extends BaseMapper { 20 | 21 | /** 22 | * 部门树分页 23 | * @param page 24 | * @param query 25 | * @return 26 | */ 27 | IPage paging(Page page, @Param("query") SysDepartDTO query); 28 | } 29 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/system/mapper/SysDictMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.system.mapper; 2 | 3 | import org.apache.ibatis.annotations.Mapper; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | /** 7 | *

8 | * 机主信息Mapper 9 | *

10 | * 11 | * @author 聪明笨狗 12 | * @since 2020-08-22 13:46 13 | */ 14 | @Mapper 15 | public interface SysDictMapper { 16 | 17 | /** 18 | * 查找数据字典 19 | * @param table 20 | * @param text 21 | * @param key 22 | * @param value 23 | * @return 24 | */ 25 | String findDict(@Param("table") String table, 26 | @Param("text") String text, 27 | @Param("key") String key, 28 | @Param("value") String value); 29 | } 30 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/system/service/SysDictService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.system.service; 2 | 3 | /** 4 | * 数据字典工具类 5 | * @author bool 6 | */ 7 | public interface SysDictService { 8 | 9 | /** 10 | * 查找数据字典 11 | * @param table 12 | * @param text 13 | * @param key 14 | * @param value 15 | * @return 16 | */ 17 | String findDict(String table, 18 | String text, 19 | String key, 20 | String value); 21 | } 22 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/system/service/impl/SysDictServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.system.service.impl; 2 | 3 | import com.yf.exam.modules.sys.system.mapper.SysDictMapper; 4 | import com.yf.exam.modules.sys.system.service.SysDictService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | /** 9 | * @author bool 10 | */ 11 | @Service 12 | public class SysDictServiceImpl implements SysDictService { 13 | 14 | @Autowired 15 | private SysDictMapper sysDictMapper; 16 | 17 | @Override 18 | public String findDict(String table, String text, String key, String value) { 19 | return sysDictMapper.findDict(table, text, key, value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/dto/SysRoleDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 角色请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-04-13 16:57 16 | */ 17 | @Data 18 | @ApiModel(value="角色", description="角色") 19 | public class SysRoleDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | @ApiModelProperty(value = "角色ID", required=true) 25 | private String id; 26 | 27 | @ApiModelProperty(value = "角色名称", required=true) 28 | private String roleName; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/dto/SysUserRoleDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 用户角色请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-04-13 16:57 16 | */ 17 | @Data 18 | @ApiModel(value="用户角色", description="用户角色") 19 | public class SysUserRoleDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | @ApiModelProperty(value = "ID", required=true) 25 | private String id; 26 | 27 | @ApiModelProperty(value = "用户ID", required=true) 28 | private String userId; 29 | 30 | @ApiModelProperty(value = "角色ID", required=true) 31 | private String roleId; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserLoginReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.dto.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 管理员登录请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-04-13 16:57 16 | */ 17 | @Data 18 | @ApiModel(value="管理员登录请求类", description="管理员登录请求类") 19 | public class SysUserLoginReqDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | @ApiModelProperty(value = "用户名", required=true) 24 | private String username; 25 | 26 | @ApiModelProperty(value = "密码", required=true) 27 | private String password; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserSaveReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.dto.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 管理员登录请求类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-04-13 16:57 17 | */ 18 | @Data 19 | @ApiModel(value="管理员保存请求类", description="管理员保存请求类") 20 | public class SysUserSaveReqDTO implements Serializable { 21 | 22 | @ApiModelProperty(value = "ID", required=true) 23 | private String id; 24 | 25 | @ApiModelProperty(value = "用户名", required=true) 26 | private String userName; 27 | 28 | @ApiModelProperty(value = "头像", required=true) 29 | private String avatar; 30 | 31 | @ApiModelProperty(value = "真实姓名", required=true) 32 | private String realName; 33 | 34 | @ApiModelProperty(value = "密码", required=true) 35 | private String password; 36 | 37 | @ApiModelProperty(value = "部门", required=true) 38 | private String departId; 39 | 40 | @ApiModelProperty(value = "角色列表", required=true) 41 | private List roles; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/dto/request/SysUserTokenReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.dto.request; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | *

11 | * 会话检查请求类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-04-13 16:57 16 | */ 17 | @Data 18 | @ApiModel(value="会话检查请求类", description="会话检查请求类") 19 | public class SysUserTokenReqDTO implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | @ApiModelProperty(value = "用户名", required=true) 24 | private String token; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/entity/SysRole.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.baomidou.mybatisplus.extension.activerecord.Model; 8 | import lombok.Data; 9 | 10 | /** 11 | *

12 | * 角色实体类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-04-13 16:57 17 | */ 18 | @Data 19 | @TableName("sys_role") 20 | public class SysRole extends Model { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | /** 25 | * 角色ID 26 | */ 27 | @TableId(value = "id", type = IdType.ASSIGN_ID) 28 | private String id; 29 | 30 | /** 31 | * 角色名称 32 | */ 33 | @TableField("role_name") 34 | private String roleName; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/entity/SysUserRole.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.baomidou.mybatisplus.extension.activerecord.Model; 8 | import lombok.Data; 9 | 10 | /** 11 | *

12 | * 用户角色实体类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-04-13 16:57 17 | */ 18 | @Data 19 | @TableName("sys_user_role") 20 | public class SysUserRole extends Model { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | /** 25 | * ID 26 | */ 27 | @TableId(value = "id", type = IdType.ASSIGN_ID) 28 | private String id; 29 | 30 | /** 31 | * 用户ID 32 | */ 33 | @TableField("user_id") 34 | private String userId; 35 | 36 | /** 37 | * 角色ID 38 | */ 39 | @TableField("role_id") 40 | private String roleId; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/mapper/SysRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.mapper; 2 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 3 | import com.yf.exam.modules.sys.user.entity.SysRole; 4 | 5 | /** 6 | *

7 | * 角色Mapper 8 | *

9 | * 10 | * @author 聪明笨狗 11 | * @since 2020-04-13 16:57 12 | */ 13 | public interface SysRoleMapper extends BaseMapper { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/mapper/SysUserMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.sys.user.entity.SysUser; 5 | 6 | /** 7 | *

8 | * 管理用户Mapper 9 | *

10 | * 11 | * @author 聪明笨狗 12 | * @since 2020-04-13 16:57 13 | */ 14 | public interface SysUserMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/mapper/SysUserRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.sys.user.entity.SysUserRole; 5 | 6 | /** 7 | *

8 | * 用户角色Mapper 9 | *

10 | * 11 | * @author 聪明笨狗 12 | * @since 2020-04-13 16:57 13 | */ 14 | public interface SysUserRoleMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/sys/user/service/SysRoleService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.sys.user.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.yf.exam.modules.sys.user.dto.SysRoleDTO; 6 | import com.yf.exam.modules.sys.user.entity.SysRole; 7 | import com.yf.exam.core.api.dto.PagingReqDTO; 8 | 9 | /** 10 | *

11 | * 角色业务类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-04-13 16:57 16 | */ 17 | public interface SysRoleService extends IService { 18 | 19 | /** 20 | * 分页查询数据 21 | * @param reqDTO 22 | * @return 23 | */ 24 | IPage paging(PagingReqDTO reqDTO); 25 | } 26 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/user/book/mapper/UserBookMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.user.book.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yf.exam.modules.user.book.entity.UserBook; 5 | 6 | /** 7 | *

8 | * 错题本Mapper 9 | *

10 | * 11 | * @author 聪明笨狗 12 | * @since 2020-05-27 17:56 13 | */ 14 | public interface UserBookMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/user/book/service/UserBookService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.user.book.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.yf.exam.core.api.dto.PagingReqDTO; 6 | import com.yf.exam.modules.user.book.dto.UserBookDTO; 7 | import com.yf.exam.modules.user.book.entity.UserBook; 8 | 9 | /** 10 | *

11 | * 错题本业务类 12 | *

13 | * 14 | * @author 聪明笨狗 15 | * @since 2020-05-27 17:56 16 | */ 17 | public interface UserBookService extends IService { 18 | 19 | /** 20 | * 分页查询数据 21 | * @param reqDTO 22 | * @return 23 | */ 24 | IPage paging(PagingReqDTO reqDTO); 25 | 26 | /** 27 | * 加入错题本 28 | * @param quId 29 | * @param examId 30 | */ 31 | void addBook(String examId, String quId); 32 | 33 | /** 34 | * 查找第一个错题 35 | * @param quId 36 | * @param examId 37 | * @return 38 | */ 39 | String findNext(String examId, String quId); 40 | } 41 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/user/exam/dto/request/UserExamReqDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.user.exam.dto.request; 2 | 3 | import com.yf.exam.modules.user.exam.dto.UserExamDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | /** 9 | *

10 | * 考试记录数据传输类 11 | *

12 | * 13 | * @author 聪明笨狗 14 | * @since 2020-09-21 15:13 15 | */ 16 | @Data 17 | @ApiModel(value="考试记录", description="考试记录") 18 | public class UserExamReqDTO extends UserExamDTO { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | 23 | @ApiModelProperty(value = "考试名称", required=true) 24 | private String title; 25 | 26 | @ApiModelProperty(value = "人员名称", required=true) 27 | private String realName; 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/user/exam/dto/response/UserExamRespDTO.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.user.exam.dto.response; 2 | 3 | import com.yf.exam.modules.user.exam.dto.UserExamDTO; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | 8 | /** 9 | *

10 | * 考试记录数据传输类 11 | *

12 | * 13 | * @author 聪明笨狗 14 | * @since 2020-09-21 15:13 15 | */ 16 | @Data 17 | @ApiModel(value="考试记录", description="考试记录") 18 | public class UserExamRespDTO extends UserExamDTO { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | 23 | @ApiModelProperty(value = "考试名称", required=true) 24 | private String title; 25 | 26 | @ApiModelProperty(value = "人员名称", required=true) 27 | private String realName; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/user/exam/mapper/UserExamMapper.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.user.exam.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; 7 | import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; 8 | import com.yf.exam.modules.user.exam.entity.UserExam; 9 | import org.apache.ibatis.annotations.Param; 10 | 11 | /** 12 | *

13 | * 考试记录Mapper 14 | *

15 | * 16 | * @author 聪明笨狗 17 | * @since 2020-09-21 15:13 18 | */ 19 | public interface UserExamMapper extends BaseMapper { 20 | 21 | /** 22 | * 我的考试分页 23 | * @param page 24 | * @param query 25 | * @return 26 | */ 27 | IPage paging(Page page, @Param("query") UserExamReqDTO query); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /exam-api/src/main/java/com/yf/exam/modules/user/exam/service/UserExamService.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam.modules.user.exam.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.yf.exam.core.api.dto.PagingReqDTO; 6 | import com.yf.exam.modules.user.exam.dto.request.UserExamReqDTO; 7 | import com.yf.exam.modules.user.exam.dto.response.UserExamRespDTO; 8 | import com.yf.exam.modules.user.exam.entity.UserExam; 9 | 10 | /** 11 | *

12 | * 考试记录业务类 13 | *

14 | * 15 | * @author 聪明笨狗 16 | * @since 2020-09-21 15:13 17 | */ 18 | public interface UserExamService extends IService { 19 | 20 | /** 21 | * 分页查询数据 22 | * @param reqDTO 23 | * @return 24 | */ 25 | IPage paging(PagingReqDTO reqDTO); 26 | 27 | /** 28 | * 分页查询数据 29 | * @param reqDTO 30 | * @return 31 | */ 32 | IPage myPaging(PagingReqDTO reqDTO); 33 | 34 | 35 | /** 36 | * 考试完成后加入成绩 37 | * @param userId 38 | * @param examId 39 | * @param score 40 | * @param passed 41 | */ 42 | void joinResult(String userId, String examId, Integer score, boolean passed); 43 | } 44 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 端口基础配置 2 | server.port=8101 3 | spring.application.name=yf_exam 4 | 5 | # 数据库配置 6 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 7 | spring.datasource.url=jdbc:mysql://localhost:3306/yf_exam_lite?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai 8 | spring.datasource.username=root 9 | spring.datasource.password=root -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/exam/ExamDepartMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | `id`,`exam_id`,`depart_id` 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/exam/ExamRepoMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | `id`,`exam_id`,`repo_id`,`radio_count`,`radio_score`,`multi_count`,`multi_score`,`judge_count`,`judge_score` 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/qu/QuAnswerMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | `id`,`qu_id`,`is_right`,`content`,`analysis` 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/qu/QuRepoMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | `id`,`qu_id`,`repo_id`,`qu_type`,`sort` 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/sys/system/SysDictMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/sys/user/SysRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | `id`,`role_name` 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/sys/user/SysUserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | `id`,`user_name`,`real_name`,`password`,`salt`,`role_ids`,`depart_id`,`create_time`,`update_time`,`state` 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/sys/user/SysUserRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | `id`,`user_id`,`role_id` 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/mapper/user/UserBookMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | `id`,`exam_id`,`user_id`,`qu_id`,`create_time`,`update_time`,`wrong_count`,`title`,`sort` 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-1fc6e879.0e8d89f6.css: -------------------------------------------------------------------------------- 1 | .el-dialog-div[data-v-66c99450]{height:60vh;overflow:auto;padding:10px} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-29be97b1.0b2c7de9.css: -------------------------------------------------------------------------------- 1 | .qu-content[data-v-7001fdda]{padding-bottom:10px}.qu-content div[data-v-7001fdda]{line-height:30px}.qu-analysis p[data-v-7001fdda]{color:#555;font-size:14px}.qu-analysis-line[data-v-7001fdda]{margin-top:20px;border-bottom:1px solid #eee}.el-checkbox-group label[data-v-7001fdda],.el-radio-group label[data-v-7001fdda]{width:100%} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-30ca8599.d0296c60.css: -------------------------------------------------------------------------------- 1 | .errPage-container[data-v-35ca77fc]{width:800px;max-width:100%;margin:100px auto}.errPage-container .pan-back-btn[data-v-35ca77fc]{background:#008489;color:#fff;border:none!important}.errPage-container .pan-gif[data-v-35ca77fc]{margin:0 auto;display:block}.errPage-container .pan-img[data-v-35ca77fc]{display:block;margin:0 auto;width:100%}.errPage-container .text-jumbo[data-v-35ca77fc]{font-size:60px;font-weight:700;color:#484848}.errPage-container .list-unstyled[data-v-35ca77fc]{font-size:14px}.errPage-container .list-unstyled li[data-v-35ca77fc]{padding-bottom:5px}.errPage-container .list-unstyled a[data-v-35ca77fc]{color:#008489;text-decoration:none}.errPage-container .list-unstyled a[data-v-35ca77fc]:hover{text-decoration:underline} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-3f05a485.72886611.css: -------------------------------------------------------------------------------- 1 | .qu-content div[data-v-5b981544]{line-height:30px}.qu-analysis p[data-v-5b981544]{color:#555;font-size:14px}.qu-analysis-line[data-v-5b981544]{margin-top:20px;border-bottom:1px solid #eee}.el-checkbox-group label[data-v-5b981544],.el-radio-group label[data-v-5b981544]{width:100%} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-4e97554c.2016041e.css: -------------------------------------------------------------------------------- 1 | .pre-exam div[data-v-5f8e5584]{line-height:42px;color:#555} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-527df50c.5be46457.css: -------------------------------------------------------------------------------- 1 | .el-input.el-input--suffix{cursor:pointer;overflow:hidden}.el-input.el-input--suffix.rotate .el-input__suffix{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.select-tree{max-height:350px;overflow-y:scroll}.select-tree::-webkit-scrollbar{z-index:11;width:6px}.select-tree::-webkit-scrollbar-corner,.select-tree::-webkit-scrollbar-track{background:#fff}.select-tree::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.select-tree::-webkit-scrollbar-track-piece{background:#fff;width:6px} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-5fc77a43.29e7bbce.css: -------------------------------------------------------------------------------- 1 | .qu-content div[data-v-6d1651aa]{line-height:30px}.el-checkbox-group label[data-v-6d1651aa],.el-radio-group label[data-v-6d1651aa]{width:100%}.card-title[data-v-6d1651aa]{background:#eee;line-height:35px;text-align:center;font-size:14px}.card-line[data-v-6d1651aa]{padding-left:10px}.card-line span[data-v-6d1651aa]{cursor:pointer;margin:2px}.el-checkbox[data-v-6d1651aa],[data-v-6d1651aa] .el-radio{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #dcdfe6;margin-bottom:10px}.is-checked[data-v-6d1651aa]{border:1px solid #409eff}.el-checkbox img[data-v-6d1651aa],.el-radio img[data-v-6d1651aa]{max-width:200px;max-height:200px;border:1px dotted #dcdfe6}[data-v-6d1651aa] .el-checkbox__inner,[data-v-6d1651aa] .el-radio__inner{display:none}[data-v-6d1651aa] .el-checkbox__label,[data-v-6d1651aa] .el-radio__label{line-height:30px} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-c09eb5c6.fb7634dd.css: -------------------------------------------------------------------------------- 1 | .qu-content[data-v-05d744c5]{border-bottom:1px solid #eee;padding-bottom:10px}.qu-content div[data-v-05d744c5]{line-height:30px}.el-checkbox-group label[data-v-05d744c5],.el-radio-group label[data-v-05d744c5]{width:100%}.card-title[data-v-05d744c5]{background:#eee;line-height:35px;text-align:center;font-size:14px}.card-line[data-v-05d744c5]{padding-left:10px}.card-line span[data-v-05d744c5]{cursor:pointer;margin:2px} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-commons.3cbb8a09.css: -------------------------------------------------------------------------------- 1 | .pagination-container[data-v-6af373ef]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-6af373ef]{display:none}.filter-container .filter-item{margin-left:5px}.filter-container .filter-item:first-child{margin-left:0} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-d0da5b72.5be46457.css: -------------------------------------------------------------------------------- 1 | .el-input.el-input--suffix{cursor:pointer;overflow:hidden}.el-input.el-input--suffix.rotate .el-input__suffix{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.select-tree{max-height:350px;overflow-y:scroll}.select-tree::-webkit-scrollbar{z-index:11;width:6px}.select-tree::-webkit-scrollbar-corner,.select-tree::-webkit-scrollbar-track{background:#fff}.select-tree::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.select-tree::-webkit-scrollbar-track-piece{background:#fff;width:6px} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-elementUI.6e808e7d.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width:767px){.hidden-xs-only{display:none!important}}@media only screen and (min-width:768px){.hidden-sm-and-up{display:none!important}}@media only screen and (min-width:768px) and (max-width:991px){.hidden-sm-only{display:none!important}}@media only screen and (max-width:991px){.hidden-sm-and-down{display:none!important}}@media only screen and (min-width:992px){.hidden-md-and-up{display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.hidden-md-only{display:none!important}}@media only screen and (max-width:1199px){.hidden-md-and-down{display:none!important}}@media only screen and (min-width:1200px){.hidden-lg-and-up{display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.hidden-lg-only{display:none!important}}@media only screen and (max-width:1919px){.hidden-lg-and-down{display:none!important}}@media only screen and (min-width:1920px){.hidden-xl-only{display:none!important}} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/css/chunk-fd454600.5464b619.css: -------------------------------------------------------------------------------- 1 | .el-dialog-div[data-v-b55c062c]{height:60vh;overflow:auto;padding:10px} -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/fonts/element-icons.535877f5.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/src/main/resources/static/static/fonts/element-icons.535877f5.woff -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/fonts/element-icons.732389de.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/src/main/resources/static/static/fonts/element-icons.732389de.ttf -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/img/401.089007e7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/src/main/resources/static/static/img/401.089007e7.gif -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/img/404.a57b6f31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/src/main/resources/static/static/img/404.a57b6f31.png -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/img/404_cloud.0f4bc32b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/src/main/resources/static/static/img/404_cloud.0f4bc32b.png -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/img/bg2.7dfac08d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-api/src/main/resources/static/static/img/bg2.7dfac08d.jpg -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/js/chunk-2d0cf932.92df6ba2.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0cf932"],{"63fe":function(t,e,o){"use strict";o.r(e);var s=function(){var t=this,e=t.$createElement,o=t._self._c||e;return o("div",{staticClass:"app-container"},[o("el-form",{ref:"postForm",attrs:{model:t.postForm,rules:t.rules,"label-position":"left","label-width":"100px"}},[o("el-card",[o("el-form-item",{attrs:{label:"系统名称"}},[o("el-input",{attrs:{placeholder:"系统显示名称"},model:{value:t.postForm.siteName,callback:function(e){t.$set(t.postForm,"siteName",e)},expression:"postForm.siteName"}})],1),o("el-row",[o("el-button",{attrs:{type:"primary"},on:{click:t.submitForm}},[t._v("保存")])],1)],1)],1)],1)},a=[],n=o("83ef"),i={name:"Config",data:function(){return{postForm:{id:"1"},loading:!1,rules:{}}},created:function(){this.fetchData()},methods:{fetchData:function(){var t=this;Object(n["a"])().then((function(e){t.postForm=e.data}))},submitForm:function(){var t=this;console.log(JSON.stringify(this.postForm)),this.$refs.postForm.validate((function(e){e&&(t.loading=!0,t.postForm.id="1",Object(n["b"])(t.postForm).then((function(){t.$notify({title:"成功",message:"配置保存成功!",type:"success",duration:2e3})})),t.loading=!1)}))}}},r=i,l=o("2877"),c=Object(l["a"])(r,s,a,!1,null,"dcc91a86",null);e["default"]=c.exports}}]); -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/js/chunk-2d0e5357.eca512cc.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0e5357"],{9406:function(t,e,a){"use strict";a.r(e);var n=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-row",{staticStyle:{"line-height":"30px"}},[a("el-row",{attrs:{span:24}},[t._v("欢迎使用 "+t._s(t.siteData.siteName))]),a("el-row",{staticStyle:{"font-size":"14px",color:"#666"},attrs:{span:24}},[t._v("系统版本:1.2.0")])],1)],1)},s=[],r=a("db72"),c=a("2f62"),o={name:"Dashboard",data:function(){return{currentRole:"adminDashboard"}},computed:Object(r["a"])({},Object(c["b"])(["roles","siteData"])),created:function(){}},i=o,l=a("2877"),u=Object(l["a"])(i,n,s,!1,null,null,null);e["default"]=u.exports}}]); -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/js/chunk-2d2105d3.68fb450b.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d2105d3"],{b829:function(n,e,o){"use strict";o.r(e);o("386d");var t,c,a={name:"AuthRedirect",created:function(){var n=window.location.search.slice(1);window.localStorage&&(window.localStorage.setItem("x-admin-oauth-code",n),window.close())},render:function(n){return n()}},d=a,i=o("2877"),l=Object(i["a"])(d,t,c,!1,null,null,null);e["default"]=l.exports}}]); -------------------------------------------------------------------------------- /exam-api/src/main/resources/static/static/js/chunk-2d230fe7.193e48a7.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d230fe7"],{ef3c:function(e,r,n){"use strict";n.r(r);n("a481");var t,u,a={created:function(){var e=this.$route,r=e.params,n=e.query,t=r.path;this.$router.replace({path:"/"+t,query:n})},render:function(e){return e()}},c=a,o=n("2877"),p=Object(o["a"])(c,t,u,!1,null,null,null);r["default"]=p.exports}}]); -------------------------------------------------------------------------------- /exam-api/src/test/java/com/yf/exam/ExamTest.java: -------------------------------------------------------------------------------- 1 | package com.yf.exam; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.yf.exam.core.api.dto.PagingReqDTO; 5 | import com.yf.exam.modules.exam.dto.ExamDTO; 6 | import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO; 7 | import com.yf.exam.modules.exam.service.ExamService; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @SpringBootTest(classes = ExamApplication.class) 16 | public class ExamTest { 17 | 18 | @Autowired 19 | private ExamService examService; 20 | 21 | @Test 22 | public void testOnline(){ 23 | PagingReqDTO reqDTO = new PagingReqDTO(); 24 | reqDTO.setCurrent(1); 25 | reqDTO.setSize(10); 26 | 27 | System.out.println("++++++=开始查询:::"); 28 | IPage data = examService.onlinePaging(reqDTO); 29 | 30 | System.out.println("++++++=查询结果:::"+data); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /exam-vue/.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /exam-vue/.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'http://localhost:8101' 6 | 7 | # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, 8 | # to control whether the babel-plugin-dynamic-import-node plugin is enabled. 9 | # It only does one thing by converting all import() to require(). 10 | # This configuration can significantly increase the speed of hot updates, 11 | # when you have a large number of pages. 12 | # Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js 13 | 14 | VUE_CLI_BABEL_TRANSPILE_MODULES = true 15 | -------------------------------------------------------------------------------- /exam-vue/.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | # base api 5 | VUE_APP_BASE_API = '' 6 | 7 | -------------------------------------------------------------------------------- /exam-vue/.env.staging: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | 3 | # just a flag 4 | ENV = 'staging' 5 | 6 | # base api 7 | VUE_APP_BASE_API = '/stage-api' 8 | 9 | -------------------------------------------------------------------------------- /exam-vue/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | src/assets 3 | public 4 | dist 5 | -------------------------------------------------------------------------------- /exam-vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | **/*.log 8 | 9 | tests/**/coverage/ 10 | tests/e2e/reports 11 | selenium-debug.log 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.local 21 | 22 | package-lock.json 23 | yarn.lock 24 | -------------------------------------------------------------------------------- /exam-vue/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 10 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /exam-vue/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present PanJiaChen 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. 22 | -------------------------------------------------------------------------------- /exam-vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /exam-vue/build/index.js: -------------------------------------------------------------------------------- 1 | const { run } = require('runjs') 2 | const chalk = require('chalk') 3 | const config = require('../vue.config.js') 4 | const rawArgv = process.argv.slice(2) 5 | const args = rawArgv.join(' ') 6 | 7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) { 8 | const report = rawArgv.includes('--report') 9 | 10 | run(`vue-cli-service build ${args}`) 11 | 12 | const port = 9526 13 | const publicPath = config.publicPath 14 | 15 | var connect = require('connect') 16 | var serveStatic = require('serve-static') 17 | const app = connect() 18 | 19 | app.use( 20 | publicPath, 21 | serveStatic('./dist', { 22 | index: ['index.html', '/'] 23 | }) 24 | ) 25 | 26 | app.listen(port, function () { 27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) 28 | if (report) { 29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) 30 | } 31 | 32 | }) 33 | } else { 34 | run(`vue-cli-service build ${args}`) 35 | } 36 | -------------------------------------------------------------------------------- /exam-vue/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | testMatch: [ 14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 15 | ], 16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], 17 | coverageDirectory: '/tests/unit/coverage', 18 | // 'collectCoverage': true, 19 | 'coverageReporters': [ 20 | 'lcov', 21 | 'text-summary' 22 | ], 23 | testURL: 'http://localhost/' 24 | } 25 | -------------------------------------------------------------------------------- /exam-vue/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } -------------------------------------------------------------------------------- /exam-vue/plop-templates/component/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if template}} 2 | 5 | {{/if}} 6 | 7 | {{#if script}} 8 | 20 | {{/if}} 21 | 22 | {{#if style}} 23 | 26 | {{/if}} 27 | -------------------------------------------------------------------------------- /exam-vue/plop-templates/store/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if state}} 2 | const state = {} 3 | {{/if}} 4 | 5 | {{#if mutations}} 6 | const mutations = {} 7 | {{/if}} 8 | 9 | {{#if actions}} 10 | const actions = {} 11 | {{/if}} 12 | 13 | export default { 14 | namespaced: true, 15 | {{options}} 16 | } 17 | -------------------------------------------------------------------------------- /exam-vue/plop-templates/utils.js: -------------------------------------------------------------------------------- 1 | exports.notEmpty = name => { 2 | return v => { 3 | if (!v || v.trim === '') { 4 | return `${name} is required` 5 | } else { 6 | return true 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /exam-vue/plop-templates/view/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if template}} 2 | 5 | {{/if}} 6 | 7 | {{#if script}} 8 | 20 | {{/if}} 21 | 22 | {{#if style}} 23 | 26 | {{/if}} 27 | -------------------------------------------------------------------------------- /exam-vue/plopfile.js: -------------------------------------------------------------------------------- 1 | const viewGenerator = require('./plop-templates/view/prompt') 2 | const componentGenerator = require('./plop-templates/component/prompt') 3 | const storeGenerator = require('./plop-templates/store/prompt.js') 4 | 5 | module.exports = function(plop) { 6 | plop.setGenerator('view', viewGenerator) 7 | plop.setGenerator('component', componentGenerator) 8 | plop.setGenerator('store', storeGenerator) 9 | } 10 | -------------------------------------------------------------------------------- /exam-vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/public/favicon.ico -------------------------------------------------------------------------------- /exam-vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= webpackConfig.name %> 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exam-vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /exam-vue/src/api/common.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | export function fetchList(url, query) { 4 | return post(url, query) 5 | } 6 | 7 | export function fetchDetail(url, id) { 8 | return post(url, { 'id': id }) 9 | } 10 | 11 | export function saveData(url, data) { 12 | return post(url, data) 13 | } 14 | 15 | export function deleteData(url, ids) { 16 | return post(url, { 'ids': ids }) 17 | } 18 | 19 | export function changeState(url, ids, state) { 20 | return post(url, { 'ids': ids, 'state': state }) 21 | } 22 | -------------------------------------------------------------------------------- /exam-vue/src/api/exam/exam.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | /** 4 | * 题库详情 5 | * @param data 6 | */ 7 | export function fetchDetail(id) { 8 | return post('/exam/api/exam/exam/detail', { id: id }) 9 | } 10 | 11 | /** 12 | * 保存题库 13 | * @param data 14 | */ 15 | export function saveData(data) { 16 | return post('/exam/api/exam/exam/save', data) 17 | } 18 | 19 | /** 20 | * 题库详情 21 | * @param data 22 | */ 23 | export function fetchList() { 24 | return post('/exam/api/exam/exam/paging', { current: 1, size: 100 }) 25 | } 26 | -------------------------------------------------------------------------------- /exam-vue/src/api/paper/exam.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | /** 4 | * 创建试卷 5 | * @param data 6 | */ 7 | export function createPaper(data) { 8 | return post('/exam/api/paper/paper/create-paper', data) 9 | } 10 | 11 | /** 12 | * 试卷详情 13 | * @param data 14 | */ 15 | export function paperDetail(data) { 16 | return post('/exam/api/paper/paper/paper-detail', data) 17 | } 18 | 19 | /** 20 | * 题目详情 21 | * @param data 22 | */ 23 | export function quDetail(data) { 24 | return post('/exam/api/paper/paper/qu-detail', data) 25 | } 26 | 27 | /** 28 | * 填充答案 29 | * @param data 30 | */ 31 | export function fillAnswer(data) { 32 | return post('/exam/api/paper/paper/fill-answer', data) 33 | } 34 | 35 | /** 36 | * 交卷 37 | * @param data 38 | */ 39 | export function handExam(data) { 40 | return post('/exam/api/paper/paper/hand-exam', data) 41 | } 42 | 43 | /** 44 | * 试卷详情 45 | * @param data 46 | */ 47 | export function paperResult(data) { 48 | return post('/exam/api/paper/paper/paper-result', data) 49 | } 50 | 51 | /** 52 | * 错题训练 53 | * @param data 54 | */ 55 | export function training(data) { 56 | return post('/exam/api/paper/paper/training', data) 57 | } 58 | -------------------------------------------------------------------------------- /exam-vue/src/api/paper/paper.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | /** 4 | * 试卷列表 5 | * @param data 6 | */ 7 | export function listPaper(userId, examId) { 8 | return post('/exam/api/paper/paper/paging', { current: 1, size: 5, params: { userId: userId, examId: examId }}) 9 | } 10 | -------------------------------------------------------------------------------- /exam-vue/src/api/qu/qu.js: -------------------------------------------------------------------------------- 1 | import { post, upload, download } from '@/utils/request' 2 | 3 | /** 4 | * 题库详情 5 | * @param data 6 | */ 7 | export function fetchDetail(id) { 8 | return post('/exam/api/qu/qu/detail', { id: id }) 9 | } 10 | 11 | /** 12 | * 保存题库 13 | * @param data 14 | */ 15 | export function saveData(data) { 16 | return post('/exam/api/qu/qu/save', data) 17 | } 18 | 19 | /** 20 | * 导出 21 | * @param data 22 | */ 23 | export function exportExcel(data) { 24 | return download('/exam/api/qu/qu/export', data, '导出的数据.xlsx') 25 | } 26 | 27 | /** 28 | * 导入模板 29 | * @param data 30 | */ 31 | export function importTemplate() { 32 | return download('/exam/api/qu/qu/import/template', {}, 'qu-import-template.xlsx') 33 | } 34 | 35 | /** 36 | * 导出 37 | * @param data 38 | */ 39 | export function importExcel(file) { 40 | return upload('/exam/api/qu/qu/import', file) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /exam-vue/src/api/qu/repo.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | /** 4 | * 题库详情 5 | * @param data 6 | */ 7 | export function fetchDetail(data) { 8 | return post('/exam/api/repo/detail', data) 9 | } 10 | 11 | /** 12 | * 保存题库 13 | * @param data 14 | */ 15 | export function saveData(data) { 16 | return post('/exam/api/repo/save', data) 17 | } 18 | 19 | /** 20 | * 保存题库 21 | * @param data 22 | */ 23 | export function fetchList(data) { 24 | return post('/exam/api/repo/list', data) 25 | } 26 | 27 | /** 28 | * 题库批量操作 29 | * @param data 30 | */ 31 | export function batchAction(data) { 32 | return post('/exam/api/repo/batch-action', data) 33 | } 34 | -------------------------------------------------------------------------------- /exam-vue/src/api/sys/config/config.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | // 获得用户协议详情,固定ID为0 4 | export function fetchDetail() { 5 | return post('/exam/api/sys/config/detail', { id: '1' }) 6 | } 7 | 8 | export function saveData(data) { 9 | return post('/exam/api/sys/config/save', data) 10 | } 11 | -------------------------------------------------------------------------------- /exam-vue/src/api/sys/depart/depart.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | export function pagingTree(data) { 4 | return post('/exam/api/sys/depart/paging', data) 5 | } 6 | 7 | export function fetchTree(data) { 8 | return post('/exam/api/sys/depart/tree', data) 9 | } 10 | 11 | export function fetchDetail(id) { 12 | const data = { id: id } 13 | return post('/exam/api/sys/depart/detail', data) 14 | } 15 | 16 | export function deleteData(ids) { 17 | const data = { ids: ids } 18 | return post('/exam/api/sys/depart/delete', data) 19 | } 20 | 21 | export function saveData(data) { 22 | return post('/exam/api/sys/depart/save', data) 23 | } 24 | 25 | export function sortData(id, sort) { 26 | const data = { id: id, sort: sort } 27 | return post('/exam/api/sys/depart/sort', data) 28 | } 29 | -------------------------------------------------------------------------------- /exam-vue/src/api/sys/role/role.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | export function fetchList() { 4 | return post('/exam/api/sys/role/list', {}) 5 | } 6 | 7 | -------------------------------------------------------------------------------- /exam-vue/src/api/sys/user/user.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | export function updateData(data) { 4 | return post('/exam/api/sys/user/update', data) 5 | } 6 | 7 | export function saveData(data) { 8 | return post('/exam/api/sys/user/save', data) 9 | } 10 | 11 | export function userReg(data) { 12 | return post('/exam/api/sys/user/reg', data) 13 | } 14 | -------------------------------------------------------------------------------- /exam-vue/src/api/user.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | export function login(data) { 4 | return post('/exam/api/sys/user/login', data) 5 | } 6 | 7 | export function getInfo(token) { 8 | return post('/exam/api/sys/user/info?token=' + token) 9 | } 10 | 11 | export function logout() { 12 | return post('/exam/api/sys/user/logout', {}) 13 | } 14 | 15 | export function reg(data) { 16 | return post('/exam/api/sys/user/reg', data) 17 | } 18 | -------------------------------------------------------------------------------- /exam-vue/src/api/user/book.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | /** 4 | * 题库详情 5 | * @param data 6 | */ 7 | export function nextQu(examId, quId) { 8 | return post('/exam/api/user/wrong-book/next', { examId: examId, quId: quId }) 9 | } 10 | 11 | -------------------------------------------------------------------------------- /exam-vue/src/assets/401_images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/401_images/401.gif -------------------------------------------------------------------------------- /exam-vue/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/404_images/404.png -------------------------------------------------------------------------------- /exam-vue/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /exam-vue/src/assets/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/bg2.jpg -------------------------------------------------------------------------------- /exam-vue/src/assets/custom-theme/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/custom-theme/fonts/element-icons.ttf -------------------------------------------------------------------------------- /exam-vue/src/assets/custom-theme/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/custom-theme/fonts/element-icons.woff -------------------------------------------------------------------------------- /exam-vue/src/assets/guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/guide.pdf -------------------------------------------------------------------------------- /exam-vue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/logo.png -------------------------------------------------------------------------------- /exam-vue/src/assets/system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatle/Examination-System/617ddd915907e7c3451d39f396bc2993797f587e/exam-vue/src/assets/system.png -------------------------------------------------------------------------------- /exam-vue/src/components/Screenfull/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 50 | 51 | 61 | -------------------------------------------------------------------------------- /exam-vue/src/directive/clipboard/index.js: -------------------------------------------------------------------------------- 1 | import Clipboard from './clipboard' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('Clipboard', Clipboard) 5 | } 6 | 7 | if (window.Vue) { 8 | window.clipboard = Clipboard 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | Clipboard.install = install 13 | export default Clipboard 14 | -------------------------------------------------------------------------------- /exam-vue/src/directive/el-drag-dialog/index.js: -------------------------------------------------------------------------------- 1 | import drag from './drag' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('el-drag-dialog', drag) 5 | } 6 | 7 | if (window.Vue) { 8 | window['el-drag-dialog'] = drag 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | drag.install = install 13 | export default drag 14 | -------------------------------------------------------------------------------- /exam-vue/src/directive/el-table/adaptive.js: -------------------------------------------------------------------------------- 1 | import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event' 2 | 3 | /** 4 | * How to use 5 | * ... 6 | * el-table height is must be set 7 | * bottomOffset: 30(default) // The height of the table from the bottom of the page. 8 | */ 9 | 10 | const doResize = (el, binding, vnode) => { 11 | const { componentInstance: $table } = vnode 12 | 13 | const { value } = binding 14 | 15 | if (!$table.height) { 16 | throw new Error(`el-$table must set the height. Such as height='100px'`) 17 | } 18 | const bottomOffset = (value && value.bottomOffset) || 30 19 | 20 | if (!$table) return 21 | 22 | const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset 23 | $table.layout.setHeight(height) 24 | $table.doLayout() 25 | } 26 | 27 | export default { 28 | bind(el, binding, vnode) { 29 | el.resizeListener = () => { 30 | doResize(el, binding, vnode) 31 | } 32 | // parameter 1 is must be "Element" type 33 | addResizeListener(window.document.body, el.resizeListener) 34 | }, 35 | inserted(el, binding, vnode) { 36 | doResize(el, binding, vnode) 37 | }, 38 | unbind(el) { 39 | removeResizeListener(window.document.body, el.resizeListener) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /exam-vue/src/directive/el-table/index.js: -------------------------------------------------------------------------------- 1 | import adaptive from './adaptive' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('el-height-adaptive-table', adaptive) 5 | } 6 | 7 | if (window.Vue) { 8 | window['el-height-adaptive-table'] = adaptive 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | adaptive.install = install 13 | export default adaptive 14 | -------------------------------------------------------------------------------- /exam-vue/src/directive/permission/index.js: -------------------------------------------------------------------------------- 1 | import permission from './permission' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('permission', permission) 5 | } 6 | 7 | if (window.Vue) { 8 | window['permission'] = permission 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | permission.install = install 13 | export default permission 14 | -------------------------------------------------------------------------------- /exam-vue/src/directive/permission/permission.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | export default { 4 | inserted(el, binding, vnode) { 5 | const { value } = binding 6 | const roles = store.getters && store.getters.roles 7 | 8 | if (value && value instanceof Array && value.length > 0) { 9 | const permissionRoles = value 10 | 11 | const hasPermission = roles.some(role => { 12 | return permissionRoles.includes(role) 13 | }) 14 | 15 | if (!hasPermission) { 16 | el.parentNode && el.parentNode.removeChild(el) 17 | } 18 | } else { 19 | throw new Error(`need roles! Like v-permission="['admin','editor']"`) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /exam-vue/src/directive/waves/index.js: -------------------------------------------------------------------------------- 1 | import waves from './waves' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('waves', waves) 5 | } 6 | 7 | if (window.Vue) { 8 | window.waves = waves 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | waves.install = install 13 | export default waves 14 | -------------------------------------------------------------------------------- /exam-vue/src/directive/waves/waves.css: -------------------------------------------------------------------------------- 1 | .waves-ripple { 2 | position: absolute; 3 | border-radius: 100%; 4 | background-color: rgba(0, 0, 0, 0.15); 5 | background-clip: padding-box; 6 | pointer-events: none; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | -webkit-transform: scale(0); 12 | -ms-transform: scale(0); 13 | transform: scale(0); 14 | opacity: 1; 15 | } 16 | 17 | .waves-ripple.z-active { 18 | opacity: 0; 19 | -webkit-transform: scale(2); 20 | -ms-transform: scale(2); 21 | transform: scale(2); 22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out; 25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; 26 | } -------------------------------------------------------------------------------- /exam-vue/src/filters/index.js: -------------------------------------------------------------------------------- 1 | // import parseTime, formatTime and set to filter 2 | export { parseTime, formatTime } from '@/utils' 3 | 4 | /** 5 | * Upper case first char 6 | * @param {String} string 7 | */ 8 | export function uppercaseFirst(string) { 9 | return string.charAt(0).toUpperCase() + string.slice(1) 10 | } 11 | 12 | /** 13 | * 通用状态过滤器 14 | * @param value 15 | * @returns {*} 16 | */ 17 | export function stateFilter(value) { 18 | const map = { 19 | '0': '正常', 20 | '1': '禁用' 21 | } 22 | return map[value] 23 | } 24 | 25 | export function quTypeFilter(value) { 26 | const map = { 27 | '1': '单选题', 28 | '2': '多选题', 29 | '3': '判断题' 30 | } 31 | return map[value] 32 | } 33 | 34 | export function paperStateFilter(value) { 35 | const map = { 36 | '0': '考试中', 37 | '1': '待阅卷', 38 | '2': '已考完', 39 | '3': '!已弃考' 40 | } 41 | return map[value] 42 | } 43 | 44 | export function examOpenType(value) { 45 | const map = { 46 | '1': '完全公开', 47 | '2': '指定部门' 48 | } 49 | return map[value] 50 | } 51 | 52 | export function examStateFilter(value) { 53 | const map = { 54 | '0': '进行中', 55 | '1': '已禁用', 56 | '2': '待开始', 57 | '3': '已结束' 58 | } 59 | return map[value] 60 | } 61 | -------------------------------------------------------------------------------- /exam-vue/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/admin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/chart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/clipboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/component.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/documentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/education.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/excel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/fire.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/guide.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/international.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/money.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/notify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/people.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/peoples.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/repo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/skill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/statis.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/stats-dots.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/stats2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/study.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/support.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/test.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/training.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/tree-table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/water.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svg/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /exam-vue/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /exam-vue/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | 25 | 49 | 50 | 58 | -------------------------------------------------------------------------------- /exam-vue/src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /exam-vue/src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /exam-vue/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 37 | -------------------------------------------------------------------------------- /exam-vue/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as AppMain } from './AppMain' 2 | export { default as Navbar } from './Navbar' 3 | export { default as Settings } from './Settings' 4 | export { default as Sidebar } from './Sidebar/index.vue' 5 | export { default as TagsView } from './TagsView/index.vue' 6 | -------------------------------------------------------------------------------- /exam-vue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import Cookies from 'js-cookie' 4 | 5 | import 'normalize.css/normalize.css' // a modern alternative to CSS resets 6 | 7 | import Element from 'element-ui' 8 | import './styles/element-variables.scss' 9 | import '@/styles/index.scss' 10 | import '@/styles/meetboxs.scss' 11 | 12 | import App from './App' 13 | import store from './store' 14 | import router from './router' 15 | 16 | import './icons' // icon 17 | import './permission' // permission control 18 | import './utils/error-log' // error log 19 | 20 | import * as filters from './filters' // global filters 21 | 22 | Vue.use(Element, { 23 | size: Cookies.get('size') || 'medium' // set element-ui default size 24 | }) 25 | 26 | // register global utility filters 27 | Object.keys(filters).forEach(key => { 28 | Vue.filter(key, filters[key]) 29 | }) 30 | 31 | Vue.config.productionTip = false 32 | 33 | new Vue({ 34 | el: '#app', 35 | router, 36 | store, 37 | render: h => h(App) 38 | }) 39 | -------------------------------------------------------------------------------- /exam-vue/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: '云帆考试培训系统', 3 | 4 | /** 5 | * @type {boolean} true | false 6 | * @description Whether show the settings right-panel 7 | */ 8 | showSettings: false, 9 | 10 | /** 11 | * @type {boolean} true | false 12 | * @description Whether need tagsView 13 | */ 14 | tagsView: true, 15 | 16 | /** 17 | * @type {boolean} true | false 18 | * @description Whether fix the header 19 | */ 20 | fixedHeader: false, 21 | 22 | /** 23 | * @type {boolean} true | false 24 | * @description Whether show the logo in sidebar 25 | */ 26 | sidebarLogo: true, 27 | 28 | /** 29 | * @type {string | array} 'production' | ['production', 'development'] 30 | * @description Need show err logs component. 31 | * The default is only used in the production env 32 | * If you want to also use it in dev, you can pass ['production', 'development'] 33 | */ 34 | errorLog: 'production' 35 | } 36 | -------------------------------------------------------------------------------- /exam-vue/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | size: state => state.app.size, 4 | device: state => state.app.device, 5 | visitedViews: state => state.tagsView.visitedViews, 6 | cachedViews: state => state.tagsView.cachedViews, 7 | token: state => state.user.token, 8 | avatar: state => state.user.avatar, 9 | userId: state => state.user.userId, 10 | name: state => state.user.name, 11 | realName: state => state.user.realName, 12 | introduction: state => state.user.introduction, 13 | roles: state => state.user.roles, 14 | permission_routes: state => state.permission.routes, 15 | errorLogs: state => state.errorLog.logs, 16 | siteData: state => state.settings.siteData 17 | } 18 | export default getters 19 | -------------------------------------------------------------------------------- /exam-vue/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | 5 | Vue.use(Vuex) 6 | 7 | // https://webpack.js.org/guides/dependency-management/#requirecontext 8 | const modulesFiles = require.context('./modules', true, /\.js$/) 9 | 10 | // you do not need `import app from './modules/app'` 11 | // it will auto require all vuex module from modules file 12 | const modules = modulesFiles.keys().reduce((modules, modulePath) => { 13 | // set './app.js' => 'app' 14 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') 15 | const value = modulesFiles(modulePath) 16 | modules[moduleName] = value.default 17 | return modules 18 | }, {}) 19 | 20 | const store = new Vuex.Store({ 21 | modules, 22 | getters 23 | }) 24 | 25 | export default store 26 | -------------------------------------------------------------------------------- /exam-vue/src/store/modules/errorLog.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | logs: [] 3 | } 4 | 5 | const mutations = { 6 | ADD_ERROR_LOG: (state, log) => { 7 | state.logs.push(log) 8 | }, 9 | CLEAR_ERROR_LOG: (state) => { 10 | state.logs.splice(0) 11 | } 12 | } 13 | 14 | const actions = { 15 | addErrorLog({ commit }, log) { 16 | commit('ADD_ERROR_LOG', log) 17 | }, 18 | clearErrorLog({ commit }) { 19 | commit('CLEAR_ERROR_LOG') 20 | } 21 | } 22 | 23 | export default { 24 | namespaced: true, 25 | state, 26 | mutations, 27 | actions 28 | } 29 | -------------------------------------------------------------------------------- /exam-vue/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import variables from '@/styles/element-variables.scss' 2 | import defaultSettings from '@/settings' 3 | import { fetchDetail } from '@/api/sys/config/config' 4 | 5 | const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings 6 | 7 | const state = { 8 | theme: variables.theme, 9 | showSettings: showSettings, 10 | tagsView: tagsView, 11 | fixedHeader: fixedHeader, 12 | sidebarLogo: sidebarLogo, 13 | siteData: {} 14 | } 15 | 16 | const mutations = { 17 | CHANGE_SETTING: (state, { key, value }) => { 18 | if (state.hasOwnProperty(key)) { 19 | state[key] = value 20 | } 21 | }, 22 | SET_SITE_DATA: (state, siteData) => { 23 | state.siteData = siteData 24 | } 25 | 26 | } 27 | 28 | const actions = { 29 | changeSetting({ commit }, data) { 30 | commit('CHANGE_SETTING', data) 31 | }, 32 | // 获取网站配置信息 33 | getSite({ commit }) { 34 | return new Promise((resolve, reject) => { 35 | fetchDetail({}).then(response => { 36 | const { data } = response 37 | commit('SET_SITE_DATA', data) 38 | resolve(data) 39 | }).catch(error => { 40 | reject(error) 41 | }) 42 | }) 43 | } 44 | } 45 | 46 | export default { 47 | namespaced: true, 48 | state, 49 | mutations, 50 | actions 51 | } 52 | 53 | -------------------------------------------------------------------------------- /exam-vue/src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * I think element-ui's default theme color is too light for long-term use. 3 | * So I modified the default color and you can modify it to your liking. 4 | **/ 5 | 6 | /* theme color */ 7 | $--color-primary: #1890ff; 8 | $--color-success: #13ce66; 9 | $--color-warning: #FFBA00; 10 | $--color-danger: #ff4949; 11 | // $--color-info: #1E1E1E; 12 | 13 | $--button-font-weight: 400; 14 | 15 | // $--color-text-regular: #1f2d3d; 16 | 17 | $--border-color-light: #dfe4ed; 18 | $--border-color-lighter: #e6ebf5; 19 | 20 | $--table-border:1px solid#dfe6ec; 21 | 22 | /* icon font path, required */ 23 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 24 | 25 | @import "~element-ui/packages/theme-chalk/src/index"; 26 | 27 | // the :export directive is the magic sauce for webpack 28 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 29 | :export { 30 | theme: $--color-primary; 31 | } 32 | -------------------------------------------------------------------------------- /exam-vue/src/styles/meetboxs.scss: -------------------------------------------------------------------------------- 1 | .col-image-square { 2 | width: 40px; 3 | height: 40px; 4 | margin: auto; 5 | } 6 | 7 | .col-image-rectangle { 8 | width: 60px; 9 | height: 40px; 10 | margin: auto; 11 | } 12 | 13 | .col-image-square img,.col-image-rectangle img { 14 | width: 100%; 15 | height: 100%; 16 | border:#eee 1px solid; 17 | } 18 | 19 | .filter-container .filter-item{ 20 | margin-left: 5px; 21 | } 22 | 23 | .filter-container .filter-item:first-child{ 24 | margin-left: 0px; 25 | } 26 | 27 | a{ 28 | color: #0d5ea5; 29 | } 30 | 31 | 32 | 33 | .avatar-uploader .el-upload { 34 | border: 1px dashed #d9d9d9; 35 | border-radius: 6px; 36 | cursor: pointer; 37 | position: relative; 38 | overflow: hidden; 39 | } 40 | 41 | .avatar-uploader .el-upload:hover { 42 | border-color: #409EFF; 43 | } 44 | 45 | .avatar-uploader-icon { 46 | font-size: 28px; 47 | color: #8c939d; 48 | width: 100px; 49 | height: 100px; 50 | line-height: 100px; 51 | text-align: center; 52 | } 53 | 54 | .avatar { 55 | width: 100px; 56 | height: 100px; 57 | display: block; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /exam-vue/src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /exam-vue/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // base color 2 | $blue:#324157; 3 | $light-blue:#3A71A8; 4 | $red:#C03639; 5 | $pink: #E65D6E; 6 | $green: #30B08F; 7 | $tiffany: #4AB7BD; 8 | $yellow:#FEC171; 9 | $panGreen: #30B08F; 10 | 11 | // sidebar 12 | $menuText:#bfcbd9; 13 | $menuActiveText:#409EFF; 14 | $subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951 15 | 16 | $menuBg:#304156; 17 | $menuHover:#263445; 18 | 19 | $subMenuBg:#1f2d3d; 20 | $subMenuHover:#001528; 21 | 22 | $sideBarWidth: 210px; 23 | 24 | // the :export directive is the magic sauce for webpack 25 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 26 | :export { 27 | menuText: $menuText; 28 | menuActiveText: $menuActiveText; 29 | subMenuActiveText: $subMenuActiveText; 30 | menuBg: $menuBg; 31 | menuHover: $menuHover; 32 | subMenuBg: $subMenuBg; 33 | subMenuHover: $subMenuHover; 34 | sideBarWidth: $sideBarWidth; 35 | } 36 | -------------------------------------------------------------------------------- /exam-vue/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'Admin-Token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /exam-vue/src/utils/clipboard.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Clipboard from 'clipboard' 3 | 4 | function clipboardSuccess() { 5 | Vue.prototype.$message({ 6 | message: 'Copy successfully', 7 | type: 'success', 8 | duration: 1500 9 | }) 10 | } 11 | 12 | function clipboardError() { 13 | Vue.prototype.$message({ 14 | message: 'Copy failed', 15 | type: 'error' 16 | }) 17 | } 18 | 19 | export default function handleClipboard(text, event) { 20 | const clipboard = new Clipboard(event.target, { 21 | text: () => text 22 | }) 23 | clipboard.on('success', () => { 24 | clipboardSuccess() 25 | clipboard.destroy() 26 | }) 27 | clipboard.on('error', () => { 28 | clipboardError() 29 | clipboard.destroy() 30 | }) 31 | clipboard.onClick(event) 32 | } 33 | -------------------------------------------------------------------------------- /exam-vue/src/utils/error-log.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | import { isString, isArray } from '@/utils/validate' 4 | import settings from '@/settings' 5 | 6 | // you can set in settings.js 7 | // errorLog:'production' | ['production', 'development'] 8 | const { errorLog: needErrorLog } = settings 9 | 10 | function checkNeed() { 11 | const env = process.env.NODE_ENV 12 | if (isString(needErrorLog)) { 13 | return env === needErrorLog 14 | } 15 | if (isArray(needErrorLog)) { 16 | return needErrorLog.includes(env) 17 | } 18 | return false 19 | } 20 | 21 | if (checkNeed()) { 22 | Vue.config.errorHandler = function(err, vm, info, a) { 23 | // Don't ask me why I use Vue.nextTick, it just a hack. 24 | // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500 25 | Vue.nextTick(() => { 26 | store.dispatch('errorLog/addErrorLog', { 27 | err, 28 | vm, 29 | info, 30 | url: window.location.href 31 | }) 32 | console.error(err, info) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /exam-vue/src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | export default function getPageTitle(title, pageTitle) { 2 | if (pageTitle) { 3 | return `${pageTitle} - ${title}` 4 | } 5 | return `${title}` 6 | } 7 | -------------------------------------------------------------------------------- /exam-vue/src/utils/open-window.js: -------------------------------------------------------------------------------- 1 | /** 2 | *Created by PanJiaChen on 16/11/29. 3 | * @param {Sting} url 4 | * @param {Sting} title 5 | * @param {Number} w 6 | * @param {Number} h 7 | */ 8 | export default function openWindow(url, title, w, h) { 9 | // Fixes dual-screen position Most browsers Firefox 10 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left 11 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top 12 | 13 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width 14 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height 15 | 16 | const left = ((width / 2) - (w / 2)) + dualScreenLeft 17 | const top = ((height / 2) - (h / 2)) + dualScreenTop 18 | const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left) 19 | 20 | // Puts focus on the newWindow 21 | if (window.focus) { 22 | newWindow.focus() 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /exam-vue/src/utils/permission.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | /** 4 | * @param {Array} value 5 | * @returns {Boolean} 6 | * @example see @/views/permission/directive.vue 7 | */ 8 | export default function checkPermission(value) { 9 | if (value && value instanceof Array && value.length > 0) { 10 | const roles = store.getters && store.getters.roles 11 | const permissionRoles = value 12 | 13 | const hasPermission = roles.some(role => { 14 | return permissionRoles.includes(role) 15 | }) 16 | 17 | if (!hasPermission) { 18 | return false 19 | } 20 | return true 21 | } else { 22 | console.error(`need roles! Like v-permission="['admin','editor']"`) 23 | return false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /exam-vue/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | -------------------------------------------------------------------------------- /exam-vue/src/views/error-log/components/ErrorTestA.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /exam-vue/src/views/error-log/components/ErrorTestB.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /exam-vue/src/views/error-log/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | 28 | 33 | -------------------------------------------------------------------------------- /exam-vue/src/views/login/auth-redirect.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /exam-vue/src/views/profile/components/Account.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 50 | -------------------------------------------------------------------------------- /exam-vue/src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /exam-vue/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /exam-vue/tests/unit/components/Hamburger.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import Hamburger from '@/components/Hamburger/index.vue' 3 | describe('Hamburger.vue', () => { 4 | it('toggle click', () => { 5 | const wrapper = shallowMount(Hamburger) 6 | const mockFn = jest.fn() 7 | wrapper.vm.$on('toggleClick', mockFn) 8 | wrapper.find('.hamburger').trigger('click') 9 | expect(mockFn).toBeCalled() 10 | }) 11 | it('prop isActive', () => { 12 | const wrapper = shallowMount(Hamburger) 13 | wrapper.setProps({ isActive: true }) 14 | expect(wrapper.contains('.is-active')).toBe(true) 15 | wrapper.setProps({ isActive: false }) 16 | expect(wrapper.contains('.is-active')).toBe(false) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /exam-vue/tests/unit/components/SvgIcon.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import SvgIcon from '@/components/SvgIcon/index.vue' 3 | describe('SvgIcon.vue', () => { 4 | it('iconClass', () => { 5 | const wrapper = shallowMount(SvgIcon, { 6 | propsData: { 7 | iconClass: 'test' 8 | } 9 | }) 10 | expect(wrapper.find('use').attributes().href).toBe('#icon-test') 11 | }) 12 | it('className', () => { 13 | const wrapper = shallowMount(SvgIcon, { 14 | propsData: { 15 | iconClass: 'test' 16 | } 17 | }) 18 | expect(wrapper.classes().length).toBe(1) 19 | wrapper.setProps({ className: 'test' }) 20 | expect(wrapper.classes().includes('test')).toBe(true) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /exam-vue/tests/unit/utils/formatTime.spec.js: -------------------------------------------------------------------------------- 1 | import { formatTime } from '@/utils/index.js' 2 | describe('Utils:formatTime', () => { 3 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" 4 | const retrofit = 5 * 1000 5 | 6 | it('ten digits timestamp', () => { 7 | expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分') 8 | }) 9 | it('test now', () => { 10 | expect(formatTime(+new Date() - 1)).toBe('刚刚') 11 | }) 12 | it('less two minute', () => { 13 | expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前') 14 | }) 15 | it('less two hour', () => { 16 | expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前') 17 | }) 18 | it('less one day', () => { 19 | expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前') 20 | }) 21 | it('more than one day', () => { 22 | expect(formatTime(d)).toBe('7月13日17时54分') 23 | }) 24 | it('format', () => { 25 | expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') 26 | expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') 27 | expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /exam-vue/tests/unit/utils/parseTime.spec.js: -------------------------------------------------------------------------------- 1 | import { parseTime } from '@/utils/index.js' 2 | describe('Utils:parseTime', () => { 3 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" 4 | it('timestamp', () => { 5 | expect(parseTime(d)).toBe('2018-07-13 17:54:01') 6 | }) 7 | it('ten digits timestamp', () => { 8 | expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01') 9 | }) 10 | it('new Date', () => { 11 | expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01') 12 | }) 13 | it('format', () => { 14 | expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') 15 | expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') 16 | expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') 17 | }) 18 | it('get the day of the week', () => { 19 | expect(parseTime(d, '{a}')).toBe('五') // 星期五 20 | }) 21 | it('get the day of the week', () => { 22 | expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日 23 | }) 24 | it('empty argument', () => { 25 | expect(parseTime()).toBeNull() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /exam-vue/tests/unit/utils/validate.spec.js: -------------------------------------------------------------------------------- 1 | import { validUsername, validURL, validLowerCase, validUpperCase, validAlphabets } from '@/utils/validate.js' 2 | describe('Utils:validate', () => { 3 | it('validUsername', () => { 4 | expect(validUsername('admin')).toBe(true) 5 | expect(validUsername('editor')).toBe(true) 6 | expect(validUsername('xxxx')).toBe(false) 7 | }) 8 | it('validURL', () => { 9 | expect(validURL('https://github.com/PanJiaChen/vue-element-admin')).toBe(true) 10 | expect(validURL('http://github.com/PanJiaChen/vue-element-admin')).toBe(true) 11 | expect(validURL('github.com/PanJiaChen/vue-element-admin')).toBe(false) 12 | }) 13 | it('validLowerCase', () => { 14 | expect(validLowerCase('abc')).toBe(true) 15 | expect(validLowerCase('Abc')).toBe(false) 16 | expect(validLowerCase('123abc')).toBe(false) 17 | }) 18 | it('validUpperCase', () => { 19 | expect(validUpperCase('ABC')).toBe(true) 20 | expect(validUpperCase('Abc')).toBe(false) 21 | expect(validUpperCase('123ABC')).toBe(false) 22 | }) 23 | it('validAlphabets', () => { 24 | expect(validAlphabets('ABC')).toBe(true) 25 | expect(validAlphabets('Abc')).toBe(true) 26 | expect(validAlphabets('123aBC')).toBe(false) 27 | }) 28 | }) 29 | --------------------------------------------------------------------------------