Repository: yangzongzhuan/RuoYi Branch: master Commit: 428043e83502 Files: 574 Total size: 5.4 MB Directory structure: gitextract_8cqv8t7r/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin/ │ ├── clean.bat │ ├── package.bat │ └── run.bat ├── doc/ │ └── 若依环境使用手册.docx ├── pom.xml ├── ruoyi-admin/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── ruoyi/ │ │ ├── RuoYiApplication.java │ │ ├── RuoYiServletInitializer.java │ │ └── web/ │ │ ├── controller/ │ │ │ ├── common/ │ │ │ │ └── CommonController.java │ │ │ ├── demo/ │ │ │ │ ├── controller/ │ │ │ │ │ ├── DemoDialogController.java │ │ │ │ │ ├── DemoFormController.java │ │ │ │ │ ├── DemoIconController.java │ │ │ │ │ ├── DemoOperateController.java │ │ │ │ │ ├── DemoReportController.java │ │ │ │ │ └── DemoTableController.java │ │ │ │ └── domain/ │ │ │ │ ├── CustomerModel.java │ │ │ │ ├── GoodsModel.java │ │ │ │ └── UserOperateModel.java │ │ │ ├── monitor/ │ │ │ │ ├── CacheController.java │ │ │ │ ├── DruidController.java │ │ │ │ ├── ServerController.java │ │ │ │ ├── SysLogininforController.java │ │ │ │ ├── SysOperlogController.java │ │ │ │ └── SysUserOnlineController.java │ │ │ ├── system/ │ │ │ │ ├── SysCaptchaController.java │ │ │ │ ├── SysConfigController.java │ │ │ │ ├── SysDeptController.java │ │ │ │ ├── SysDictDataController.java │ │ │ │ ├── SysDictTypeController.java │ │ │ │ ├── SysIndexController.java │ │ │ │ ├── SysLoginController.java │ │ │ │ ├── SysMenuController.java │ │ │ │ ├── SysNoticeController.java │ │ │ │ ├── SysPostController.java │ │ │ │ ├── SysProfileController.java │ │ │ │ ├── SysRegisterController.java │ │ │ │ ├── SysRoleController.java │ │ │ │ └── SysUserController.java │ │ │ └── tool/ │ │ │ ├── BuildController.java │ │ │ ├── SwaggerController.java │ │ │ └── TestController.java │ │ └── core/ │ │ └── config/ │ │ └── SwaggerConfig.java │ └── resources/ │ ├── application-druid.yml │ ├── application.yml │ ├── banner.txt │ ├── ehcache/ │ │ └── ehcache-shiro.xml │ ├── logback.xml │ ├── mybatis/ │ │ └── mybatis-config.xml │ ├── static/ │ │ ├── ajax/ │ │ │ └── libs/ │ │ │ ├── beautifyhtml/ │ │ │ │ └── beautifyhtml.js │ │ │ ├── blockUI/ │ │ │ │ └── jquery.blockUI.js │ │ │ ├── bootstrap-fileinput/ │ │ │ │ ├── fileinput.css │ │ │ │ └── fileinput.js │ │ │ ├── bootstrap-select/ │ │ │ │ ├── bootstrap-select.css │ │ │ │ └── bootstrap-select.js │ │ │ ├── bootstrap-table/ │ │ │ │ ├── extensions/ │ │ │ │ │ ├── auto-refresh/ │ │ │ │ │ │ └── bootstrap-table-auto-refresh.js │ │ │ │ │ ├── columns/ │ │ │ │ │ │ └── bootstrap-table-fixed-columns.js │ │ │ │ │ ├── cookie/ │ │ │ │ │ │ └── bootstrap-table-cookie.js │ │ │ │ │ ├── custom-view/ │ │ │ │ │ │ └── bootstrap-table-custom-view.js │ │ │ │ │ ├── editable/ │ │ │ │ │ │ ├── bootstrap-editable.css │ │ │ │ │ │ └── bootstrap-table-editable.js │ │ │ │ │ ├── export/ │ │ │ │ │ │ └── bootstrap-table-export.js │ │ │ │ │ ├── mobile/ │ │ │ │ │ │ └── bootstrap-table-mobile.js │ │ │ │ │ ├── print/ │ │ │ │ │ │ └── bootstrap-table-print.js │ │ │ │ │ ├── reorder-columns/ │ │ │ │ │ │ ├── bootstrap-table-reorder-columns.js │ │ │ │ │ │ └── jquery.dragtable.js │ │ │ │ │ ├── reorder-rows/ │ │ │ │ │ │ ├── bootstrap-table-reorder-rows.js │ │ │ │ │ │ └── jquery.tablednd.js │ │ │ │ │ ├── resizable/ │ │ │ │ │ │ └── bootstrap-table-resizable.js │ │ │ │ │ └── tree/ │ │ │ │ │ └── bootstrap-table-tree.js │ │ │ │ └── locale/ │ │ │ │ └── bootstrap-table-zh-CN.js │ │ │ ├── cropper/ │ │ │ │ ├── cropper.css │ │ │ │ └── cropper.js │ │ │ ├── cxselect/ │ │ │ │ └── jquery.cxselect.js │ │ │ ├── datapicker/ │ │ │ │ ├── bootstrap-datetimepicker.css │ │ │ │ └── bootstrap-datetimepicker.js │ │ │ ├── duallistbox/ │ │ │ │ ├── bootstrap-duallistbox.css │ │ │ │ └── bootstrap-duallistbox.js │ │ │ ├── flot/ │ │ │ │ ├── curvedLines.js │ │ │ │ ├── jquery.flot.js │ │ │ │ ├── jquery.flot.pie.js │ │ │ │ ├── jquery.flot.resize.js │ │ │ │ ├── jquery.flot.spline.js │ │ │ │ └── jquery.flot.symbol.js │ │ │ ├── fullscreen/ │ │ │ │ └── jquery.fullscreen.js │ │ │ ├── iCheck/ │ │ │ │ └── custom.css │ │ │ ├── jasny/ │ │ │ │ ├── jasny-bootstrap.css │ │ │ │ └── jasny-bootstrap.js │ │ │ ├── jquery-layout/ │ │ │ │ ├── jquery.layout-latest.css │ │ │ │ └── jquery.layout-latest.js │ │ │ ├── jquery-ztree/ │ │ │ │ └── 3.5/ │ │ │ │ ├── css/ │ │ │ │ │ ├── default/ │ │ │ │ │ │ └── zTreeStyle.css │ │ │ │ │ ├── metro/ │ │ │ │ │ │ └── zTreeStyle.css │ │ │ │ │ └── simple/ │ │ │ │ │ └── zTreeStyle.css │ │ │ │ ├── js/ │ │ │ │ │ ├── jquery.ztree.all-3.5.js │ │ │ │ │ ├── jquery.ztree.core-3.5.js │ │ │ │ │ ├── jquery.ztree.excheck-3.5.js │ │ │ │ │ ├── jquery.ztree.exedit-3.5.js │ │ │ │ │ └── jquery.ztree.exhide-3.5.js │ │ │ │ └── log v3.x.txt │ │ │ ├── jsonview/ │ │ │ │ ├── jquery.jsonview.css │ │ │ │ └── jquery.jsonview.js │ │ │ ├── layer/ │ │ │ │ ├── css/ │ │ │ │ │ └── layer.css │ │ │ │ └── theme/ │ │ │ │ └── moon/ │ │ │ │ └── style.css │ │ │ ├── layui/ │ │ │ │ ├── css/ │ │ │ │ │ └── modules/ │ │ │ │ │ └── laydate.css │ │ │ │ └── modules/ │ │ │ │ └── laydate.js │ │ │ ├── select2/ │ │ │ │ ├── select2.css │ │ │ │ └── select2.js │ │ │ ├── suggest/ │ │ │ │ └── bootstrap-suggest.js │ │ │ ├── summernote/ │ │ │ │ ├── summernote-zh-CN.js │ │ │ │ ├── summernote.css │ │ │ │ └── summernote.js │ │ │ ├── typeahead/ │ │ │ │ └── bootstrap-typeahead.js │ │ │ └── validate/ │ │ │ ├── jquery.validate.extend.js │ │ │ └── messages_zh.js │ │ ├── css/ │ │ │ ├── login.css │ │ │ ├── skins.css │ │ │ ├── style.css │ │ │ └── zen-checkbox.css │ │ ├── file/ │ │ │ └── rml.txt │ │ ├── fonts/ │ │ │ └── FontAwesome.otf │ │ ├── html/ │ │ │ └── ie.html │ │ ├── i18n/ │ │ │ └── messages.properties │ │ ├── js/ │ │ │ ├── cron.js │ │ │ ├── jquery.tmpl.js │ │ │ ├── plugins/ │ │ │ │ └── metisMenu/ │ │ │ │ └── jquery.metisMenu.js │ │ │ └── resize-tabs.js │ │ └── ruoyi/ │ │ ├── css/ │ │ │ └── ry-ui.css │ │ ├── index.js │ │ ├── js/ │ │ │ ├── common.js │ │ │ └── ry-ui.js │ │ ├── login.js │ │ └── register.js │ └── templates/ │ ├── demo/ │ │ ├── form/ │ │ │ ├── autocomplete.html │ │ │ ├── basic.html │ │ │ ├── button.html │ │ │ ├── cards.html │ │ │ ├── cxselect.html │ │ │ ├── datetime.html │ │ │ ├── duallistbox.html │ │ │ ├── grid.html │ │ │ ├── invoice.html │ │ │ ├── jasny.html │ │ │ ├── labels_tips.html │ │ │ ├── localrefresh.html │ │ │ ├── progress_bars.html │ │ │ ├── select.html │ │ │ ├── sortable.html │ │ │ ├── summernote.html │ │ │ ├── tabs_panels.html │ │ │ ├── timeline.html │ │ │ ├── upload.html │ │ │ ├── validate.html │ │ │ └── wizard.html │ │ ├── icon/ │ │ │ ├── fontawesome.html │ │ │ └── glyphicons.html │ │ ├── modal/ │ │ │ ├── dialog.html │ │ │ ├── form.html │ │ │ ├── layer.html │ │ │ ├── table/ │ │ │ │ ├── check.html │ │ │ │ ├── frame1.html │ │ │ │ ├── frame2.html │ │ │ │ ├── parent.html │ │ │ │ └── radio.html │ │ │ └── table.html │ │ ├── operate/ │ │ │ ├── add.html │ │ │ ├── detail.html │ │ │ ├── edit.html │ │ │ ├── other.html │ │ │ └── table.html │ │ ├── report/ │ │ │ ├── echarts.html │ │ │ ├── metrics.html │ │ │ ├── peity.html │ │ │ └── sparkline.html │ │ └── table/ │ │ ├── asynTree.html │ │ ├── button.html │ │ ├── child.html │ │ ├── cookie.html │ │ ├── curd.html │ │ ├── customView.html │ │ ├── data.html │ │ ├── detail.html │ │ ├── dynamicColumns.html │ │ ├── editable.html │ │ ├── event.html │ │ ├── export.html │ │ ├── exportSelected.html │ │ ├── fixedColumns.html │ │ ├── footer.html │ │ ├── groupHeader.html │ │ ├── headerStyle.html │ │ ├── image.html │ │ ├── multi.html │ │ ├── other.html │ │ ├── pageGo.html │ │ ├── params.html │ │ ├── print.html │ │ ├── refresh.html │ │ ├── remember.html │ │ ├── reorderColumns.html │ │ ├── reorderRows.html │ │ ├── resizable.html │ │ ├── search.html │ │ ├── subdata.html │ │ ├── textSearch.html │ │ └── virtualScroll.html │ ├── error/ │ │ ├── 404.html │ │ ├── 500.html │ │ ├── service.html │ │ └── unauth.html │ ├── include.html │ ├── index-topnav.html │ ├── index.html │ ├── lock.html │ ├── login.html │ ├── main.html │ ├── main_v1.html │ ├── monitor/ │ │ ├── cache/ │ │ │ └── cache.html │ │ ├── logininfor/ │ │ │ └── logininfor.html │ │ ├── online/ │ │ │ └── online.html │ │ ├── operlog/ │ │ │ ├── detail.html │ │ │ └── operlog.html │ │ └── server/ │ │ └── server.html │ ├── register.html │ ├── skin.html │ ├── system/ │ │ ├── config/ │ │ │ ├── add.html │ │ │ ├── config.html │ │ │ └── edit.html │ │ ├── dept/ │ │ │ ├── add.html │ │ │ ├── dept.html │ │ │ ├── edit.html │ │ │ └── tree.html │ │ ├── dict/ │ │ │ ├── data/ │ │ │ │ ├── add.html │ │ │ │ ├── data.html │ │ │ │ └── edit.html │ │ │ └── type/ │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ ├── tree.html │ │ │ └── type.html │ │ ├── menu/ │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ ├── icon.html │ │ │ ├── menu.html │ │ │ └── tree.html │ │ ├── notice/ │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ ├── notice.html │ │ │ └── view.html │ │ ├── post/ │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ └── post.html │ │ ├── role/ │ │ │ ├── add.html │ │ │ ├── authUser.html │ │ │ ├── dataScope.html │ │ │ ├── edit.html │ │ │ ├── role.html │ │ │ ├── selectUser.html │ │ │ └── view.html │ │ └── user/ │ │ ├── add.html │ │ ├── authRole.html │ │ ├── deptTree.html │ │ ├── edit.html │ │ ├── profile/ │ │ │ ├── avatar.html │ │ │ ├── profile.html │ │ │ └── resetPwd.html │ │ ├── resetPwd.html │ │ ├── user.html │ │ └── view.html │ └── tool/ │ └── build/ │ └── build.html ├── ruoyi-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── ruoyi/ │ └── common/ │ ├── annotation/ │ │ ├── Anonymous.java │ │ ├── DataScope.java │ │ ├── DataSource.java │ │ ├── Excel.java │ │ ├── Excels.java │ │ ├── Log.java │ │ ├── RepeatSubmit.java │ │ └── Sensitive.java │ ├── config/ │ │ ├── RuoYiConfig.java │ │ ├── ServerConfig.java │ │ ├── datasource/ │ │ │ └── DynamicDataSourceContextHolder.java │ │ ├── serializer/ │ │ │ └── SensitiveJsonSerializer.java │ │ └── thread/ │ │ └── ThreadPoolConfig.java │ ├── constant/ │ │ ├── Constants.java │ │ ├── GenConstants.java │ │ ├── PermissionConstants.java │ │ ├── ScheduleConstants.java │ │ ├── ShiroConstants.java │ │ └── UserConstants.java │ ├── core/ │ │ ├── context/ │ │ │ └── PermissionContextHolder.java │ │ ├── controller/ │ │ │ └── BaseController.java │ │ ├── domain/ │ │ │ ├── AjaxResult.java │ │ │ ├── BaseEntity.java │ │ │ ├── CxSelect.java │ │ │ ├── R.java │ │ │ ├── TreeEntity.java │ │ │ ├── Ztree.java │ │ │ └── entity/ │ │ │ ├── SysDept.java │ │ │ ├── SysDictData.java │ │ │ ├── SysDictType.java │ │ │ ├── SysMenu.java │ │ │ ├── SysRole.java │ │ │ └── SysUser.java │ │ ├── page/ │ │ │ ├── PageDomain.java │ │ │ ├── TableDataInfo.java │ │ │ └── TableSupport.java │ │ ├── session/ │ │ │ └── OnlineSession.java │ │ └── text/ │ │ ├── CharsetKit.java │ │ ├── Convert.java │ │ └── StrFormatter.java │ ├── enums/ │ │ ├── BusinessStatus.java │ │ ├── BusinessType.java │ │ ├── DataSourceType.java │ │ ├── DesensitizedType.java │ │ ├── OnlineStatus.java │ │ ├── OperatorType.java │ │ └── UserStatus.java │ ├── exception/ │ │ ├── DemoModeException.java │ │ ├── GlobalException.java │ │ ├── ServiceException.java │ │ ├── UtilException.java │ │ ├── base/ │ │ │ └── BaseException.java │ │ ├── file/ │ │ │ ├── FileException.java │ │ │ ├── FileNameLengthLimitExceededException.java │ │ │ ├── FileSizeLimitExceededException.java │ │ │ ├── FileUploadException.java │ │ │ └── InvalidExtensionException.java │ │ ├── job/ │ │ │ └── TaskException.java │ │ └── user/ │ │ ├── BlackListException.java │ │ ├── CaptchaException.java │ │ ├── RoleBlockedException.java │ │ ├── UserBlockedException.java │ │ ├── UserDeleteException.java │ │ ├── UserException.java │ │ ├── UserNotExistsException.java │ │ ├── UserPasswordNotMatchException.java │ │ ├── UserPasswordRetryLimitCountException.java │ │ └── UserPasswordRetryLimitExceedException.java │ ├── json/ │ │ ├── JSON.java │ │ └── JSONObject.java │ ├── utils/ │ │ ├── AddressUtils.java │ │ ├── Arith.java │ │ ├── CacheUtils.java │ │ ├── CookieUtils.java │ │ ├── DateUtils.java │ │ ├── DesensitizedUtil.java │ │ ├── DictUtils.java │ │ ├── ExceptionUtil.java │ │ ├── IpUtils.java │ │ ├── LogUtils.java │ │ ├── MapDataUtil.java │ │ ├── MessageUtils.java │ │ ├── PageUtils.java │ │ ├── ServletUtils.java │ │ ├── ShiroUtils.java │ │ ├── StringUtils.java │ │ ├── Threads.java │ │ ├── bean/ │ │ │ ├── BeanUtils.java │ │ │ └── BeanValidators.java │ │ ├── file/ │ │ │ ├── FileTypeUtils.java │ │ │ ├── FileUploadUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── ImageUtils.java │ │ │ └── MimeTypeUtils.java │ │ ├── html/ │ │ │ ├── EscapeUtil.java │ │ │ └── HTMLFilter.java │ │ ├── http/ │ │ │ ├── HttpUtils.java │ │ │ └── UserAgentUtils.java │ │ ├── poi/ │ │ │ ├── ExcelHandlerAdapter.java │ │ │ └── ExcelUtil.java │ │ ├── reflect/ │ │ │ └── ReflectUtils.java │ │ ├── security/ │ │ │ ├── CipherUtils.java │ │ │ ├── Md5Utils.java │ │ │ └── PermissionUtils.java │ │ ├── spring/ │ │ │ └── SpringUtils.java │ │ ├── sql/ │ │ │ └── SqlUtil.java │ │ └── uuid/ │ │ ├── IdUtils.java │ │ ├── Seq.java │ │ └── UUID.java │ └── xss/ │ ├── Xss.java │ ├── XssFilter.java │ ├── XssHttpServletRequestWrapper.java │ └── XssValidator.java ├── ruoyi-framework/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── ruoyi/ │ └── framework/ │ ├── aspectj/ │ │ ├── DataScopeAspect.java │ │ ├── DataSourceAspect.java │ │ ├── LogAspect.java │ │ └── PermissionsAspect.java │ ├── config/ │ │ ├── ApplicationConfig.java │ │ ├── CaptchaConfig.java │ │ ├── DruidConfig.java │ │ ├── FilterConfig.java │ │ ├── I18nConfig.java │ │ ├── KaptchaTextCreator.java │ │ ├── MyBatisConfig.java │ │ ├── ResourcesConfig.java │ │ ├── ShiroConfig.java │ │ └── properties/ │ │ └── DruidProperties.java │ ├── datasource/ │ │ └── DynamicDataSource.java │ ├── interceptor/ │ │ ├── RepeatSubmitInterceptor.java │ │ └── impl/ │ │ └── SameUrlDataInterceptor.java │ ├── manager/ │ │ ├── AsyncManager.java │ │ ├── ShutdownManager.java │ │ └── factory/ │ │ └── AsyncFactory.java │ ├── shiro/ │ │ ├── realm/ │ │ │ └── UserRealm.java │ │ ├── rememberMe/ │ │ │ └── CustomCookieRememberMeManager.java │ │ ├── service/ │ │ │ ├── SysLoginService.java │ │ │ ├── SysPasswordService.java │ │ │ ├── SysRegisterService.java │ │ │ └── SysShiroService.java │ │ ├── session/ │ │ │ ├── OnlineSessionDAO.java │ │ │ └── OnlineSessionFactory.java │ │ ├── util/ │ │ │ └── AuthorizationUtils.java │ │ └── web/ │ │ ├── CustomShiroFilterFactoryBean.java │ │ ├── filter/ │ │ │ ├── LogoutFilter.java │ │ │ ├── captcha/ │ │ │ │ └── CaptchaValidateFilter.java │ │ │ ├── csrf/ │ │ │ │ └── CsrfValidateFilter.java │ │ │ ├── kickout/ │ │ │ │ └── KickoutSessionFilter.java │ │ │ ├── online/ │ │ │ │ └── OnlineSessionFilter.java │ │ │ └── sync/ │ │ │ └── SyncOnlineSessionFilter.java │ │ └── session/ │ │ ├── OnlineWebSessionManager.java │ │ └── SpringSessionValidationScheduler.java │ └── web/ │ ├── domain/ │ │ ├── Server.java │ │ └── server/ │ │ ├── Cpu.java │ │ ├── Jvm.java │ │ ├── Mem.java │ │ ├── Sys.java │ │ └── SysFile.java │ ├── exception/ │ │ └── GlobalExceptionHandler.java │ └── service/ │ ├── CacheService.java │ ├── ConfigService.java │ ├── DictService.java │ └── PermissionService.java ├── ruoyi-generator/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── ruoyi/ │ │ └── generator/ │ │ ├── config/ │ │ │ └── GenConfig.java │ │ ├── controller/ │ │ │ └── GenController.java │ │ ├── domain/ │ │ │ ├── GenTable.java │ │ │ └── GenTableColumn.java │ │ ├── mapper/ │ │ │ ├── GenTableColumnMapper.java │ │ │ └── GenTableMapper.java │ │ ├── service/ │ │ │ ├── IGenTableColumnService.java │ │ │ ├── IGenTableService.java │ │ │ └── impl/ │ │ │ ├── GenTableColumnServiceImpl.java │ │ │ └── GenTableServiceImpl.java │ │ └── util/ │ │ ├── GenUtils.java │ │ ├── VelocityInitializer.java │ │ └── VelocityUtils.java │ └── resources/ │ ├── generator.yml │ ├── mapper/ │ │ └── generator/ │ │ ├── GenTableColumnMapper.xml │ │ └── GenTableMapper.xml │ ├── templates/ │ │ └── tool/ │ │ └── gen/ │ │ ├── createTable.html │ │ ├── edit.html │ │ ├── gen.html │ │ └── importTable.html │ └── vm/ │ ├── html/ │ │ ├── add.html.vm │ │ ├── edit.html.vm │ │ ├── list-tree.html.vm │ │ ├── list.html.vm │ │ └── tree.html.vm │ ├── java/ │ │ ├── controller.java.vm │ │ ├── domain.java.vm │ │ ├── mapper.java.vm │ │ ├── service.java.vm │ │ ├── serviceImpl.java.vm │ │ └── sub-domain.java.vm │ ├── sql/ │ │ └── sql.vm │ └── xml/ │ └── mapper.xml.vm ├── ruoyi-quartz/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── ruoyi/ │ │ └── quartz/ │ │ ├── config/ │ │ │ └── ScheduleConfig.java │ │ ├── controller/ │ │ │ ├── SysJobController.java │ │ │ └── SysJobLogController.java │ │ ├── domain/ │ │ │ ├── SysJob.java │ │ │ └── SysJobLog.java │ │ ├── mapper/ │ │ │ ├── SysJobLogMapper.java │ │ │ └── SysJobMapper.java │ │ ├── service/ │ │ │ ├── ISysJobLogService.java │ │ │ ├── ISysJobService.java │ │ │ └── impl/ │ │ │ ├── SysJobLogServiceImpl.java │ │ │ └── SysJobServiceImpl.java │ │ ├── task/ │ │ │ └── RyTask.java │ │ └── util/ │ │ ├── AbstractQuartzJob.java │ │ ├── CronUtils.java │ │ ├── JobInvokeUtil.java │ │ ├── QuartzDisallowConcurrentExecution.java │ │ ├── QuartzJobExecution.java │ │ └── ScheduleUtils.java │ └── resources/ │ ├── mapper/ │ │ └── quartz/ │ │ ├── SysJobLogMapper.xml │ │ └── SysJobMapper.xml │ └── templates/ │ └── monitor/ │ └── job/ │ ├── add.html │ ├── cron.html │ ├── detail.html │ ├── edit.html │ ├── job.html │ └── jobLog.html ├── ruoyi-system/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── ruoyi/ │ │ └── system/ │ │ ├── domain/ │ │ │ ├── SysConfig.java │ │ │ ├── SysLogininfor.java │ │ │ ├── SysNotice.java │ │ │ ├── SysNoticeRead.java │ │ │ ├── SysOperLog.java │ │ │ ├── SysPost.java │ │ │ ├── SysRoleDept.java │ │ │ ├── SysRoleMenu.java │ │ │ ├── SysUserOnline.java │ │ │ ├── SysUserPost.java │ │ │ └── SysUserRole.java │ │ ├── mapper/ │ │ │ ├── SysConfigMapper.java │ │ │ ├── SysDeptMapper.java │ │ │ ├── SysDictDataMapper.java │ │ │ ├── SysDictTypeMapper.java │ │ │ ├── SysLogininforMapper.java │ │ │ ├── SysMenuMapper.java │ │ │ ├── SysNoticeMapper.java │ │ │ ├── SysNoticeReadMapper.java │ │ │ ├── SysOperLogMapper.java │ │ │ ├── SysPostMapper.java │ │ │ ├── SysRoleDeptMapper.java │ │ │ ├── SysRoleMapper.java │ │ │ ├── SysRoleMenuMapper.java │ │ │ ├── SysUserMapper.java │ │ │ ├── SysUserOnlineMapper.java │ │ │ ├── SysUserPostMapper.java │ │ │ └── SysUserRoleMapper.java │ │ └── service/ │ │ ├── ISysConfigService.java │ │ ├── ISysDeptService.java │ │ ├── ISysDictDataService.java │ │ ├── ISysDictTypeService.java │ │ ├── ISysLogininforService.java │ │ ├── ISysMenuService.java │ │ ├── ISysNoticeReadService.java │ │ ├── ISysNoticeService.java │ │ ├── ISysOperLogService.java │ │ ├── ISysPostService.java │ │ ├── ISysRoleService.java │ │ ├── ISysUserOnlineService.java │ │ ├── ISysUserService.java │ │ └── impl/ │ │ ├── SysConfigServiceImpl.java │ │ ├── SysDeptServiceImpl.java │ │ ├── SysDictDataServiceImpl.java │ │ ├── SysDictTypeServiceImpl.java │ │ ├── SysLogininforServiceImpl.java │ │ ├── SysMenuServiceImpl.java │ │ ├── SysNoticeReadServiceImpl.java │ │ ├── SysNoticeServiceImpl.java │ │ ├── SysOperLogServiceImpl.java │ │ ├── SysPostServiceImpl.java │ │ ├── SysRoleServiceImpl.java │ │ ├── SysUserOnlineServiceImpl.java │ │ └── SysUserServiceImpl.java │ └── resources/ │ └── mapper/ │ └── system/ │ ├── SysConfigMapper.xml │ ├── SysDeptMapper.xml │ ├── SysDictDataMapper.xml │ ├── SysDictTypeMapper.xml │ ├── SysLogininforMapper.xml │ ├── SysMenuMapper.xml │ ├── SysNoticeMapper.xml │ ├── SysNoticeReadMapper.xml │ ├── SysOperLogMapper.xml │ ├── SysPostMapper.xml │ ├── SysRoleDeptMapper.xml │ ├── SysRoleMapper.xml │ ├── SysRoleMenuMapper.xml │ ├── SysUserMapper.xml │ ├── SysUserOnlineMapper.xml │ ├── SysUserPostMapper.xml │ └── SysUserRoleMapper.xml ├── ry.bat ├── ry.sh └── sql/ ├── quartz.sql ├── ruoyi.html ├── ruoyi.pdm └── ry_20260319.sql ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ custom: http://doc.ruoyi.vip/ruoyi/other/donate.html ================================================ FILE: .gitignore ================================================ ###################################################################### # Build Tools .gradle /build/ !gradle/wrapper/gradle-wrapper.jar target/ !.mvn/wrapper/maven-wrapper.jar ###################################################################### # IDE ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### JRebel ### rebel.xml ### NetBeans ### nbproject/private/ build/* nbbuild/ dist/ nbdist/ .nb-gradle/ ###################################################################### # Others *.log *.xml.versionsBackup *.swp !*/build/*.java !*/build/*.html !*/build/*.xml ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2018 RuoYi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

logo

RuoYi v4.8.2

基于SpringBoot开发的轻量级Java快速开发框架

## 平台简介 一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适的。于是利用空闲休息时间开始自己写了一套后台系统。如此有了若依。她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。 性别男,若依是给女儿取的名字(寓意:你若不离不弃,我必生死相依) 若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 * 前端基于 [Hplus(H+)](https://gitee.com/hplus_admin/hplus) 后台主题 UI 框架。 * 前后端分离版本,请移步[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) * 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)   ## 版本分支 RuoYi 框架提供 Spring Boot 2.x / 3.x / 4.x 多版本分支的并行维护。 | 名称 | 说明 | 地址 | | :---------------- | :------------------------ | :-------------------------------------------------- | | master 默认分支 | Spring Boot 4.x (JDK 17+) | https://gitee.com/y_project/RuoYi | | springboot3 分支 | Spring Boot 3.x (JDK 17+) | https://gitee.com/y_project/RuoYi/tree/springboot3 | | springboot2 分支 | Spring Boot 2.x (JDK 8+) | https://gitee.com/y_project/RuoYi/tree/springboot2 | ## 内置功能 1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 3. 岗位管理:配置系统用户所属担任职务。 4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 7. 参数管理:对系统动态配置常用参数。 8. 通知公告:系统通知公告信息发布维护。 9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 10. 登录日志:系统登录日志记录查询包含登录异常。 11. 在线用户:当前系统中活跃用户状态监控。 12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 14. 系统接口:根据业务代码自动生成相关的api接口文档。 15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 16. 缓存监控:对系统的缓存查询,删除、清空等操作。 17. 在线构建器:拖动表单元素生成相应的HTML代码。 18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 ## 在线体验 - admin/admin123 - 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 演示地址:http://ruoyi.vip 文档地址:http://doc.ruoyi.vip ## 演示图
## 若依交流群 QQ群: [![加入QQ群](https://img.shields.io/badge/已满-1389287-blue.svg)](https://jq.qq.com/?_wv=1027&k=5HBAaYN) [![加入QQ群](https://img.shields.io/badge/已满-1679294-blue.svg)](https://jq.qq.com/?_wv=1027&k=5cHeRVW) [![加入QQ群](https://img.shields.io/badge/已满-1529866-blue.svg)](https://jq.qq.com/?_wv=1027&k=53R0L5Z) [![加入QQ群](https://img.shields.io/badge/已满-1772718-blue.svg)](https://jq.qq.com/?_wv=1027&k=5g75dCU) [![加入QQ群](https://img.shields.io/badge/已满-1366522-blue.svg)](https://jq.qq.com/?_wv=1027&k=58cPoHA) [![加入QQ群](https://img.shields.io/badge/已满-1382251-blue.svg)](https://jq.qq.com/?_wv=1027&k=5Ofd4Pb) [![加入QQ群](https://img.shields.io/badge/已满-1145125-blue.svg)](https://jq.qq.com/?_wv=1027&k=5yugASz) [![加入QQ群](https://img.shields.io/badge/已满-86752435-blue.svg)](https://jq.qq.com/?_wv=1027&k=5Rf3d2P) [![加入QQ群](https://img.shields.io/badge/已满-134072510-blue.svg)](https://jq.qq.com/?_wv=1027&k=5ZIjaeP) [![加入QQ群](https://img.shields.io/badge/已满-210336300-blue.svg)](https://jq.qq.com/?_wv=1027&k=5CJw1jY) [![加入QQ群](https://img.shields.io/badge/已满-339522636-blue.svg)](https://jq.qq.com/?_wv=1027&k=5omzbKc) [![加入QQ群](https://img.shields.io/badge/已满-130035985-blue.svg)](https://jq.qq.com/?_wv=1027&k=qPIKBb7s) [![加入QQ群](https://img.shields.io/badge/已满-143151071-blue.svg)](https://jq.qq.com/?_wv=1027&k=4NsjKbtU) [![加入QQ群](https://img.shields.io/badge/已满-158781320-blue.svg)](https://jq.qq.com/?_wv=1027&k=VD2pkz2G) [![加入QQ群](https://img.shields.io/badge/已满-201531282-blue.svg)](https://jq.qq.com/?_wv=1027&k=HlshFwkJ) [![加入QQ群](https://img.shields.io/badge/已满-101526938-blue.svg)](https://jq.qq.com/?_wv=1027&k=0ARRrO9V) [![加入QQ群](https://img.shields.io/badge/已满-264355400-blue.svg)](https://jq.qq.com/?_wv=1027&k=up9k3ZXJ) [![加入QQ群](https://img.shields.io/badge/已满-298522656-blue.svg)](https://jq.qq.com/?_wv=1027&k=540WfdEr) [![加入QQ群](https://img.shields.io/badge/已满-139845794-blue.svg)](https://jq.qq.com/?_wv=1027&k=ss91fC4t) [![加入QQ群](https://img.shields.io/badge/已满-185760789-blue.svg)](https://jq.qq.com/?_wv=1027&k=Cqd66IKe) [![加入QQ群](https://img.shields.io/badge/已满-175104288-blue.svg)](https://jq.qq.com/?_wv=1027&k=7FplYUnR) [![加入QQ群](https://img.shields.io/badge/已满-174942938-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=lqMHu_5Fskm7H2S1vNAQTtzAUokVydwc&authKey=ptw0Fpch5pbNocML3CIJKKqZBaq2DI7cusKuzIgfMNiY3t9Pvd9hP%2BA8WYx3yaY1&noverify=0&group_code=174942938) [![加入QQ群](https://img.shields.io/badge/已满-287843737-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=blYlRDmwZXSXI5pVrPPU7ZJ1stFJ6Q2Q&authKey=ForGBWffHVlPt9NE3d7g4DoOIouBh%2BqvAj2lp1CLReHfZAUaK7SRrdwsChKpRJDJ&noverify=0&group_code=287843737) [![加入QQ群](https://img.shields.io/badge/已满-232896766-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=KTVAIhggR3rR3uZWK9A8kR4yYNREQ4jo&authKey=An4DUV9e7uK8I8VgBbp949z0ypQoDrOoqvVg%2FWOr2vuNNDMZUAMPvqHor6TFMIgz&noverify=0&group_code=232896766) [![加入QQ群](https://img.shields.io/badge/已满-180208928-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XwhV8deuZXt__yteR1clNanVSXzA-ugq&authKey=ezgwKqEZPdP%2FgC9I03OBkJb%2Biii8yvVfwrcQuu0%2FL6ILXcRdHYDBFKCXeoeBT0E6&noverify=0&group_code=180208928) [![加入QQ群](https://img.shields.io/badge/已满-140284548-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=WqsGDxpGkqOPeWGOf3I32f_rXxdhqYNr&authKey=kvdF5df7PO9bzWxmixKhZN6ShsECBiuGUmmzTZBWVr2MVOfJ8%2F4oD0Gws0rbgYfz&noverify=0&group_code=140284548) [![加入QQ群](https://img.shields.io/badge/177203794-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=z9z9jkkkinfAElKZk2FXqlN4XIXlXsMi&authKey=Sm9XMTV%2FFyANBrv9rVpMfMNcX4v1lVah3795O9VclQwU4DNzQcT5BLXTmTBouIkM&noverify=0&group_code=177203794) ================================================ FILE: bin/clean.bat ================================================ @echo off echo. echo [Ϣ] target· echo. %~d0 cd %~dp0 cd .. call mvn clean pause ================================================ FILE: bin/package.bat ================================================ @echo off echo. echo [Ϣ] Weḅwar/jarļ echo. %~d0 cd %~dp0 cd .. call mvn clean package -Dmaven.test.skip=true pause ================================================ FILE: bin/run.bat ================================================ @echo off echo. echo [Ϣ] ʹJarWeb̡ echo. cd %~dp0 cd ../ruoyi-admin/target set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m java -jar %JAVA_OPTS% ruoyi-admin.jar cd bin pause ================================================ FILE: pom.xml ================================================ 4.0.0 com.ruoyi ruoyi 4.8.2 ruoyi http://www.ruoyi.vip 若依管理系统 4.8.2 UTF-8 UTF-8 17 3.1.1 4.0.3 2.1.0 4.0.1 2.1.0 1.2.28 8.1.0 2.3.3 2.1.1 1.2.83 6.10.0 2.21.0 4.1.2 2.3 3.0.2 org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import com.alibaba druid-spring-boot-4-starter ${druid.version} pro.fessional kaptcha ${kaptcha.version} org.apache.shiro shiro-core jakarta ${shiro.version} org.apache.shiro shiro-web jakarta ${shiro.version} org.apache.shiro shiro-spring jakarta ${shiro.version} org.apache.shiro shiro-web org.apache.shiro shiro-ehcache ${shiro.version} com.github.theborakompanioni thymeleaf-extras-shiro ${thymeleaf.extras.shiro.version} org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis-spring-boot.version} nl.basjes.parse.useragent yauaa ${yauaa.version} com.github.pagehelper pagehelper-spring-boot-starter ${pagehelper.boot.version} com.github.oshi oshi-core ${oshi.version} org.springdoc springdoc-openapi-starter-webmvc-ui ${springdoc.version} commons-io commons-io ${commons.io.version} org.apache.poi poi-ooxml ${poi.version} org.apache.velocity velocity-engine-core ${velocity.version} com.alibaba fastjson ${fastjson.version} com.ruoyi ruoyi-quartz ${ruoyi.version} com.ruoyi ruoyi-generator ${ruoyi.version} com.ruoyi ruoyi-framework ${ruoyi.version} com.ruoyi ruoyi-system ${ruoyi.version} com.ruoyi ruoyi-common ${ruoyi.version} ruoyi-admin ruoyi-framework ruoyi-system ruoyi-quartz ruoyi-generator ruoyi-common pom org.apache.maven.plugins maven-compiler-plugin 3.13.0 true ${java.version} ${java.version} ${project.build.sourceEncoding} org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} public aliyun nexus https://maven.aliyun.com/repository/public true public aliyun nexus https://maven.aliyun.com/repository/public true false ================================================ FILE: ruoyi-admin/pom.xml ================================================ ruoyi com.ruoyi 4.8.2 4.0.0 jar ruoyi-admin web服务入口 org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-devtools true org.springdoc springdoc-openapi-starter-webmvc-ui com.mysql mysql-connector-j com.ruoyi ruoyi-framework com.ruoyi ruoyi-quartz com.ruoyi ruoyi-generator org.springframework.boot spring-boot-maven-plugin true repackage org.apache.maven.plugins maven-war-plugin 3.1.0 false ${project.artifactId} ${project.artifactId} ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java ================================================ package com.ruoyi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; /** * 启动程序 * * @author ruoyi */ @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) public class RuoYiApplication { public static void main(String[] args) { // System.setProperty("spring.devtools.restart.enabled", "false"); SpringApplication.run(RuoYiApplication.class, args); System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" + " .-------. ____ __ \n" + " | _ _ \\ \\ \\ / / \n" + " | ( ' ) | \\ _. / ' \n" + " |(_ o _) / _( )_ .' \n" + " | (_,_).' __ ___(_ o _)' \n" + " | |\\ \\ | || |(_,_)' \n" + " | | \\ `' /| `-' / \n" + " | | \\ / \\ / \n" + " ''-' `'-' `-..-' "); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/RuoYiServletInitializer.java ================================================ package com.ruoyi; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; /** * web容器中进行部署 * * @author ruoyi */ public class RuoYiServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(RuoYiApplication.class); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java ================================================ package com.ruoyi.web.controller.common; import java.util.ArrayList; import java.util.List; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.config.ServerConfig; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.FileUtils; /** * 通用请求处理 * * @author ruoyi */ @Controller @RequestMapping("/common") public class CommonController { private static final Logger log = LoggerFactory.getLogger(CommonController.class); @Autowired private ServerConfig serverConfig; private static final String FILE_DELIMETER = ","; /** * 通用下载请求 * * @param fileName 文件名称 * @param delete 是否删除 */ @GetMapping("/download") public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) { try { if (!FileUtils.checkAllowDownload(fileName)) { throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); } String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); String filePath = RuoYiConfig.getDownloadPath() + fileName; response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); FileUtils.setAttachmentResponseHeader(response, realFileName); FileUtils.writeBytes(filePath, response.getOutputStream()); if (delete) { FileUtils.deleteFile(filePath); } } catch (Exception e) { log.error("下载文件失败", e); } } /** * 通用上传请求(单个) */ @PostMapping("/upload") @ResponseBody public AjaxResult uploadFile(MultipartFile file) throws Exception { try { // 上传文件路径 String filePath = RuoYiConfig.getUploadPath(); // 上传并返回新文件名称 String fileName = FileUploadUtils.upload(filePath, file); String url = serverConfig.getUrl() + fileName; AjaxResult ajax = AjaxResult.success(); ajax.put("url", url); ajax.put("fileName", fileName); ajax.put("newFileName", FileUtils.getName(fileName)); ajax.put("originalFilename", file.getOriginalFilename()); return ajax; } catch (Exception e) { return AjaxResult.error(e.getMessage()); } } /** * 通用上传请求(多个) */ @PostMapping("/uploads") @ResponseBody public AjaxResult uploadFiles(List files) throws Exception { try { // 上传文件路径 String filePath = RuoYiConfig.getUploadPath(); List urls = new ArrayList(); List fileNames = new ArrayList(); List newFileNames = new ArrayList(); List originalFilenames = new ArrayList(); for (MultipartFile file : files) { // 上传并返回新文件名称 String fileName = FileUploadUtils.upload(filePath, file); String url = serverConfig.getUrl() + fileName; urls.add(url); fileNames.add(fileName); newFileNames.add(FileUtils.getName(fileName)); originalFilenames.add(file.getOriginalFilename()); } AjaxResult ajax = AjaxResult.success(); ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); return ajax; } catch (Exception e) { return AjaxResult.error(e.getMessage()); } } /** * 本地资源通用下载 */ @GetMapping("/download/resource") public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response) throws Exception { try { if (!FileUtils.checkAllowDownload(resource)) { throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); } // 本地资源路径 String localPath = RuoYiConfig.getProfile(); // 数据库资源地址 String downloadPath = localPath + FileUtils.stripPrefix(resource); // 下载名称 String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); FileUtils.setAttachmentResponseHeader(response, downloadName); FileUtils.writeBytes(downloadPath, response.getOutputStream()); } catch (Exception e) { log.error("下载文件失败", e); } } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoDialogController.java ================================================ package com.ruoyi.web.controller.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; /** * 模态窗口 * * @author ruoyi */ @Controller @RequestMapping("/demo/modal") public class DemoDialogController { private String prefix = "demo/modal"; /** * 模态窗口 */ @GetMapping("/dialog") public String dialog() { return prefix + "/dialog"; } /** * 弹层组件 */ @GetMapping("/layer") public String layer() { return prefix + "/layer"; } /** * 表单 */ @GetMapping("/form") public String form() { return prefix + "/form"; } /** * 表格 */ @GetMapping("/table") public String table() { return prefix + "/table"; } /** * 表格check */ @GetMapping("/check") public String check() { return prefix + "/table/check"; } /** * 表格radio */ @GetMapping("/radio") public String radio() { return prefix + "/table/radio"; } /** * 表格回传父窗体 */ @GetMapping("/parent") public String parent() { return prefix + "/table/parent"; } /** * 多层窗口frame1 */ @GetMapping("/frame1") public String frame1() { return prefix + "/table/frame1"; } /** * 多层窗口frame2 */ @GetMapping("/frame2") public String frame2() { return prefix + "/table/frame2"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoFormController.java ================================================ package com.ruoyi.web.controller.demo.controller; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSON; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.CxSelect; import com.ruoyi.common.json.JSONObject; import com.ruoyi.common.json.JSONObject.JSONArray; import com.ruoyi.common.utils.StringUtils; /** * 表单相关 * * @author ruoyi */ @Controller @RequestMapping("/demo/form") public class DemoFormController { private String prefix = "demo/form"; private final static List users = new ArrayList(); { users.add(new UserFormModel(1, "1000001", "测试1", "15888888888")); users.add(new UserFormModel(2, "1000002", "测试2", "15666666666")); users.add(new UserFormModel(3, "1000003", "测试3", "15666666666")); users.add(new UserFormModel(4, "1000004", "测试4", "15666666666")); users.add(new UserFormModel(5, "1000005", "测试5", "15666666666")); } /** * 按钮页 */ @GetMapping("/button") public String button() { return prefix + "/button"; } /** * 下拉框 */ @GetMapping("/select") public String select() { return prefix + "/select"; } /** * 时间轴 */ @GetMapping("/timeline") public String timeline() { return prefix + "/timeline"; } /** * 进度条 */ @GetMapping("/progress_bars") public String progress_bars() { return prefix + "/progress_bars"; } /** * 表单校验 */ @GetMapping("/validate") public String validate() { return prefix + "/validate"; } /** * 功能扩展(包含文件上传) */ @GetMapping("/jasny") public String jasny() { return prefix + "/jasny"; } /** * 拖动排序 */ @GetMapping("/sortable") public String sortable() { return prefix + "/sortable"; } /** * 单据打印 */ @GetMapping("/invoice") public String invoice() { return prefix + "/invoice"; } /** * 标签 & 提示 */ @GetMapping("/labels_tips") public String labels_tips() { return prefix + "/labels_tips"; } /** * 选项卡 & 面板 */ @GetMapping("/tabs_panels") public String tabs_panels() { return prefix + "/tabs_panels"; } /** * 栅格 */ @GetMapping("/grid") public String grid() { return prefix + "/grid"; } /** * 表单向导 */ @GetMapping("/wizard") public String wizard() { return prefix + "/wizard"; } /** * 文件上传 */ @GetMapping("/upload") public String upload() { return prefix + "/upload"; } /** * 日期和时间页 */ @GetMapping("/datetime") public String datetime() { return prefix + "/datetime"; } /** * 左右互选组件 */ @GetMapping("/duallistbox") public String duallistbox() { return prefix + "/duallistbox"; } /** * 基本表单 */ @GetMapping("/basic") public String basic() { return prefix + "/basic"; } /** * 卡片列表 */ @GetMapping("/cards") public String cards() { return prefix + "/cards"; } /** * summernote 富文本编辑器 */ @GetMapping("/summernote") public String summernote() { return prefix + "/summernote"; } /** * 搜索自动补全 */ @GetMapping("/autocomplete") public String autocomplete() { return prefix + "/autocomplete"; } /** * 多级联动下拉 */ @GetMapping("/cxselect") public String cxselect(ModelMap mmap) { CxSelect cxSelectTB = new CxSelect(); cxSelectTB.setN("淘宝"); cxSelectTB.setV("taobao"); CxSelect cxSelectTm = new CxSelect(); cxSelectTm.setN("天猫"); cxSelectTm.setV("tm"); CxSelect cxSelectJhs = new CxSelect(); cxSelectJhs.setN("聚划算"); cxSelectJhs.setV("jhs"); List tmList = new ArrayList(); tmList.add(cxSelectTm); tmList.add(cxSelectJhs); cxSelectTB.setS(tmList); CxSelect cxSelectJD = new CxSelect(); cxSelectJD.setN("京东"); cxSelectJD.setV("jd"); CxSelect cxSelectCs = new CxSelect(); cxSelectCs.setN("京东超市"); cxSelectCs.setV("jdcs"); CxSelect cxSelectSx = new CxSelect(); cxSelectSx.setN("京东生鲜"); cxSelectSx.setV("jdsx"); List jdList = new ArrayList(); jdList.add(cxSelectCs); jdList.add(cxSelectSx); cxSelectJD.setS(jdList); List cxList = new ArrayList(); cxList.add(cxSelectTB); cxList.add(cxSelectJD); mmap.put("data", JSON.toJSON(cxList)); return prefix + "/cxselect"; } /** * 局部刷新 */ @GetMapping("/localrefresh") public String localRefresh(ModelMap mmap) { JSONArray list = new JSONArray(); JSONObject item = new JSONObject(); item.put("name", "这条任务数据是由ModelMap传递到页面的,点击添加按钮后会将这条数据替换为新数据"); item.put("type", "默认"); item.put("date", "2020.06.10"); list.add(item); mmap.put("tasks", list); mmap.put("min", 2); mmap.put("max", 10); return prefix + "/localrefresh"; } /** * 局部刷新-添加任务 * * @param fragment 页面中的模板名称 * @param taskName 任务名称 */ @PostMapping("/localrefresh/task") public String localRefreshTask(String taskName, ModelMap mmap) { JSONArray list = new JSONArray(); JSONObject item = new JSONObject(); item.put("name", StringUtils.defaultIfBlank(taskName, "通过电话销售过程中了解各盛市的设备仪器使用、采购情况及相关重要追踪人")); item.put("type", "新增"); item.put("date", "2018.06.10"); list.add(item); item = new JSONObject(); item.put("name", "提高自己电话营销技巧,灵活专业地与客户进行电话交流"); item.put("type", "新增"); item.put("date", "2018.06.12"); list.add(item); mmap.put("tasks", list); return prefix + "/localrefresh::fragment-tasklist"; } /** * 模拟数据 */ @GetMapping("/cityData") @ResponseBody public String cityData() { String data = "[{\"n\":\"湖南省\",\"s\":[{\"n\":\"长沙市\",\"s\":[{\"n\":\"芙蓉区\"},{\"n\":\"天心区\"},{\"n\":\"岳麓区\"},{\"n\":\"开福区\"},{\"n\":\"雨花区\"},{\"n\":\"望城区\"},{\"n\":\"长沙县\"},{\"n\":\"宁乡县\"},{\"n\":\"浏阳市\"}]},{\"n\":\"株洲市\",\"s\":[{\"n\":\"荷塘区\"},{\"n\":\"芦淞区\"},{\"n\":\"石峰区\"},{\"n\":\"天元区\"},{\"n\":\"株洲县\"},{\"n\":\"攸县\"},{\"n\":\"茶陵县\"},{\"n\":\"炎陵县\"},{\"n\":\"醴陵市\"}]},{\"n\":\"湘潭市\",\"s\":[{\"n\":\"雨湖区\"},{\"n\":\"岳塘区\"},{\"n\":\"湘潭县\"},{\"n\":\"湘乡市\"},{\"n\":\"韶山市\"}]},{\"n\":\"衡阳市\",\"s\":[{\"n\":\"珠晖区\"},{\"n\":\"雁峰区\"},{\"n\":\"石鼓区\"},{\"n\":\"蒸湘区\"},{\"n\":\"南岳区\"},{\"n\":\"衡阳县\"},{\"n\":\"衡南县\"},{\"n\":\"衡山县\"},{\"n\":\"衡东县\"},{\"n\":\"祁东县\"},{\"n\":\"耒阳市\"},{\"n\":\"常宁市\"}]},{\"n\":\"邵阳市\",\"s\":[{\"n\":\"双清区\"},{\"n\":\"大祥区\"},{\"n\":\"北塔区\"},{\"n\":\"邵东县\"},{\"n\":\"新邵县\"},{\"n\":\"邵阳县\"},{\"n\":\"隆回县\"},{\"n\":\"洞口县\"},{\"n\":\"绥宁县\"},{\"n\":\"新宁县\"},{\"n\":\"城步苗族自治县\"},{\"n\":\"武冈市\"}]},{\"n\":\"岳阳市\",\"s\":[{\"n\":\"岳阳楼区\"},{\"n\":\"云溪区\"},{\"n\":\"君山区\"},{\"n\":\"岳阳县\"},{\"n\":\"华容县\"},{\"n\":\"湘阴县\"},{\"n\":\"平江县\"},{\"n\":\"汨罗市\"},{\"n\":\"临湘市\"}]},{\"n\":\"常德市\",\"s\":[{\"n\":\"武陵区\"},{\"n\":\"鼎城区\"},{\"n\":\"安乡县\"},{\"n\":\"汉寿县\"},{\"n\":\"澧县\"},{\"n\":\"临澧县\"},{\"n\":\"桃源县\"},{\"n\":\"石门县\"},{\"n\":\"津市市\"}]},{\"n\":\"张家界市\",\"s\":[{\"n\":\"永定区\"},{\"n\":\"武陵源区\"},{\"n\":\"慈利县\"},{\"n\":\"桑植县\"}]},{\"n\":\"益阳市\",\"s\":[{\"n\":\"资阳区\"},{\"n\":\"赫山区\"},{\"n\":\"南县\"},{\"n\":\"桃江县\"},{\"n\":\"安化县\"},{\"n\":\"沅江市\"}]},{\"n\":\"郴州市\",\"s\":[{\"n\":\"北湖区\"},{\"n\":\"苏仙区\"},{\"n\":\"桂阳县\"},{\"n\":\"宜章县\"},{\"n\":\"永兴县\"},{\"n\":\"嘉禾县\"},{\"n\":\"临武县\"},{\"n\":\"汝城县\"},{\"n\":\"桂东县\"},{\"n\":\"安仁县\"},{\"n\":\"资兴市\"}]},{\"n\":\"永州市\",\"s\":[{\"n\":\"零陵区\"},{\"n\":\"冷水滩区\"},{\"n\":\"祁阳县\"},{\"n\":\"东安县\"},{\"n\":\"双牌县\"},{\"n\":\"道县\"},{\"n\":\"江永县\"},{\"n\":\"宁远县\"},{\"n\":\"蓝山县\"},{\"n\":\"新田县\"},{\"n\":\"江华瑶族自治县\"}]},{\"n\":\"怀化市\",\"s\":[{\"n\":\"鹤城区\"},{\"n\":\"中方县\"},{\"n\":\"沅陵县\"},{\"n\":\"辰溪县\"},{\"n\":\"溆浦县\"},{\"n\":\"会同县\"},{\"n\":\"麻阳苗族自治县\"},{\"n\":\"新晃侗族自治县\"},{\"n\":\"芷江侗族自治县\"},{\"n\":\"靖州苗族侗族自治县\"},{\"n\":\"通道侗族自治县\"},{\"n\":\"洪江市\"}]},{\"n\":\"娄底市\",\"s\":[{\"n\":\"娄星区\"},{\"n\":\"双峰县\"},{\"n\":\"新化县\"},{\"n\":\"冷水江市\"},{\"n\":\"涟源市\"}]},{\"n\":\"湘西土家族苗族自治州\",\"s\":[{\"n\":\"吉首市\"},{\"n\":\"泸溪县\"},{\"n\":\"凤凰县\"},{\"n\":\"花垣县\"},{\"n\":\"保靖县\"},{\"n\":\"古丈县\"},{\"n\":\"永顺县\"},{\"n\":\"龙山县\"}]}]},{\"n\":\"广东省\",\"s\":[{\"n\":\"广州市\",\"s\":[{\"n\":\"荔湾区\"},{\"n\":\"越秀区\"},{\"n\":\"海珠区\"},{\"n\":\"天河区\"},{\"n\":\"白云区\"},{\"n\":\"黄埔区\"},{\"n\":\"番禺区\"},{\"n\":\"花都区\"},{\"n\":\"南沙区\"},{\"n\":\"萝岗区\"},{\"n\":\"增城市\"},{\"n\":\"从化市\"}]},{\"n\":\"韶关市\",\"s\":[{\"n\":\"武江区\"},{\"n\":\"浈江区\"},{\"n\":\"曲江区\"},{\"n\":\"始兴县\"},{\"n\":\"仁化县\"},{\"n\":\"翁源县\"},{\"n\":\"乳源瑶族自治县\"},{\"n\":\"新丰县\"},{\"n\":\"乐昌市\"},{\"n\":\"南雄市\"}]},{\"n\":\"深圳市\",\"s\":[{\"n\":\"罗湖区\"},{\"n\":\"福田区\"},{\"n\":\"南山区\"},{\"n\":\"宝安区\"},{\"n\":\"龙岗区\"},{\"n\":\"盐田区\"}]},{\"n\":\"珠海市\",\"s\":[{\"n\":\"香洲区\"},{\"n\":\"斗门区\"},{\"n\":\"金湾区\"}]},{\"n\":\"汕头市\",\"s\":[{\"n\":\"龙湖区\"},{\"n\":\"金平区\"},{\"n\":\"濠江区\"},{\"n\":\"潮阳区\"},{\"n\":\"潮南区\"},{\"n\":\"澄海区\"},{\"n\":\"南澳县\"}]},{\"n\":\"佛山市\",\"s\":[{\"n\":\"禅城区\"},{\"n\":\"南海区\"},{\"n\":\"顺德区\"},{\"n\":\"三水区\"},{\"n\":\"高明区\"}]},{\"n\":\"江门市\",\"s\":[{\"n\":\"蓬江区\"},{\"n\":\"江海区\"},{\"n\":\"新会区\"},{\"n\":\"台山市\"},{\"n\":\"开平市\"},{\"n\":\"鹤山市\"},{\"n\":\"恩平市\"}]},{\"n\":\"湛江市\",\"s\":[{\"n\":\"赤坎区\"},{\"n\":\"霞山区\"},{\"n\":\"坡头区\"},{\"n\":\"麻章区\"},{\"n\":\"遂溪县\"},{\"n\":\"徐闻县\"},{\"n\":\"廉江市\"},{\"n\":\"雷州市\"},{\"n\":\"吴川市\"}]},{\"n\":\"茂名市\",\"s\":[{\"n\":\"茂南区\"},{\"n\":\"茂港区\"},{\"n\":\"电白县\"},{\"n\":\"高州市\"},{\"n\":\"化州市\"},{\"n\":\"信宜市\"}]},{\"n\":\"肇庆市\",\"s\":[{\"n\":\"端州区\"},{\"n\":\"鼎湖区\"},{\"n\":\"广宁县\"},{\"n\":\"怀集县\"},{\"n\":\"封开县\"},{\"n\":\"德庆县\"},{\"n\":\"高要市\"},{\"n\":\"四会市\"}]},{\"n\":\"惠州市\",\"s\":[{\"n\":\"惠城区\"},{\"n\":\"惠阳区\"},{\"n\":\"博罗县\"},{\"n\":\"惠东县\"},{\"n\":\"龙门县\"}]},{\"n\":\"梅州市\",\"s\":[{\"n\":\"梅江区\"},{\"n\":\"梅县\"},{\"n\":\"大埔县\"},{\"n\":\"丰顺县\"},{\"n\":\"五华县\"},{\"n\":\"平远县\"},{\"n\":\"蕉岭县\"},{\"n\":\"兴宁市\"}]},{\"n\":\"汕尾市\",\"s\":[{\"n\":\"城区\"},{\"n\":\"海丰县\"},{\"n\":\"陆河县\"},{\"n\":\"陆丰市\"}]},{\"n\":\"河源市\",\"s\":[{\"n\":\"源城区\"},{\"n\":\"紫金县\"},{\"n\":\"龙川县\"},{\"n\":\"连平县\"},{\"n\":\"和平县\"},{\"n\":\"东源县\"}]},{\"n\":\"阳江市\",\"s\":[{\"n\":\"江城区\"},{\"n\":\"阳西县\"},{\"n\":\"阳东县\"},{\"n\":\"阳春市\"}]},{\"n\":\"清远市\",\"s\":[{\"n\":\"清城区\"},{\"n\":\"清新区\"},{\"n\":\"佛冈县\"},{\"n\":\"阳山县\"},{\"n\":\"连山壮族瑶族自治县\"},{\"n\":\"连南瑶族自治县\"},{\"n\":\"英德市\"},{\"n\":\"连州市\"}]},{\"n\":\"东莞市\"},{\"n\":\"中山市\"},{\"n\":\"潮州市\",\"s\":[{\"n\":\"湘桥区\"},{\"n\":\"潮安区\"},{\"n\":\"饶平县\"}]},{\"n\":\"揭阳市\",\"s\":[{\"n\":\"榕城区\"},{\"n\":\"揭东区\"},{\"n\":\"揭西县\"},{\"n\":\"惠来县\"},{\"n\":\"普宁市\"}]},{\"n\":\"云浮市\",\"s\":[{\"n\":\"云城区\"},{\"n\":\"新兴县\"},{\"n\":\"郁南县\"},{\"n\":\"云安县\"},{\"n\":\"罗定市\"}]}]}]"; return data; } /** * 获取用户数据 */ @GetMapping("/userModel") @ResponseBody public AjaxResult userModel() { AjaxResult ajax = new AjaxResult(); ajax.put("code", 200); ajax.put("value", users); return ajax; } /** * 获取数据集合 */ @GetMapping("/collection") @ResponseBody public AjaxResult collection() { String[] array = { "ruoyi 1", "ruoyi 2", "ruoyi 3", "ruoyi 4", "ruoyi 5" }; AjaxResult ajax = new AjaxResult(); ajax.put("value", array); return ajax; } } class UserFormModel { /** 用户ID */ private int userId; /** 用户编号 */ private String userCode; /** 用户姓名 */ private String userName; /** 用户手机 */ private String userPhone; public UserFormModel() { } public UserFormModel(int userId, String userCode, String userName, String userPhone) { this.userId = userId; this.userCode = userCode; this.userName = userName; this.userPhone = userPhone; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getUserCode() { return userCode; } public void setUserCode(String userCode) { this.userCode = userCode; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPhone() { return userPhone; } public void setUserPhone(String userPhone) { this.userPhone = userPhone; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoIconController.java ================================================ package com.ruoyi.web.controller.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; /** * 图标相关 * * @author ruoyi */ @Controller @RequestMapping("/demo/icon") public class DemoIconController { private String prefix = "demo/icon"; /** * FontAwesome图标 */ @GetMapping("/fontawesome") public String fontAwesome() { return prefix + "/fontawesome"; } /** * Glyphicons图标 */ @GetMapping("/glyphicons") public String glyphicons() { return prefix + "/glyphicons"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoOperateController.java ================================================ package com.ruoyi.web.controller.demo.controller; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.PageDomain; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableSupport; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.web.controller.demo.domain.CustomerModel; import com.ruoyi.web.controller.demo.domain.UserOperateModel; /** * 操作控制 * * @author ruoyi */ @Controller @RequestMapping("/demo/operate") public class DemoOperateController extends BaseController { private String prefix = "demo/operate"; private final static Map users = new LinkedHashMap(); { users.put(1, new UserOperateModel(1, "1000001", "测试1", "0", "15888888888", "ry@qq.com", 150.0, "0")); users.put(2, new UserOperateModel(2, "1000002", "测试2", "1", "15666666666", "ry@qq.com", 180.0, "1")); users.put(3, new UserOperateModel(3, "1000003", "测试3", "0", "15666666666", "ry@qq.com", 110.0, "1")); users.put(4, new UserOperateModel(4, "1000004", "测试4", "1", "15666666666", "ry@qq.com", 220.0, "1")); users.put(5, new UserOperateModel(5, "1000005", "测试5", "0", "15666666666", "ry@qq.com", 140.0, "1")); users.put(6, new UserOperateModel(6, "1000006", "测试6", "1", "15666666666", "ry@qq.com", 330.0, "1")); users.put(7, new UserOperateModel(7, "1000007", "测试7", "0", "15666666666", "ry@qq.com", 160.0, "1")); users.put(8, new UserOperateModel(8, "1000008", "测试8", "1", "15666666666", "ry@qq.com", 170.0, "1")); users.put(9, new UserOperateModel(9, "1000009", "测试9", "0", "15666666666", "ry@qq.com", 180.0, "1")); users.put(10, new UserOperateModel(10, "1000010", "测试10", "0", "15666666666", "ry@qq.com", 210.0, "1")); users.put(11, new UserOperateModel(11, "1000011", "测试11", "1", "15666666666", "ry@qq.com", 110.0, "1")); users.put(12, new UserOperateModel(12, "1000012", "测试12", "0", "15666666666", "ry@qq.com", 120.0, "1")); users.put(13, new UserOperateModel(13, "1000013", "测试13", "1", "15666666666", "ry@qq.com", 380.0, "1")); users.put(14, new UserOperateModel(14, "1000014", "测试14", "0", "15666666666", "ry@qq.com", 280.0, "1")); users.put(15, new UserOperateModel(15, "1000015", "测试15", "0", "15666666666", "ry@qq.com", 570.0, "1")); users.put(16, new UserOperateModel(16, "1000016", "测试16", "1", "15666666666", "ry@qq.com", 260.0, "1")); users.put(17, new UserOperateModel(17, "1000017", "测试17", "1", "15666666666", "ry@qq.com", 210.0, "1")); users.put(18, new UserOperateModel(18, "1000018", "测试18", "1", "15666666666", "ry@qq.com", 340.0, "1")); users.put(19, new UserOperateModel(19, "1000019", "测试19", "1", "15666666666", "ry@qq.com", 160.0, "1")); users.put(20, new UserOperateModel(20, "1000020", "测试20", "1", "15666666666", "ry@qq.com", 220.0, "1")); users.put(21, new UserOperateModel(21, "1000021", "测试21", "1", "15666666666", "ry@qq.com", 120.0, "1")); users.put(22, new UserOperateModel(22, "1000022", "测试22", "1", "15666666666", "ry@qq.com", 130.0, "1")); users.put(23, new UserOperateModel(23, "1000023", "测试23", "1", "15666666666", "ry@qq.com", 490.0, "1")); users.put(24, new UserOperateModel(24, "1000024", "测试24", "1", "15666666666", "ry@qq.com", 570.0, "1")); users.put(25, new UserOperateModel(25, "1000025", "测试25", "1", "15666666666", "ry@qq.com", 250.0, "1")); users.put(26, new UserOperateModel(26, "1000026", "测试26", "1", "15666666666", "ry@qq.com", 250.0, "1")); } /** * 表格 */ @GetMapping("/table") public String table() { return prefix + "/table"; } /** * 其他 */ @GetMapping("/other") public String other() { return prefix + "/other"; } /** * 查询数据 */ @PostMapping("/list") @ResponseBody public TableDataInfo list(UserOperateModel userModel) { TableDataInfo rspData = new TableDataInfo(); List userList = new ArrayList(users.values()); // 查询条件过滤 if (StringUtils.isNotEmpty(userModel.getSearchValue())) { userList.clear(); for (Map.Entry entry : users.entrySet()) { if (entry.getValue().getUserName().equals(userModel.getSearchValue())) { userList.add(entry.getValue()); } } } else if (StringUtils.isNotEmpty(userModel.getUserName())) { userList.clear(); for (Map.Entry entry : users.entrySet()) { if (entry.getValue().getUserName().equals(userModel.getUserName())) { userList.add(entry.getValue()); } } } PageDomain pageDomain = TableSupport.buildPageRequest(); if (null == pageDomain.getPageNum() || null == pageDomain.getPageSize()) { rspData.setRows(userList); rspData.setTotal(userList.size()); return rspData; } Integer pageNum = (pageDomain.getPageNum() - 1) * 10; Integer pageSize = pageDomain.getPageNum() * 10; if (pageSize > userList.size()) { pageSize = userList.size(); } rspData.setRows(userList.subList(pageNum, pageSize)); rspData.setTotal(userList.size()); return rspData; } /** * 新增用户 */ @GetMapping("/add") public String add(ModelMap mmap) { return prefix + "/add"; } /** * 新增保存用户 */ @PostMapping("/add") @ResponseBody public AjaxResult addSave(UserOperateModel user) { Integer userId = users.size() + 1; user.setUserId(userId); return AjaxResult.success(users.put(userId, user)); } /** * 新增保存主子表信息 */ @PostMapping("/customer/add") @ResponseBody public AjaxResult addSave(CustomerModel customerModel) { System.out.println(customerModel.toString()); return AjaxResult.success(); } /** * 修改用户 */ @GetMapping("/edit/{userId}") public String edit(@PathVariable("userId") Integer userId, ModelMap mmap) { mmap.put("user", users.get(userId)); return prefix + "/edit"; } /** * 修改保存用户 */ @PostMapping("/edit") @ResponseBody public AjaxResult editSave(UserOperateModel user) { return AjaxResult.success(users.put(user.getUserId(), user)); } /** * 导出 */ @PostMapping("/export") @ResponseBody public AjaxResult export(UserOperateModel user) { List list = new ArrayList(users.values()); ExcelUtil util = new ExcelUtil(UserOperateModel.class); return util.exportExcel(list, "用户数据"); } /** * 下载模板 */ @GetMapping("/importTemplate") @ResponseBody public AjaxResult importTemplate() { ExcelUtil util = new ExcelUtil(UserOperateModel.class); return util.importTemplateExcel("用户数据"); } /** * 导入数据 */ @PostMapping("/importData") @ResponseBody public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { ExcelUtil util = new ExcelUtil(UserOperateModel.class); List userList = util.importExcel(file.getInputStream()); String message = importUser(userList, updateSupport); return AjaxResult.success(message); } /** * 删除用户 */ @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { Integer[] userIds = Convert.toIntArray(ids); for (Integer userId : userIds) { users.remove(userId); } return AjaxResult.success(); } /** * 查看详细 */ @GetMapping("/detail/{userId}") public String detail(@PathVariable("userId") Integer userId, ModelMap mmap) { mmap.put("user", users.get(userId)); return prefix + "/detail"; } @PostMapping("/clean") @ResponseBody public AjaxResult clean() { users.clear(); return success(); } /** * 导入用户数据 * * @param userList 用户数据列表 * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 * @return 结果 */ public String importUser(List userList, Boolean isUpdateSupport) { if (StringUtils.isNull(userList) || userList.size() == 0) { throw new ServiceException("导入用户数据不能为空!"); } int successNum = 0; int failureNum = 0; StringBuilder successMsg = new StringBuilder(); StringBuilder failureMsg = new StringBuilder(); for (UserOperateModel user : userList) { try { // 验证是否存在这个用户 boolean userFlag = false; for (Map.Entry entry : users.entrySet()) { if (entry.getValue().getUserName().equals(user.getUserName())) { userFlag = true; break; } } if (!userFlag) { Integer userId = users.size() + 1; user.setUserId(userId); users.put(userId, user); successNum++; successMsg.append("
" + successNum + "、用户 " + user.getUserName() + " 导入成功"); } else if (isUpdateSupport) { users.put(user.getUserId(), user); successNum++; successMsg.append("
" + successNum + "、用户 " + user.getUserName() + " 更新成功"); } else { failureNum++; failureMsg.append("
" + failureNum + "、用户 " + user.getUserName() + " 已存在"); } } catch (Exception e) { failureNum++; String msg = "
" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; failureMsg.append(msg + e.getMessage()); } } if (failureNum > 0) { failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); throw new ServiceException(failureMsg.toString()); } else { successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); } return successMsg.toString(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoReportController.java ================================================ package com.ruoyi.web.controller.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; /** * 报表 * * @author ruoyi */ @Controller @RequestMapping("/demo/report") public class DemoReportController { private String prefix = "demo/report"; /** * 百度ECharts */ @GetMapping("/echarts") public String echarts() { return prefix + "/echarts"; } /** * 图表插件 */ @GetMapping("/peity") public String peity() { return prefix + "/peity"; } /** * 线状图插件 */ @GetMapping("/sparkline") public String sparkline() { return prefix + "/sparkline"; } /** * 图表组合 */ @GetMapping("/metrics") public String metrics() { return prefix + "/metrics"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/controller/DemoTableController.java ================================================ package com.ruoyi.web.controller.demo.controller; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.page.PageDomain; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableSupport; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; /** * 表格相关 * * @author ruoyi */ @Controller @RequestMapping("/demo/table") public class DemoTableController extends BaseController { private String prefix = "demo/table"; private final static List users = new ArrayList(); { users.add(new UserTableModel(1, "1000001", "测试1", "0", "15888888888", "ry@qq.com", 150.0, "0")); users.add(new UserTableModel(2, "1000002", "测试2", "1", "15666666666", "ry@qq.com", 180.0, "1")); users.add(new UserTableModel(3, "1000003", "测试3", "0", "15666666666", "ry@qq.com", 110.0, "1")); users.add(new UserTableModel(4, "1000004", "测试4", "1", "15666666666", "ry@qq.com", 220.0, "1")); users.add(new UserTableModel(5, "1000005", "测试5", "0", "15666666666", "ry@qq.com", 140.0, "1")); users.add(new UserTableModel(6, "1000006", "测试6", "1", "15666666666", "ry@qq.com", 330.0, "1")); users.add(new UserTableModel(7, "1000007", "测试7", "0", "15666666666", "ry@qq.com", 160.0, "1")); users.add(new UserTableModel(8, "1000008", "测试8", "1", "15666666666", "ry@qq.com", 170.0, "1")); users.add(new UserTableModel(9, "1000009", "测试9", "0", "15666666666", "ry@qq.com", 180.0, "1")); users.add(new UserTableModel(10, "1000010", "测试10", "0", "15666666666", "ry@qq.com", 210.0, "1")); users.add(new UserTableModel(11, "1000011", "测试11", "1", "15666666666", "ry@qq.com", 110.0, "1")); users.add(new UserTableModel(12, "1000012", "测试12", "0", "15666666666", "ry@qq.com", 120.0, "1")); users.add(new UserTableModel(13, "1000013", "测试13", "1", "15666666666", "ry@qq.com", 380.0, "1")); users.add(new UserTableModel(14, "1000014", "测试14", "0", "15666666666", "ry@qq.com", 280.0, "1")); users.add(new UserTableModel(15, "1000015", "测试15", "0", "15666666666", "ry@qq.com", 570.0, "1")); users.add(new UserTableModel(16, "1000016", "测试16", "1", "15666666666", "ry@qq.com", 260.0, "1")); users.add(new UserTableModel(17, "1000017", "测试17", "1", "15666666666", "ry@qq.com", 210.0, "1")); users.add(new UserTableModel(18, "1000018", "测试18", "1", "15666666666", "ry@qq.com", 340.0, "1")); users.add(new UserTableModel(19, "1000019", "测试19", "1", "15666666666", "ry@qq.com", 160.0, "1")); users.add(new UserTableModel(20, "1000020", "测试20", "1", "15666666666", "ry@qq.com", 220.0, "1")); users.add(new UserTableModel(21, "1000021", "测试21", "1", "15666666666", "ry@qq.com", 120.0, "1")); users.add(new UserTableModel(22, "1000022", "测试22", "1", "15666666666", "ry@qq.com", 130.0, "1")); users.add(new UserTableModel(23, "1000023", "测试23", "1", "15666666666", "ry@qq.com", 490.0, "1")); users.add(new UserTableModel(24, "1000024", "测试24", "1", "15666666666", "ry@qq.com", 570.0, "1")); users.add(new UserTableModel(25, "1000025", "测试25", "1", "15666666666", "ry@qq.com", 250.0, "1")); users.add(new UserTableModel(26, "1000026", "测试26", "1", "15666666666", "ry@qq.com", 250.0, "1")); } private final static List areas = new ArrayList(); { areas.add(new AreaModel(1, 0, "广东省", "440000", "GDS", "GuangDongSheng", 1)); areas.add(new AreaModel(2, 0, "湖南省", "430000", "HNS", "HuNanSheng", 1)); areas.add(new AreaModel(3, 0, "河南省", "410000", "HNS", "HeNanSheng", 0)); areas.add(new AreaModel(4, 0, "湖北省", "420000", "HBS", "HuBeiSheng", 0)); areas.add(new AreaModel(5, 0, "辽宁省", "210000", "LNS", "LiaoNingSheng", 0)); areas.add(new AreaModel(6, 0, "山东省", "370000", "SDS", "ShanDongSheng", 0)); areas.add(new AreaModel(7, 0, "陕西省", "610000", "SXS", "ShanXiSheng", 0)); areas.add(new AreaModel(8, 0, "贵州省", "520000", "GZS", "GuiZhouSheng", 0)); areas.add(new AreaModel(9, 0, "上海市", "310000", "SHS", "ShangHaiShi", 0)); areas.add(new AreaModel(10, 0, "重庆市", "500000", "CQS", "ChongQingShi", 0)); areas.add(new AreaModel(11, 0, "若依省", "666666", "YYS", "RuoYiSheng", 0)); areas.add(new AreaModel(12, 0, "安徽省", "340000", "AHS", "AnHuiSheng", 0)); areas.add(new AreaModel(13, 0, "福建省", "350000", "FJS", "FuJianSheng", 0)); areas.add(new AreaModel(14, 0, "海南省", "460000", "HNS", "HaiNanSheng", 0)); areas.add(new AreaModel(15, 0, "江苏省", "320000", "JSS", "JiangSuSheng", 0)); areas.add(new AreaModel(16, 0, "青海省", "630000", "QHS", "QingHaiSheng", 0)); areas.add(new AreaModel(17, 0, "广西壮族自治区", "450000", "GXZZZZQ", "GuangXiZhuangZuZiZhiQu", 0)); areas.add(new AreaModel(18, 0, "宁夏回族自治区", "640000", "NXHZZZQ", "NingXiaHuiZuZiZhiQu", 0)); areas.add(new AreaModel(19, 0, "内蒙古自治区", "150000", "NMGZZQ", "NeiMengGuZiZhiQu", 0)); areas.add(new AreaModel(20, 0, "新疆维吾尔自治区", "650000", "XJWWEZZQ", "XinJiangWeiWuErZiZhiQu", 0)); areas.add(new AreaModel(21, 0, "江西省", "360000", "JXS", "JiangXiSheng", 0)); areas.add(new AreaModel(22, 0, "浙江省", "330000", "ZJS", "ZheJiangSheng", 0)); areas.add(new AreaModel(23, 0, "河北省", "130000", "HBS", "HeBeiSheng", 0)); areas.add(new AreaModel(24, 0, "天津市", "120000", "TJS", "TianJinShi", 0)); areas.add(new AreaModel(25, 0, "山西省", "140000", "SXS", "ShanXiSheng", 0)); areas.add(new AreaModel(26, 0, "台湾省", "710000", "TWS", "TaiWanSheng", 0)); areas.add(new AreaModel(27, 0, "甘肃省", "620000", "GSS", "GanSuSheng", 0)); areas.add(new AreaModel(28, 0, "四川省", "510000", "SCS", "SiChuanSheng", 0)); areas.add(new AreaModel(29, 0, "云南省", "530000", "YNS", "YunNanSheng", 0)); areas.add(new AreaModel(30, 0, "北京市", "110000", "BJS", "BeiJingShi", 0)); areas.add(new AreaModel(31, 0, "香港特别行政区", "810000", "XGTBXZQ", "XiangGangTeBieXingZhengQu", 0)); areas.add(new AreaModel(32, 0, "澳门特别行政区", "820000", "AMTBXZQ", "AoMenTeBieXingZhengQu", 0)); areas.add(new AreaModel(100, 1, "深圳市", "440300", "SZS", "ShenZhenShi", 1)); areas.add(new AreaModel(101, 1, "广州市", "440100", "GZS", "GuangZhouShi", 0)); areas.add(new AreaModel(102, 1, "东莞市", "441900", "DGS", "DongGuanShi", 0)); areas.add(new AreaModel(103, 2, "长沙市", "410005", "CSS", "ChangShaShi", 1)); areas.add(new AreaModel(104, 2, "岳阳市", "414000", "YYS", "YueYangShi", 0)); areas.add(new AreaModel(1000, 100, "龙岗区", "518172", "LGQ", "LongGangQu", 0)); areas.add(new AreaModel(1001, 100, "南山区", "518051", "NSQ", "NanShanQu", 0)); areas.add(new AreaModel(1002, 100, "宝安区", "518101", "BAQ", "BaoAnQu", 0)); areas.add(new AreaModel(1003, 100, "福田区", "518081", "FTQ", "FuTianQu", 0)); areas.add(new AreaModel(1004, 103, "天心区", "410004", "TXQ", "TianXinQu", 0)); areas.add(new AreaModel(1005, 103, "开福区", "410008", "KFQ", "KaiFuQu", 0)); areas.add(new AreaModel(1006, 103, "芙蓉区", "410011", "FRQ", "FuRongQu", 0)); areas.add(new AreaModel(1007, 103, "雨花区", "410011", "YHQ", "YuHuaQu", 0)); } private final static List columns = new ArrayList(); { columns.add(new UserTableColumn("用户ID", "userId")); columns.add(new UserTableColumn("用户编号", "userCode")); columns.add(new UserTableColumn("用户姓名", "userName")); columns.add(new UserTableColumn("用户手机", "userPhone")); columns.add(new UserTableColumn("用户邮箱", "userEmail")); columns.add(new UserTableColumn("用户状态", "status")); } private final static List documents = new ArrayList(); { documents.add(new DocumentModel(1, "247-XW·2024-D10-0001", "新闻热线[2024]000001", "索尼射击游戏《Concord》停止运营,玩家将获全额退款", "索尼宣布多人射击游戏《Concord》将于9月6日停止运营,玩家将获得全额退款。游戏总监Ryan Ellis在给玩家的信中表示,这款游戏首次发布“并没有像我们预期的那样顺利”。《Concord》的开发历时8年,投资超过1.5亿美元。游戏在Steam平台的售价为40美元,采用买断制销售模式。据SteamDB统计,游戏上市后的最高同时在线人数为697人。")); documents.add(new DocumentModel(2, "247-XW·2024-D30-0002", "新闻热线[2024]000002", "网红账号被封,央媒:如此炫富毒瘤早就该拔了", "在社交平台上分享自己的生活日常,本来无可厚非。但无底线地展示物欲、宣扬拜金,取笑甚至嘲讽工薪者的烟火生活,就会遮蔽普通人的平凡质朴和坚韧奋斗,在无形中消解芸芸众生脚踏实地、自立自强的社会正气。对这种助长金钱至上、刺激公众焦虑,既污染网络生态,又撕裂社会和谐的炫富“毒瘤”,必须坚决拔除之。在国家有关部门的部署下,近日,多个网络平台开展“不良价值导向内容专项治理”行动,对“奢靡浪费”“炫富拜金”等问题从严打击,倡导理性、文明的消费观和价值观。")); documents.add(new DocumentModel(3, "CT01-XW·2024-Y-0003", "新闻热线[2024]000003", "重庆一夫妻被骗至缅甸,家属:两人已被解救,预计很快能回国", "5月25日,重庆一对夫妻在前往泰国后失联,疑被诈骗集团骗至缅甸的消息引发广泛关注。警方已对此事立案调查,而这对夫妻的亲属则每天生活在焦急和不安之中。亲属:家都瘫痪了,事情一经曝光,迅速登上了热搜,成为公众热议的话题。据了解,这对夫妻原计划是去泰国谈生意,但不幸的是,他们的泰国之行变成了一场噩梦。亲属李先生透露,4月14日,他们夫妻二人抵达泰国,不久后便疑似被人以10万元的价格卖到缅甸,目前被困在缅甸妙瓦底的一个电信诈骗园区。")); documents.add(new DocumentModel(4, "CT01-XW·2024-Y-0004", "新闻热线[2024]000004", "江滨社区联合派出所、金霞消防站开展电动自行车安全隐患夜查活动", "近日,长沙市开福区江滨社区联合派出所、金霞消防站深入居民小区、单位场所,以电动车自行车火灾防范为重点,开展消防安全夜查行动。此次夜查紧紧围绕老旧居民区、“三合一”场所、沿街门店、夜间经营使用场所等场所开展监督检查,重点检查电动自行车违规停放充电、堵塞疏散通道和安全出口,架空层违规作为电动自行车停放充电场所,电动自行车违规“进楼入户”“飞线充电”,电动自行车擅自改装等五大类问题。")); documents.add(new DocumentModel(5, "CT01-XW·2024-Y-0005", "新闻热线[2024]000005", "《黑神话》让海外玩家迷上“悟空”", "备受全球玩家瞩目的首款国产3A游戏《黑神话:悟空》日前正式发布。精美绝伦的东方美学世界、精彩纷呈的中国神话故事、酣畅淋漓的游戏体验,这款游戏为全球玩家带来一场视觉与文化的双重盛宴。从“悟空”成功出海的背后,海外人士看到了中国游戏产业的巨大进步,感受到了中国文化的多元精彩,并对下一个“悟空”的诞生及更多中国文化产品走向世界充满期待。")); documents.add(new DocumentModel(6, "CT01-XW·2024-Y-0006", "新闻热线[2024]000006", "市场状况充满挑战!极星宣布裁员全球约15%的员工", "据路透社报道,极星周五以“充满挑战的市场状况”为由,宣布计划在全球范围内裁减约450个职位。此次裁员之际,许多人都对电动汽车需求降温表示担忧,而且极星预计汽车业务最早将在2025年开始实现收支平衡。极星发言人表示:“作为该商业计划的一部分,我们需要调整我们的业务和运营规模。” “这涉及减少外部支出,遗憾的是,还包括我们的员工数量。”该公司还表示,希望减少对沃尔沃及其母公司吉利外部融资的依赖,最近还表示希望削减成本并提高电动汽车的利润率。")); documents.add(new DocumentModel(7, "CT01-XW·2024-Y-0007", "新闻热线[2024]000007", "浙江隐秘富豪涉百亿非法集资案,部分资金流入新造车公司", "5月10日、11日,上海北广投资管理有限公司(下称“北广投”)非法集资案在上海黄浦区人民法院一审公开开庭审理,北广投实控人周敏、法定代表人朱江等30余名中高管被控非法吸收公众存款罪。根据财新报道,这一案件中,非法集资的资金有部分流入了两家新能源车企——爱驰汽车、万象汽车。同时,有多位投资人引述与经侦部门沟通时的说法称,该案事发时未兑付金额有130余亿元,其中去往广微控股45亿元、万象汽车63亿元、爱驰汽车15亿元。")); documents.add(new DocumentModel(8, "CT01-XW·2024-Y-0008", "新闻热线[2024]000008", "特斯拉宣布Model Y升级:搭载HW4.0硬件,售价仍25.89万元起", "2月1日,特斯拉官方宣布ModelY升级。外观上,新增ModelY专属色“快银车漆”,并采用烈焰红代替中国红、星空灰代替冷光银;性能上,ModelY全系配备全新一代自动辅助驾驶硬件(HW4.0),通过搭载超远距离双目摄像头,ModelY的最远探测距离达424米。由此,特斯拉全系车型均配备了自动辅助驾驶硬件HW4.0。在售价方面,特斯拉中国官网显示,ModelY车型依然保持原价。ModelY后轮驱动版25.89万元起、ModelY长续航版29.99万元起、ModelY高性能版售价36.39万元起。")); documents.add(new DocumentModel(9, "CT01-XW·2024-D10-0009", "新闻热线[2024]000009", "华为手机归来,谁最受伤?", "低迷周期下的智能手机市场在2023年下半年迎来了华为的回归,这也给本就竞争激烈的市场环境带来了更大变数。1月29日,有消息称,华为已注册“星耀手机”品牌商标,定位中端手机市场,但上述消息并未获得华为方面确认。“目前星耀的相关信息我们看到了,但是没有获得产品信息以及启动线下铺货的通知。对于和其他品牌的二选一问题,听其他省份的经销商说过,但目前(华为渠道)这边也没有更多动作。”一位广东区域的华为核心经销商对记者说。但华为手机的反扑已经开始。在多家调研机构公布的2023年四季度智能手机出货数据中,华为手机的量正在明显上升,当季增幅在35%到47%之间。不过,从全年数据来看,并未登上前五榜单。")); documents.add(new DocumentModel(10, "CT01-XW·2024-D10-0010", "新闻热线[2024]000010", "疯狂裁员的硅谷大厂:除了AI,其它都是将就", "放眼望去,近期科技企业财报形势一片大好,裁员浪潮却仍在不断蔓延。国内职场动态看脉脉,那硅谷裁员情况就得看layoff.fyi了。数据显示,2024年,103家科技企业进行了裁员,28963位员工失去了饭碗。其中,电子支付公司PayPal大笔一挥,裁掉2500人,微软则在开年就裁掉1900人。回望2023年,谷歌、Meta、亚马逊、微软均为裁员重灾区,裁员人数在一万左右。具体而言,谷歌近日披露的财报指出,2023年谷歌解雇了12000多名员工,光是在遣散费和其他费用上就花费了21亿美元。而且裁员费用还在不断增加,2024年刚过去一个月,谷歌就已经花费了7亿美元用来裁员。")); documents.add(new DocumentModel(11, "CT01-XW·2024-D30-0011", "新闻热线[2024]000011", "国产手机品牌重新崛起背后:市场正在逐步恢复活力,竞争也愈发激烈", "2024年伊始,随着全球经济的逐渐复苏,手机消费市场也展现出勃勃生机。中国信通院最新数据显示,2023年中国市场手机出货量实现了6.5%的同比增长,其中5G手机增长势头更为强劲,占比高达82.8%。1月25日,国际数据公司(IDC)发布了最新手机季度跟踪报告,揭示了中国智能手机市场在2023年第四季度的出货量情况。报告显示,该季度中国智能手机市场出货量达到了约7363万台,同比增长1.2%。这是在连续十个季度同比下降后,中国智能手机市场首次实现反弹。这一积极信号表明,市场正在逐步恢复活力,各大品牌之间的竞争也愈发激烈。")); documents.add(new DocumentModel(12, "CT01-XW·2024-D30-0012", "新闻热线[2024]000012", "SpaceX将于1月31日向国际空间站发射天鹅号货运飞船", "1月29日消息,美国太空探索技术公司SpaceX计划于当地时间1月30日,利用“猎鹰9号”火箭从佛罗里达州肯尼迪航天中心发射诺斯罗普·格鲁曼公司的“天鹅号”货运飞船至国际空间站。此次任务是执行NG-20商业补给,将运送约8200多磅的物资、设备及科学实验器材。")); } /** * 搜索相关 */ @GetMapping("/search") public String search() { return prefix + "/search"; } /** * 数据汇总 */ @GetMapping("/footer") public String footer() { return prefix + "/footer"; } /** * 组合表头 */ @GetMapping("/groupHeader") public String groupHeader() { return prefix + "/groupHeader"; } /** * 表格导出 */ @GetMapping("/export") public String export() { return prefix + "/export"; } /** * 表格导出选择列 */ @GetMapping("/exportSelected") public String exportSelected() { return prefix + "/exportSelected"; } /** * 导出数据 */ @PostMapping("/exportData") @ResponseBody public AjaxResult exportSelected(UserTableModel userModel, String userIds) { List userList = new ArrayList(Arrays.asList(new UserTableModel[users.size()])); Collections.copy(userList, users); // 条件过滤 if (StringUtils.isNotEmpty(userIds)) { userList.clear(); for (Long userId : Convert.toLongArray(userIds)) { for (UserTableModel user : users) { if (user.getUserId() == userId) { userList.add(user); } } } } ExcelUtil util = new ExcelUtil(UserTableModel.class); return util.exportExcel(userList, "用户数据"); } /** * 翻页记住选择 */ @GetMapping("/remember") public String remember() { return prefix + "/remember"; } /** * 表格保存状态 */ @GetMapping("/cookie") public String cookie() { return prefix + "/cookie"; } /** * 跳转至指定页 */ @GetMapping("/pageGo") public String pageGo() { return prefix + "/pageGo"; } /** * 自定义查询参数 */ @GetMapping("/params") public String params() { return prefix + "/params"; } /** * 多表格 */ @GetMapping("/multi") public String multi() { return prefix + "/multi"; } /** * 点击按钮加载表格 */ @GetMapping("/button") public String button() { return prefix + "/button"; } /** * 直接加载表格数据 */ @GetMapping("/data") public String data(ModelMap mmap) { mmap.put("users", users); return prefix + "/data"; } /** * 表格冻结列 */ @GetMapping("/fixedColumns") public String fixedColumns() { return prefix + "/fixedColumns"; } /** * 自定义触发事件 */ @GetMapping("/event") public String event() { return prefix + "/event"; } /** * 表格细节视图 */ @GetMapping("/detail") public String detail() { return prefix + "/detail"; } /** * 表格父子视图 */ @GetMapping("/child") public String child() { return prefix + "/child"; } /** * 表格图片预览 */ @GetMapping("/image") public String image() { return prefix + "/image"; } /** * 动态增删改查 */ @GetMapping("/curd") public String curd() { return prefix + "/curd"; } /** * 表格行拖拽操作 */ @GetMapping("/reorderRows") public String reorderRows() { return prefix + "/reorderRows"; } /** * 表格列拖拽操作 */ @GetMapping("/reorderColumns") public String reorderColumns() { return prefix + "/reorderColumns"; } /** * 表格列宽拖动 */ @GetMapping("/resizable") public String resizable() { return prefix + "/resizable"; } /** * 表格行内编辑操作 */ @GetMapping("/editable") public String editable() { return prefix + "/editable"; } /** * 主子表提交 */ @GetMapping("/subdata") public String subdata() { return prefix + "/subdata"; } /** * 表格自动刷新 */ @GetMapping("/refresh") public String refresh() { return prefix + "/refresh"; } /** * 表格打印配置 */ @GetMapping("/print") public String print() { return prefix + "/print"; } /** * 表格标题格式化 */ @GetMapping("/headerStyle") public String headerStyle() { return prefix + "/headerStyle"; } /** * 表格动态列 */ @GetMapping("/dynamicColumns") public String dynamicColumns() { return prefix + "/dynamicColumns"; } /** * 表格虚拟滚动 */ @GetMapping("/virtualScroll") public String virtualScroll() { return prefix + "/virtualScroll"; } /** * 自定义视图分页 */ @GetMapping("/customView") public String customView() { return prefix + "/customView"; } /** * 全文索引 */ @GetMapping("/textSearch") public String textSearch() { return prefix + "/textSearch"; } /** * 异步加载表格树 */ @GetMapping("/asynTree") public String asynTree() { return prefix + "/asynTree"; } /** * 表格其他操作 */ @GetMapping("/other") public String other() { return prefix + "/other"; } /** * 动态获取列 */ @PostMapping("/ajaxColumns") @ResponseBody public AjaxResult ajaxColumns(UserTableColumn userColumn) { List columnList = new ArrayList(Arrays.asList(new UserTableColumn[columns.size()])); Collections.copy(columnList, columns); if (userColumn != null && "userBalance".equals(userColumn.getField())) { columnList.add(new UserTableColumn("用户余额", "userBalance")); } return AjaxResult.success(columnList); } /** * 查询数据 */ @PostMapping("/list") @ResponseBody public TableDataInfo list(UserTableModel userModel) { TableDataInfo rspData = new TableDataInfo(); List userList = new ArrayList(Arrays.asList(new UserTableModel[users.size()])); Collections.copy(userList, users); // 查询条件过滤 if (StringUtils.isNotEmpty(userModel.getUserName())) { userList.clear(); for (UserTableModel user : users) { if (user.getUserName().equals(userModel.getUserName())) { userList.add(user); } } } PageDomain pageDomain = TableSupport.buildPageRequest(); if (null == pageDomain.getPageNum() || null == pageDomain.getPageSize()) { rspData.setRows(userList); rspData.setTotal(userList.size()); return rspData; } Integer pageNum = (pageDomain.getPageNum() - 1) * 10; Integer pageSize = pageDomain.getPageNum() * 10; if (pageSize > userList.size()) { pageSize = userList.size(); } rspData.setRows(userList.subList(pageNum, pageSize)); rspData.setTotal(userList.size()); return rspData; } /** * 查询全文索引数据 */ @PostMapping("/text/list") @ResponseBody public TableDataInfo textList(BaseEntity baseEntity) { TableDataInfo rspData = new TableDataInfo(); List documentList = new ArrayList(Arrays.asList(new DocumentModel[documents.size()])); Collections.copy(documentList, documents); // 查询条件过滤 if (StringUtils.isNotEmpty(baseEntity.getSearchValue())) { documentList.clear(); for (DocumentModel document : documents) { boolean indexFlag = false; if (document.getTitle().contains(baseEntity.getSearchValue())) { indexFlag = true; document.setTitle(document.getTitle().replace(baseEntity.getSearchValue(), "" + baseEntity.getSearchValue() + "")); } if (document.getContent().contains(baseEntity.getSearchValue())) { indexFlag = true; document.setContent(document.getContent().replace(baseEntity.getSearchValue(), "" + baseEntity.getSearchValue() + "")); } if (indexFlag) { documentList.add(document); } } } PageDomain pageDomain = TableSupport.buildPageRequest(); if (null == pageDomain.getPageNum() || null == pageDomain.getPageSize()) { rspData.setRows(documentList); rspData.setTotal(documentList.size()); return rspData; } Integer pageNum = (pageDomain.getPageNum() - 1) * 10; Integer pageSize = pageDomain.getPageNum() * 10; if (pageSize > documentList.size()) { pageSize = documentList.size(); } rspData.setRows(documentList.subList(pageNum, pageSize)); rspData.setTotal(documentList.size()); return rspData; } /** * 查询树表数据 */ @PostMapping("/tree/list") @ResponseBody public TableDataInfo treeList(AreaModel areaModel) { TableDataInfo rspData = new TableDataInfo(); List areaList = new ArrayList(Arrays.asList(new AreaModel[areas.size()])); // 默认查询条件 parentId 0 Collections.copy(areaList, areas); areaList.clear(); if (StringUtils.isNotEmpty(areaModel.getAreaName())) { for (AreaModel area : areas) { if (area.getParentId() == 0 && area.getAreaName().equals(areaModel.getAreaName())) { areaList.add(area); } } } else { for (AreaModel area : areas) { if (area.getParentId() == 0) { areaList.add(area); } } } PageDomain pageDomain = TableSupport.buildPageRequest(); Integer pageNum = (pageDomain.getPageNum() - 1) * pageDomain.getPageSize(); Integer pageSize = pageDomain.getPageNum() * pageDomain.getPageSize(); if (pageSize > areaList.size()) { pageSize = areaList.size(); } rspData.setRows(areaList.subList(pageNum, pageSize)); rspData.setTotal(areaList.size()); return rspData; } /** * 查询树表子节点数据 */ @PostMapping("/tree/listChild") @ResponseBody public List listChild(AreaModel areaModel) { List areaList = new ArrayList(Arrays.asList(new AreaModel[areas.size()])); // 查询条件 parentId Collections.copy(areaList, areas); areaList.clear(); if (StringUtils.isNotEmpty(areaModel.getAreaName())) { for (AreaModel area : areas) { if (area.getParentId().intValue() == areaModel.getParentId().intValue() && area.getAreaName().equals(areaModel.getAreaName())) { areaList.add(area); } } } else { for (AreaModel area : areas) { if (area.getParentId().intValue() == areaModel.getParentId().intValue()) { areaList.add(area); } } } return areaList; } } class UserTableColumn { /** 表头 */ private String title; /** 字段 */ private String field; public UserTableColumn() { } public UserTableColumn(String title, String field) { this.title = title; this.field = field; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getField() { return field; } public void setField(String field) { this.field = field; } } class UserTableModel { /** 用户ID */ private int userId; /** 用户编号 */ @Excel(name = "用户编号", cellType = ColumnType.NUMERIC) private String userCode; /** 用户姓名 */ @Excel(name = "用户姓名") private String userName; /** 用户性别 */ private String userSex; /** 用户手机 */ @Excel(name = "用户手机") private String userPhone; /** 用户邮箱 */ @Excel(name = "用户邮箱") private String userEmail; /** 用户余额 */ @Excel(name = "用户余额", cellType = ColumnType.NUMERIC) private double userBalance; /** 用户状态(0正常 1停用) */ private String status; /** 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; public UserTableModel() { } public UserTableModel(int userId, String userCode, String userName, String userSex, String userPhone, String userEmail, double userBalance, String status) { this.userId = userId; this.userCode = userCode; this.userName = userName; this.userSex = userSex; this.userPhone = userPhone; this.userEmail = userEmail; this.userBalance = userBalance; this.status = status; this.createTime = DateUtils.getNowDate(); } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getUserCode() { return userCode; } public void setUserCode(String userCode) { this.userCode = userCode; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } public String getUserPhone() { return userPhone; } public void setUserPhone(String userPhone) { this.userPhone = userPhone; } public String getUserEmail() { return userEmail; } public void setUserEmail(String userEmail) { this.userEmail = userEmail; } public double getUserBalance() { return userBalance; } public void setUserBalance(double userBalance) { this.userBalance = userBalance; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } } class AreaModel { /** 编号 */ private Long id; /** 父编号 */ private Long parentId; /** 区域名称 */ private String areaName; /** 区域代码 */ private String areaCode; /** 名称首字母 */ private String simplePy; /** 名称全拼 */ private String pinYin; /** 是否有子节点(0无 1有) */ private Integer isTreeLeaf = 1; public AreaModel() { } public AreaModel(int id, int parentId, String areaName, String areaCode, String simplePy, String pinYin, Integer isTreeLeaf) { this.id = Long.valueOf(id); this.parentId = Long.valueOf(parentId); this.areaName = areaName; this.areaCode = areaCode; this.simplePy = simplePy; this.pinYin = pinYin; this.isTreeLeaf = isTreeLeaf; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public String getAreaName() { return areaName; } public void setAreaName(String areaName) { this.areaName = areaName; } public String getAreaCode() { return areaCode; } public void setAreaCode(String areaCode) { this.areaCode = areaCode; } public String getSimplePy() { return simplePy; } public void setSimplePy(String simplePy) { this.simplePy = simplePy; } public String getPinYin() { return pinYin; } public void setPinYin(String pinYin) { this.pinYin = pinYin; } public Integer getIsTreeLeaf() { return isTreeLeaf; } public void setIsTreeLeaf(Integer isTreeLeaf) { this.isTreeLeaf = isTreeLeaf; } } class DocumentModel { /** 编号 */ private int tableId; /** 档号 */ private String archiveNo; /** 文件编号 */ private String docNo; /** 标题 */ private String title; /** 内容 */ private String content; public DocumentModel() { } public DocumentModel(int tableId, String archiveNo, String docNo, String title, String content) { this.tableId = tableId; this.archiveNo = archiveNo; this.docNo = docNo; this.title = title; this.content = content; } public int getTableId() { return tableId; } public String getArchiveNo() { return archiveNo; } public String getDocNo() { return docNo; } public String getTitle() { return title; } public String getContent() { return content; } public void setTableId(int tableId) { this.tableId = tableId; } public void setArchiveNo(String archiveNo) { this.archiveNo = archiveNo; } public void setDocNo(String docNo) { this.docNo = docNo; } public void setTitle(String title) { this.title = title; } public void setContent(String content) { this.content = content; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/CustomerModel.java ================================================ package com.ruoyi.web.controller.demo.domain; import java.util.List; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * 客户测试信息 * * @author ruoyi */ public class CustomerModel { /** * 客户姓名 */ private String name; /** * 客户手机 */ private String phonenumber; /** * 客户性别 */ private String sex; /** * 客户生日 */ private String birthday; /** * 客户描述 */ private String remark; /** * 商品信息 */ private List goods; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhonenumber() { return phonenumber; } public void setPhonenumber(String phonenumber) { this.phonenumber = phonenumber; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getBirthday() { return birthday; } public void setBirthday(String birthday) { this.birthday = birthday; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public List getGoods() { return goods; } public void setGoods(List goods) { this.goods = goods; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("name", getName()) .append("phonenumber", getPhonenumber()) .append("sex", getSex()) .append("birthday", getBirthday()) .append("goods", getGoods()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/GoodsModel.java ================================================ package com.ruoyi.web.controller.demo.domain; import java.util.Date; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * 商品测试信息 * * @author ruoyi */ public class GoodsModel { /** * 商品名称 */ private String name; /** * 商品重量 */ private Integer weight; /** * 商品价格 */ private Double price; /** * 商品日期 */ private Date date; /** * 商品种类 */ private String type; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("name", getName()) .append("weight", getWeight()) .append("price", getPrice()) .append("date", getDate()) .append("type", getType()) .toString(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/demo/domain/UserOperateModel.java ================================================ package com.ruoyi.web.controller.demo.domain; import java.util.Date; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.Type; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.utils.DateUtils; public class UserOperateModel extends BaseEntity { private static final long serialVersionUID = 1L; private int userId; @Excel(name = "用户编号") private String userCode; @Excel(name = "用户姓名") private String userName; @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") private String userSex; @Excel(name = "用户手机") private String userPhone; @Excel(name = "用户邮箱") private String userEmail; @Excel(name = "用户余额") private double userBalance; @Excel(name = "用户状态", readConverterExp = "0=正常,1=停用") private String status; @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) private Date createTime; public UserOperateModel() { } public UserOperateModel(int userId, String userCode, String userName, String userSex, String userPhone, String userEmail, double userBalance, String status) { this.userId = userId; this.userCode = userCode; this.userName = userName; this.userSex = userSex; this.userPhone = userPhone; this.userEmail = userEmail; this.userBalance = userBalance; this.status = status; this.createTime = DateUtils.getNowDate(); } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public String getUserCode() { return userCode; } public void setUserCode(String userCode) { this.userCode = userCode; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } public String getUserPhone() { return userPhone; } public void setUserPhone(String userPhone) { this.userPhone = userPhone; } public String getUserEmail() { return userEmail; } public void setUserEmail(String userEmail) { this.userEmail = userEmail; } public double getUserBalance() { return userBalance; } public void setUserBalance(double userBalance) { this.userBalance = userBalance; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public Date getCreateTime() { return createTime; } @Override public void setCreateTime(Date createTime) { this.createTime = createTime; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java ================================================ package com.ruoyi.web.controller.monitor; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.framework.web.service.CacheService; /** * 缓存监控 * * @author ruoyi */ @Controller @RequestMapping("/monitor/cache") public class CacheController extends BaseController { private String prefix = "monitor/cache"; @Autowired private CacheService cacheService; @RequiresPermissions("monitor:cache:view") @GetMapping() public String cache(ModelMap mmap) { mmap.put("cacheNames", cacheService.getCacheNames()); return prefix + "/cache"; } @RequiresPermissions("monitor:cache:view") @PostMapping("/getNames") public String getCacheNames(ModelMap mmap) { mmap.put("cacheNames", cacheService.getCacheNames()); return prefix + "/cache::fragment-cache-names"; } @RequiresPermissions("monitor:cache:view") @PostMapping("/getKeys") public String getCacheKeys(String cacheName, ModelMap mmap) { mmap.put("cacheName", cacheName); mmap.put("cacheKeys", cacheService.getCacheKeys(cacheName)); return prefix + "/cache::fragment-cache-kyes"; } @RequiresPermissions("monitor:cache:view") @PostMapping("/getValue") public String getCacheValue(String cacheName, String cacheKey, ModelMap mmap) { mmap.put("cacheName", cacheName); mmap.put("cacheKey", cacheKey); mmap.put("cacheValue", cacheService.getCacheValue(cacheName, cacheKey)); return prefix + "/cache::fragment-cache-value"; } @RequiresPermissions("monitor:cache:view") @PostMapping("/clearCacheName") @ResponseBody public AjaxResult clearCacheName(String cacheName, ModelMap mmap) { cacheService.clearCacheName(cacheName); return AjaxResult.success(); } @RequiresPermissions("monitor:cache:view") @PostMapping("/clearCacheKey") @ResponseBody public AjaxResult clearCacheKey(String cacheName, String cacheKey, ModelMap mmap) { cacheService.clearCacheKey(cacheName, cacheKey); return AjaxResult.success(); } @RequiresPermissions("monitor:cache:view") @GetMapping("/clearAll") @ResponseBody public AjaxResult clearAll(ModelMap mmap) { cacheService.clearAll(); return AjaxResult.success(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/DruidController.java ================================================ package com.ruoyi.web.controller.monitor; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.ruoyi.common.core.controller.BaseController; /** * druid 监控 * * @author ruoyi */ @Controller @RequestMapping("/monitor/data") public class DruidController extends BaseController { private String prefix = "/druid"; @RequiresPermissions("monitor:data:view") @GetMapping() public String index() { return redirect(prefix + "/index.html"); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java ================================================ package com.ruoyi.web.controller.monitor; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.framework.web.domain.Server; /** * 服务器监控 * * @author ruoyi */ @Controller @RequestMapping("/monitor/server") public class ServerController extends BaseController { private String prefix = "monitor/server"; @RequiresPermissions("monitor:server:view") @GetMapping() public String server(ModelMap mmap) throws Exception { Server server = new Server(); server.copyTo(); mmap.put("server", server); return prefix + "/server"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java ================================================ package com.ruoyi.web.controller.monitor; import java.util.List; import com.ruoyi.framework.shiro.service.SysPasswordService; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.domain.SysLogininfor; import com.ruoyi.system.service.ISysLogininforService; /** * 系统访问记录 * * @author ruoyi */ @Controller @RequestMapping("/monitor/logininfor") public class SysLogininforController extends BaseController { private String prefix = "monitor/logininfor"; @Autowired private ISysLogininforService logininforService; @Autowired private SysPasswordService passwordService; @RequiresPermissions("monitor:logininfor:view") @GetMapping() public String logininfor() { return prefix + "/logininfor"; } @RequiresPermissions("monitor:logininfor:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysLogininfor logininfor) { startPage(); List list = logininforService.selectLogininforList(logininfor); return getDataTable(list); } @Log(title = "登录日志", businessType = BusinessType.EXPORT) @RequiresPermissions("monitor:logininfor:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysLogininfor logininfor) { List list = logininforService.selectLogininforList(logininfor); ExcelUtil util = new ExcelUtil(SysLogininfor.class); return util.exportExcel(list, "登录日志"); } @RequiresPermissions("monitor:logininfor:remove") @Log(title = "登录日志", businessType = BusinessType.DELETE) @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { return toAjax(logininforService.deleteLogininforByIds(ids)); } @RequiresPermissions("monitor:logininfor:remove") @Log(title = "登录日志", businessType = BusinessType.CLEAN) @PostMapping("/clean") @ResponseBody public AjaxResult clean() { logininforService.cleanLogininfor(); return success(); } @RequiresPermissions("monitor:logininfor:unlock") @Log(title = "账户解锁", businessType = BusinessType.OTHER) @PostMapping("/unlock") @ResponseBody public AjaxResult unlock(String loginName) { passwordService.clearLoginRecordCache(loginName); return success(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java ================================================ package com.ruoyi.web.controller.monitor; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.service.ISysOperLogService; /** * 操作日志记录 * * @author ruoyi */ @Controller @RequestMapping("/monitor/operlog") public class SysOperlogController extends BaseController { private String prefix = "monitor/operlog"; @Autowired private ISysOperLogService operLogService; @RequiresPermissions("monitor:operlog:view") @GetMapping() public String operlog() { return prefix + "/operlog"; } @RequiresPermissions("monitor:operlog:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysOperLog operLog) { startPage(); List list = operLogService.selectOperLogList(operLog); return getDataTable(list); } @Log(title = "操作日志", businessType = BusinessType.EXPORT) @RequiresPermissions("monitor:operlog:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysOperLog operLog) { List list = operLogService.selectOperLogList(operLog); ExcelUtil util = new ExcelUtil(SysOperLog.class); return util.exportExcel(list, "操作日志"); } @Log(title = "操作日志", businessType = BusinessType.DELETE) @RequiresPermissions("monitor:operlog:remove") @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { return toAjax(operLogService.deleteOperLogByIds(ids)); } @RequiresPermissions("monitor:operlog:detail") @GetMapping("/detail/{operId}") public String detail(@PathVariable("operId") Long operId, ModelMap mmap) { mmap.put("operLog", operLogService.selectOperLogById(operId)); return prefix + "/detail"; } @Log(title = "操作日志", businessType = BusinessType.CLEAN) @RequiresPermissions("monitor:operlog:remove") @PostMapping("/clean") @ResponseBody public AjaxResult clean() { operLogService.cleanOperLog(); return success(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java ================================================ package com.ruoyi.web.controller.monitor; import java.util.List; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.OnlineStatus; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.system.service.ISysUserOnlineService; /** * 在线用户监控 * * @author ruoyi */ @Controller @RequestMapping("/monitor/online") public class SysUserOnlineController extends BaseController { private String prefix = "monitor/online"; @Autowired private ISysUserOnlineService userOnlineService; @Autowired private OnlineSessionDAO onlineSessionDAO; @RequiresPermissions("monitor:online:view") @GetMapping() public String online() { return prefix + "/online"; } @RequiresPermissions("monitor:online:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysUserOnline userOnline) { startPage(); List list = userOnlineService.selectUserOnlineList(userOnline); return getDataTable(list); } @RequiresPermissions(value = { "monitor:online:batchForceLogout", "monitor:online:forceLogout" }, logical = Logical.OR) @Log(title = "在线用户", businessType = BusinessType.FORCE) @PostMapping("/batchForceLogout") @ResponseBody public AjaxResult batchForceLogout(String ids) { for (String sessionId : Convert.toStrArray(ids)) { SysUserOnline online = userOnlineService.selectOnlineById(sessionId); if (online == null) { return error("用户已下线"); } OnlineSession onlineSession = (OnlineSession) onlineSessionDAO.readSession(online.getSessionId()); if (onlineSession == null) { return error("用户已下线"); } if (sessionId.equals(ShiroUtils.getSessionId())) { return error("当前登录用户无法强退"); } onlineSessionDAO.delete(onlineSession); online.setStatus(OnlineStatus.off_line); userOnlineService.saveOnline(online); userOnlineService.removeUserCache(online.getLoginName(), sessionId); } return success(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysCaptchaController.java ================================================ package com.ruoyi.web.controller.system; import java.awt.image.BufferedImage; import java.io.IOException; import jakarta.annotation.Resource; import javax.imageio.ImageIO; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import com.google.code.kaptcha.Constants; import com.google.code.kaptcha.Producer; import com.ruoyi.common.core.controller.BaseController; /** * 图片验证码(支持算术形式) * * @author ruoyi */ @Controller @RequestMapping("/captcha") public class SysCaptchaController extends BaseController { @Resource(name = "captchaProducer") private Producer captchaProducer; @Resource(name = "captchaProducerMath") private Producer captchaProducerMath; /** * 验证码生成 */ @GetMapping(value = "/captchaImage") public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response) { ServletOutputStream out = null; try { HttpSession session = request.getSession(); response.setDateHeader("Expires", 0); response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); response.addHeader("Cache-Control", "post-check=0, pre-check=0"); response.setHeader("Pragma", "no-cache"); response.setContentType("image/jpeg"); String type = request.getParameter("type"); String capStr = null; String code = null; BufferedImage bi = null; if ("math".equals(type)) { String capText = captchaProducerMath.createText(); capStr = capText.substring(0, capText.lastIndexOf("@")); code = capText.substring(capText.lastIndexOf("@") + 1); bi = captchaProducerMath.createImage(capStr); } else if ("char".equals(type)) { capStr = code = captchaProducer.createText(); bi = captchaProducer.createImage(capStr); } session.setAttribute(Constants.KAPTCHA_SESSION_KEY, code); out = response.getOutputStream(); ImageIO.write(bi, "jpg", out); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } return null; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.service.ISysConfigService; /** * 参数配置 信息操作处理 * * @author ruoyi */ @Controller @RequestMapping("/system/config") public class SysConfigController extends BaseController { private String prefix = "system/config"; @Autowired private ISysConfigService configService; @RequiresPermissions("system:config:view") @GetMapping() public String config() { return prefix + "/config"; } /** * 查询参数配置列表 */ @RequiresPermissions("system:config:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysConfig config) { startPage(); List list = configService.selectConfigList(config); return getDataTable(list); } @Log(title = "参数管理", businessType = BusinessType.EXPORT) @RequiresPermissions("system:config:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysConfig config) { List list = configService.selectConfigList(config); ExcelUtil util = new ExcelUtil(SysConfig.class); return util.exportExcel(list, "参数数据"); } /** * 新增参数配置 */ @RequiresPermissions("system:config:add") @GetMapping("/add") public String add() { return prefix + "/add"; } /** * 新增保存参数配置 */ @RequiresPermissions("system:config:add") @Log(title = "参数管理", businessType = BusinessType.INSERT) @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysConfig config) { if (!configService.checkConfigKeyUnique(config)) { return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); } config.setCreateBy(getLoginName()); return toAjax(configService.insertConfig(config)); } /** * 修改参数配置 */ @RequiresPermissions("system:config:edit") @GetMapping("/edit/{configId}") public String edit(@PathVariable("configId") Long configId, ModelMap mmap) { mmap.put("config", configService.selectConfigById(configId)); return prefix + "/edit"; } /** * 修改保存参数配置 */ @RequiresPermissions("system:config:edit") @Log(title = "参数管理", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysConfig config) { if (!configService.checkConfigKeyUnique(config)) { return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); } config.setUpdateBy(getLoginName()); return toAjax(configService.updateConfig(config)); } /** * 删除参数配置 */ @RequiresPermissions("system:config:remove") @Log(title = "参数管理", businessType = BusinessType.DELETE) @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { configService.deleteConfigByIds(ids); return success(); } /** * 刷新参数缓存 */ @RequiresPermissions("system:config:remove") @Log(title = "参数管理", businessType = BusinessType.CLEAN) @GetMapping("/refreshCache") @ResponseBody public AjaxResult refreshCache() { configService.resetConfigCache(); return success(); } /** * 校验参数键名 */ @PostMapping("/checkConfigKeyUnique") @ResponseBody public boolean checkConfigKeyUnique(SysConfig config) { return configService.checkConfigKeyUnique(config); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.service.ISysDeptService; /** * 部门信息 * * @author ruoyi */ @Controller @RequestMapping("/system/dept") public class SysDeptController extends BaseController { private String prefix = "system/dept"; @Autowired private ISysDeptService deptService; @RequiresPermissions("system:dept:view") @GetMapping() public String dept() { return prefix + "/dept"; } @RequiresPermissions("system:dept:list") @PostMapping("/list") @ResponseBody public List list(SysDept dept) { List deptList = deptService.selectDeptList(dept); return deptList; } /** * 新增部门 */ @RequiresPermissions("system:dept:add") @GetMapping("/add/{parentId}") public String add(@PathVariable("parentId") Long parentId, ModelMap mmap) { if (!getSysUser().isAdmin()) { parentId = getSysUser().getDeptId(); } mmap.put("dept", deptService.selectDeptById(parentId)); return prefix + "/add"; } /** * 新增保存部门 */ @Log(title = "部门管理", businessType = BusinessType.INSERT) @RequiresPermissions("system:dept:add") @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysDept dept) { if (!deptService.checkDeptNameUnique(dept)) { return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); } dept.setCreateBy(getLoginName()); return toAjax(deptService.insertDept(dept)); } /** * 修改部门 */ @RequiresPermissions("system:dept:edit") @GetMapping("/edit/{deptId}") public String edit(@PathVariable("deptId") Long deptId, ModelMap mmap) { deptService.checkDeptDataScope(deptId); SysDept dept = deptService.selectDeptById(deptId); if (StringUtils.isNotNull(dept) && 100L == deptId) { dept.setParentName("无"); } mmap.put("dept", dept); return prefix + "/edit"; } /** * 修改保存部门 */ @Log(title = "部门管理", businessType = BusinessType.UPDATE) @RequiresPermissions("system:dept:edit") @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysDept dept) { Long deptId = dept.getDeptId(); deptService.checkDeptDataScope(deptId); if (!deptService.checkDeptNameUnique(dept)) { return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); } else if (dept.getParentId().equals(deptId)) { return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0) { return AjaxResult.error("该部门包含未停用的子部门!"); } dept.setUpdateBy(getLoginName()); return toAjax(deptService.updateDept(dept)); } /** * 删除 */ @Log(title = "部门管理", businessType = BusinessType.DELETE) @RequiresPermissions("system:dept:remove") @GetMapping("/remove/{deptId}") @ResponseBody public AjaxResult remove(@PathVariable("deptId") Long deptId) { if (deptService.selectDeptCount(deptId) > 0) { return AjaxResult.warn("存在下级部门,不允许删除"); } if (deptService.checkDeptExistUser(deptId)) { return AjaxResult.warn("部门存在用户,不允许删除"); } deptService.checkDeptDataScope(deptId); return toAjax(deptService.deleteDeptById(deptId)); } /** * 校验部门名称 */ @PostMapping("/checkDeptNameUnique") @ResponseBody public boolean checkDeptNameUnique(SysDept dept) { return deptService.checkDeptNameUnique(dept); } /** * 选择部门树 * * @param deptId 部门ID * @param excludeId 排除ID */ @RequiresPermissions("system:dept:list") @GetMapping(value = { "/selectDeptTree/{deptId}", "/selectDeptTree/{deptId}/{excludeId}" }) public String selectDeptTree(@PathVariable("deptId") Long deptId, @PathVariable(value = "excludeId", required = false) Long excludeId, ModelMap mmap) { mmap.put("dept", deptService.selectDeptById(deptId)); mmap.put("excludeId", excludeId); return prefix + "/tree"; } /** * 加载部门列表树(排除下级) */ @RequiresPermissions("system:dept:list") @GetMapping("/treeData/{excludeId}") @ResponseBody public List treeDataExcludeChild(@PathVariable(value = "excludeId", required = false) Long excludeId) { SysDept dept = new SysDept(); dept.setExcludeId(excludeId); List ztrees = deptService.selectDeptTreeExcludeChild(dept); return ztrees; } /** * 保存部门排序 */ @PostMapping("/updateSort") @ResponseBody public AjaxResult updateSort(@RequestParam String[] deptIds, @RequestParam String[] orderNums) { deptService.updateDeptSort(deptIds, orderNums); return success(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.service.ISysDictDataService; /** * 数据字典信息 * * @author ruoyi */ @Controller @RequestMapping("/system/dict/data") public class SysDictDataController extends BaseController { private String prefix = "system/dict/data"; @Autowired private ISysDictDataService dictDataService; @RequiresPermissions("system:dict:view") @GetMapping() public String dictData() { return prefix + "/data"; } @PostMapping("/list") @RequiresPermissions("system:dict:list") @ResponseBody public TableDataInfo list(SysDictData dictData) { startPage(); List list = dictDataService.selectDictDataList(dictData); return getDataTable(list); } @Log(title = "字典数据", businessType = BusinessType.EXPORT) @RequiresPermissions("system:dict:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysDictData dictData) { List list = dictDataService.selectDictDataList(dictData); ExcelUtil util = new ExcelUtil(SysDictData.class); return util.exportExcel(list, "字典数据"); } /** * 新增字典类型 */ @RequiresPermissions("system:dict:add") @GetMapping("/add/{dictType}") public String add(@PathVariable("dictType") String dictType, ModelMap mmap) { mmap.put("dictType", dictType); return prefix + "/add"; } /** * 新增保存字典类型 */ @Log(title = "字典数据", businessType = BusinessType.INSERT) @RequiresPermissions("system:dict:add") @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysDictData dict) { dict.setCreateBy(getLoginName()); return toAjax(dictDataService.insertDictData(dict)); } /** * 修改字典类型 */ @RequiresPermissions("system:dict:edit") @GetMapping("/edit/{dictCode}") public String edit(@PathVariable("dictCode") Long dictCode, ModelMap mmap) { mmap.put("dict", dictDataService.selectDictDataById(dictCode)); return prefix + "/edit"; } /** * 修改保存字典类型 */ @Log(title = "字典数据", businessType = BusinessType.UPDATE) @RequiresPermissions("system:dict:edit") @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysDictData dict) { dict.setUpdateBy(getLoginName()); return toAjax(dictDataService.updateDictData(dict)); } @Log(title = "字典数据", businessType = BusinessType.DELETE) @RequiresPermissions("system:dict:remove") @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { dictDataService.deleteDictDataByIds(ids); return success(); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDictType; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.service.ISysDictTypeService; /** * 数据字典信息 * * @author ruoyi */ @Controller @RequestMapping("/system/dict") public class SysDictTypeController extends BaseController { private String prefix = "system/dict/type"; @Autowired private ISysDictTypeService dictTypeService; @RequiresPermissions("system:dict:view") @GetMapping() public String dictType() { return prefix + "/type"; } @PostMapping("/list") @RequiresPermissions("system:dict:list") @ResponseBody public TableDataInfo list(SysDictType dictType) { startPage(); List list = dictTypeService.selectDictTypeList(dictType); return getDataTable(list); } @Log(title = "字典类型", businessType = BusinessType.EXPORT) @RequiresPermissions("system:dict:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysDictType dictType) { List list = dictTypeService.selectDictTypeList(dictType); ExcelUtil util = new ExcelUtil(SysDictType.class); return util.exportExcel(list, "字典类型"); } /** * 新增字典类型 */ @RequiresPermissions("system:dict:add") @GetMapping("/add") public String add() { return prefix + "/add"; } /** * 新增保存字典类型 */ @Log(title = "字典类型", businessType = BusinessType.INSERT) @RequiresPermissions("system:dict:add") @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysDictType dict) { if (!dictTypeService.checkDictTypeUnique(dict)) { return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); } dict.setCreateBy(getLoginName()); return toAjax(dictTypeService.insertDictType(dict)); } /** * 修改字典类型 */ @RequiresPermissions("system:dict:edit") @GetMapping("/edit/{dictId}") public String edit(@PathVariable("dictId") Long dictId, ModelMap mmap) { mmap.put("dict", dictTypeService.selectDictTypeById(dictId)); return prefix + "/edit"; } /** * 修改保存字典类型 */ @Log(title = "字典类型", businessType = BusinessType.UPDATE) @RequiresPermissions("system:dict:edit") @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysDictType dict) { if (!dictTypeService.checkDictTypeUnique(dict)) { return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); } dict.setUpdateBy(getLoginName()); return toAjax(dictTypeService.updateDictType(dict)); } @Log(title = "字典类型", businessType = BusinessType.DELETE) @RequiresPermissions("system:dict:remove") @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { dictTypeService.deleteDictTypeByIds(ids); return success(); } /** * 刷新字典缓存 */ @RequiresPermissions("system:dict:remove") @Log(title = "字典类型", businessType = BusinessType.CLEAN) @GetMapping("/refreshCache") @ResponseBody public AjaxResult refreshCache() { dictTypeService.resetDictCache(); return success(); } /** * 查询字典详细 */ @RequiresPermissions("system:dict:list") @GetMapping("/detail/{dictId}") public String detail(@PathVariable("dictId") Long dictId, ModelMap mmap) { mmap.put("dict", dictTypeService.selectDictTypeById(dictId)); mmap.put("dictList", dictTypeService.selectDictTypeAll()); return "system/dict/data/data"; } /** * 校验字典类型 */ @PostMapping("/checkDictTypeUnique") @ResponseBody public boolean checkDictTypeUnique(SysDictType dictType) { return dictTypeService.checkDictTypeUnique(dictType); } /** * 选择字典树 */ @GetMapping("/selectDictTree/{columnId}/{dictType}") public String selectDictTree(@PathVariable("columnId") Long columnId, @PathVariable("dictType") String dictType, ModelMap mmap) { mmap.put("columnId", columnId); mmap.put("dict", dictTypeService.selectDictTypeByType(dictType)); return prefix + "/tree"; } /** * 加载字典列表树 */ @GetMapping("/treeData") @ResponseBody public List treeData() { List ztrees = dictTypeService.selectDictTree(new SysDictType()); return ztrees; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java ================================================ package com.ruoyi.web.controller.system; import java.util.Date; import java.util.List; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysMenu; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.CookieUtils; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.shiro.service.SysPasswordService; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysMenuService; /** * 首页 业务处理 * * @author ruoyi */ @Controller public class SysIndexController extends BaseController { @Autowired private ISysMenuService menuService; @Autowired private ISysConfigService configService; @Autowired private SysPasswordService passwordService; // 系统首页 @GetMapping("/index") public String index(ModelMap mmap, HttpServletRequest request) { // 取身份信息 SysUser user = getSysUser(); // 根据用户id取出菜单 List menus = menuService.selectMenusByUser(user); mmap.put("menus", menus); mmap.put("user", user); mmap.put("sideTheme", configService.selectConfigByKey("sys.index.sideTheme")); mmap.put("skinName", configService.selectConfigByKey("sys.index.skinName")); Boolean footer = Convert.toBool(configService.selectConfigByKey("sys.index.footer"), true); Boolean tagsView = Convert.toBool(configService.selectConfigByKey("sys.index.tagsView"), true); mmap.put("footer", footer); mmap.put("tagsView", tagsView); mmap.put("mainClass", contentMainClass(footer, tagsView)); mmap.put("copyrightYear", RuoYiConfig.getCopyrightYear()); mmap.put("demoEnabled", RuoYiConfig.isDemoEnabled()); mmap.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate())); mmap.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate())); mmap.put("isMobile", ServletUtils.checkAgentIsMobile(ServletUtils.getRequest().getHeader("User-Agent"))); // 菜单导航显示风格 String menuStyle = configService.selectConfigByKey("sys.index.menuStyle"); // 移动端,默认使左侧导航菜单,否则取默认配置 String indexStyle = ServletUtils.checkAgentIsMobile(ServletUtils.getRequest().getHeader("User-Agent")) ? "index" : menuStyle; // 优先Cookie配置导航菜单 Cookie[] cookies = ServletUtils.getRequest().getCookies(); for (Cookie cookie : cookies) { if (StringUtils.isNotEmpty(cookie.getName()) && "nav-style".equalsIgnoreCase(cookie.getName())) { indexStyle = cookie.getValue(); break; } } String webIndex = "topnav".equalsIgnoreCase(indexStyle) ? "index-topnav" : "index"; // CSRF Token request.getSession().setAttribute(ShiroConstants.CSRF_TOKEN, ServletUtils.generateToken()); return webIndex; } // 锁定屏幕 @GetMapping("/lockscreen") public String lockscreen(ModelMap mmap) { mmap.put("user", getSysUser()); ServletUtils.getSession().setAttribute(ShiroConstants.LOCK_SCREEN, true); return "lock"; } // 解锁屏幕 @PostMapping("/unlockscreen") @ResponseBody public AjaxResult unlockscreen(String password) { SysUser user = getSysUser(); if (StringUtils.isNull(user)) { return AjaxResult.error("服务器超时,请重新登录"); } if (passwordService.matches(user, password)) { ServletUtils.getSession().removeAttribute(ShiroConstants.LOCK_SCREEN); return AjaxResult.success(); } return AjaxResult.error("密码不正确,请重新输入。"); } // 切换主题 @GetMapping("/system/switchSkin") public String switchSkin() { return "skin"; } // 切换菜单 @GetMapping("/system/menuStyle/{style}") public void menuStyle(@PathVariable String style, HttpServletResponse response) { CookieUtils.setCookie(response, "nav-style", style); } // 系统介绍 @GetMapping("/system/main") public String main(ModelMap mmap) { mmap.put("version", RuoYiConfig.getVersion()); return "main"; } // content-main class public String contentMainClass(Boolean footer, Boolean tagsView) { if (!footer && !tagsView) { return "tagsview-footer-hide"; } else if (!footer) { return "footer-hide"; } else if (!tagsView) { return "tagsview-hide"; } return StringUtils.EMPTY; } // 检查初始密码是否提醒修改 public boolean initPasswordIsModify(Date pwdUpdateDate) { Integer initPasswordModify = Convert.toInt(configService.selectConfigByKey("sys.account.initPasswordModify")); return initPasswordModify != null && initPasswordModify == 1 && pwdUpdateDate == null; } // 检查密码是否过期 public boolean passwordIsExpiration(Date pwdUpdateDate) { Integer passwordValidateDays = Convert.toInt(configService.selectConfigByKey("sys.account.passwordValidateDays")); if (passwordValidateDays != null && passwordValidateDays > 0) { if (StringUtils.isNull(pwdUpdateDate)) { // 如果从未修改过初始密码,直接提醒过期 return true; } Date nowDate = DateUtils.getNowDate(); return DateUtils.differentDaysByMillisecond(nowDate, pwdUpdateDate) > passwordValidateDays; } return false; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java ================================================ package com.ruoyi.web.controller.system; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.web.service.ConfigService; /** * 登录验证 * * @author ruoyi */ @Controller public class SysLoginController extends BaseController { /** * 是否开启记住我功能 */ @Value("${shiro.rememberMe.enabled: false}") private boolean rememberMe; @Autowired private ConfigService configService; @GetMapping("/login") public String login(HttpServletRequest request, HttpServletResponse response, ModelMap mmap) { // 如果是Ajax请求,返回Json字符串。 if (ServletUtils.isAjaxRequest(request)) { return ServletUtils.renderString(response, "{\"code\":\"1\",\"msg\":\"未登录或登录超时。请重新登录\"}"); } // 是否开启记住我 mmap.put("isRemembered", rememberMe); // 是否开启用户注册 mmap.put("isAllowRegister", Convert.toBool(configService.getKey("sys.account.registerUser"), false)); return "login"; } @PostMapping("/login") @ResponseBody public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe) { UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); return success(); } catch (AuthenticationException e) { String msg = "用户或密码错误"; if (StringUtils.isNotEmpty(e.getMessage())) { msg = e.getMessage(); } return error(msg); } } @GetMapping("/unauth") public String unauth() { return "error/unauth"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysMenu; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.framework.shiro.util.AuthorizationUtils; import com.ruoyi.system.service.ISysMenuService; /** * 菜单信息 * * @author ruoyi */ @Controller @RequestMapping("/system/menu") public class SysMenuController extends BaseController { private String prefix = "system/menu"; @Autowired private ISysMenuService menuService; @RequiresPermissions("system:menu:view") @GetMapping() public String menu() { return prefix + "/menu"; } @RequiresPermissions("system:menu:list") @PostMapping("/list") @ResponseBody public List list(SysMenu menu) { Long userId = ShiroUtils.getUserId(); List menuList = menuService.selectMenuList(menu, userId); return menuList; } /** * 删除菜单 */ @Log(title = "菜单管理", businessType = BusinessType.DELETE) @RequiresPermissions("system:menu:remove") @GetMapping("/remove/{menuId}") @ResponseBody public AjaxResult remove(@PathVariable("menuId") Long menuId) { if (menuService.selectCountMenuByParentId(menuId) > 0) { return AjaxResult.warn("存在子菜单,不允许删除"); } if (menuService.selectCountRoleMenuByMenuId(menuId) > 0) { return AjaxResult.warn("菜单已分配,不允许删除"); } AuthorizationUtils.clearAllCachedAuthorizationInfo(); return toAjax(menuService.deleteMenuById(menuId)); } /** * 新增 */ @RequiresPermissions("system:menu:add") @GetMapping("/add/{parentId}") public String add(@PathVariable("parentId") Long parentId, ModelMap mmap) { SysMenu menu = null; if (0L != parentId) { menu = menuService.selectMenuById(parentId); } else { menu = new SysMenu(); menu.setMenuId(0L); menu.setMenuName("主目录"); } mmap.put("menu", menu); return prefix + "/add"; } /** * 新增保存菜单 */ @Log(title = "菜单管理", businessType = BusinessType.INSERT) @RequiresPermissions("system:menu:add") @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysMenu menu) { if (!menuService.checkMenuNameUnique(menu)) { return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); } menu.setCreateBy(getLoginName()); AuthorizationUtils.clearAllCachedAuthorizationInfo(); return toAjax(menuService.insertMenu(menu)); } /** * 修改菜单 */ @RequiresPermissions("system:menu:edit") @GetMapping("/edit/{menuId}") public String edit(@PathVariable("menuId") Long menuId, ModelMap mmap) { mmap.put("menu", menuService.selectMenuById(menuId)); return prefix + "/edit"; } /** * 修改保存菜单 */ @Log(title = "菜单管理", businessType = BusinessType.UPDATE) @RequiresPermissions("system:menu:edit") @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysMenu menu) { if (!menuService.checkMenuNameUnique(menu)) { return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); } menu.setUpdateBy(getLoginName()); AuthorizationUtils.clearAllCachedAuthorizationInfo(); return toAjax(menuService.updateMenu(menu)); } /** * 保存菜单排序 */ @PostMapping("/updateSort") @ResponseBody public AjaxResult updateSort(@RequestParam String[] menuIds, @RequestParam String[] orderNums) { menuService.updateMenuSort(menuIds, orderNums); return success(); } /** * 选择菜单图标 */ @GetMapping("/icon") public String icon() { return prefix + "/icon"; } /** * 校验菜单名称 */ @PostMapping("/checkMenuNameUnique") @ResponseBody public boolean checkMenuNameUnique(SysMenu menu) { return menuService.checkMenuNameUnique(menu); } /** * 加载角色菜单列表树 */ @GetMapping("/roleMenuTreeData") @ResponseBody public List roleMenuTreeData(SysRole role) { Long userId = ShiroUtils.getUserId(); List ztrees = menuService.roleMenuTreeData(role, userId); return ztrees; } /** * 加载所有菜单列表树 */ @GetMapping("/menuTreeData") @ResponseBody public List menuTreeData() { Long userId = ShiroUtils.getUserId(); List ztrees = menuService.menuTreeData(userId); return ztrees; } /** * 选择菜单树 */ @GetMapping("/selectMenuTree/{menuId}") public String selectMenuTree(@PathVariable("menuId") Long menuId, ModelMap mmap) { mmap.put("menu", menuService.selectMenuById(menuId)); return prefix + "/tree"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.system.domain.SysNotice; import com.ruoyi.system.service.ISysNoticeReadService; import com.ruoyi.system.service.ISysNoticeService; /** * 公告 信息操作处理 * * @author ruoyi */ @Controller @RequestMapping("/system/notice") public class SysNoticeController extends BaseController { private String prefix = "system/notice"; @Autowired private ISysNoticeService noticeService; @Autowired private ISysNoticeReadService noticeReadService; @RequiresPermissions("system:notice:view") @GetMapping() public String notice() { return prefix + "/notice"; } /** * 查询公告列表 */ @RequiresPermissions("system:notice:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysNotice notice) { startPage(); List list = noticeService.selectNoticeList(notice); return getDataTable(list); } /** * 新增公告 */ @RequiresPermissions("system:notice:add") @GetMapping("/add") public String add() { return prefix + "/add"; } /** * 新增保存公告 */ @RequiresPermissions("system:notice:add") @Log(title = "通知公告", businessType = BusinessType.INSERT) @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysNotice notice) { notice.setCreateBy(getLoginName()); return toAjax(noticeService.insertNotice(notice)); } /** * 修改公告 */ @RequiresPermissions("system:notice:edit") @GetMapping("/edit/{noticeId}") public String edit(@PathVariable("noticeId") Long noticeId, ModelMap mmap) { mmap.put("notice", noticeService.selectNoticeById(noticeId)); return prefix + "/edit"; } /** * 修改保存公告 */ @RequiresPermissions("system:notice:edit") @Log(title = "通知公告", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysNotice notice) { notice.setUpdateBy(getLoginName()); return toAjax(noticeService.updateNotice(notice)); } /** * 查询公告详细 */ @RequiresPermissions("system:notice:list") @GetMapping("/view/{noticeId}") public String view(@PathVariable("noticeId") Long noticeId, ModelMap mmap) { mmap.put("notice", noticeService.selectNoticeById(noticeId)); return prefix + "/view"; } /** * 首页顶部公告列表(返回全部正常公告,带当前用户已读标记,最多5条) */ @GetMapping("/listTop") @ResponseBody public AjaxResult listTop() { Long userId = ShiroUtils.getSysUser().getUserId(); List list = noticeReadService.selectNoticeListWithReadStatus(userId, 5); long unreadCount = list.stream().filter(n -> !n.getIsRead()).count(); AjaxResult result = AjaxResult.success(list); result.put("unreadCount", unreadCount); return result; } /** * 标记公告已读 */ @PostMapping("/markRead") @ResponseBody public AjaxResult markRead(Long noticeId) { Long userId = ShiroUtils.getSysUser().getUserId(); noticeReadService.markRead(noticeId, userId); return success(); } /** * 批量标记已读 */ @PostMapping("/markReadAll") @ResponseBody public AjaxResult markReadAll(String ids) { Long userId = ShiroUtils.getSysUser().getUserId(); Long[] noticeIds = Convert.toLongArray(ids); noticeReadService.markReadBatch(userId, noticeIds); return success(); } /** * 删除公告 */ @RequiresPermissions("system:notice:remove") @Log(title = "通知公告", businessType = BusinessType.DELETE) @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { noticeReadService.deleteByNoticeIds(ids); return toAjax(noticeService.deleteNoticeByIds(ids)); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.domain.SysPost; import com.ruoyi.system.service.ISysPostService; /** * 岗位信息操作处理 * * @author ruoyi */ @Controller @RequestMapping("/system/post") public class SysPostController extends BaseController { private String prefix = "system/post"; @Autowired private ISysPostService postService; @RequiresPermissions("system:post:view") @GetMapping() public String operlog() { return prefix + "/post"; } @RequiresPermissions("system:post:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysPost post) { startPage(); List list = postService.selectPostList(post); return getDataTable(list); } @Log(title = "岗位管理", businessType = BusinessType.EXPORT) @RequiresPermissions("system:post:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysPost post) { List list = postService.selectPostList(post); ExcelUtil util = new ExcelUtil(SysPost.class); return util.exportExcel(list, "岗位数据"); } @RequiresPermissions("system:post:remove") @Log(title = "岗位管理", businessType = BusinessType.DELETE) @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { return toAjax(postService.deletePostByIds(ids)); } /** * 新增岗位 */ @RequiresPermissions("system:post:add") @GetMapping("/add") public String add() { return prefix + "/add"; } /** * 新增保存岗位 */ @RequiresPermissions("system:post:add") @Log(title = "岗位管理", businessType = BusinessType.INSERT) @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysPost post) { if (!postService.checkPostNameUnique(post)) { return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); } else if (!postService.checkPostCodeUnique(post)) { return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); } post.setCreateBy(getLoginName()); return toAjax(postService.insertPost(post)); } /** * 修改岗位 */ @RequiresPermissions("system:post:edit") @GetMapping("/edit/{postId}") public String edit(@PathVariable("postId") Long postId, ModelMap mmap) { mmap.put("post", postService.selectPostById(postId)); return prefix + "/edit"; } /** * 修改保存岗位 */ @RequiresPermissions("system:post:edit") @Log(title = "岗位管理", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysPost post) { if (!postService.checkPostNameUnique(post)) { return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); } else if (!postService.checkPostCodeUnique(post)) { return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); } post.setUpdateBy(getLoginName()); return toAjax(postService.updatePost(post)); } /** * 校验岗位名称 */ @PostMapping("/checkPostNameUnique") @ResponseBody public boolean checkPostNameUnique(SysPost post) { return postService.checkPostNameUnique(post); } /** * 校验岗位编码 */ @PostMapping("/checkPostCodeUnique") @ResponseBody public boolean checkPostCodeUnique(SysPost post) { return postService.checkPostCodeUnique(post); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java ================================================ package com.ruoyi.web.controller.system; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.MimeTypeUtils; import com.ruoyi.framework.shiro.service.SysPasswordService; import com.ruoyi.system.service.ISysUserService; /** * 个人信息 业务处理 * * @author ruoyi */ @Controller @RequestMapping("/system/user/profile") public class SysProfileController extends BaseController { private static final Logger log = LoggerFactory.getLogger(SysProfileController.class); private String prefix = "system/user/profile"; @Autowired private ISysUserService userService; @Autowired private SysPasswordService passwordService; /** * 个人信息 */ @GetMapping() public String profile(ModelMap mmap) { SysUser user = getSysUser(); mmap.put("user", user); mmap.put("roleGroup", userService.selectUserRoleGroup(user.getUserId())); mmap.put("postGroup", userService.selectUserPostGroup(user.getUserId())); return prefix + "/profile"; } @GetMapping("/checkPassword") @ResponseBody public boolean checkPassword(String password) { SysUser user = getSysUser(); return passwordService.matches(user, password); } @GetMapping("/resetPwd") public String resetPwd(ModelMap mmap) { SysUser user = getSysUser(); mmap.put("user", userService.selectUserById(user.getUserId())); return prefix + "/resetPwd"; } @Log(title = "重置密码", businessType = BusinessType.UPDATE) @PostMapping("/resetPwd") @ResponseBody public AjaxResult resetPwd(String oldPassword, String newPassword) { SysUser user = getSysUser(); if (!passwordService.matches(user, oldPassword)) { return error("修改密码失败,旧密码错误"); } if (passwordService.matches(user, newPassword)) { return error("新密码不能与旧密码相同"); } user.setSalt(ShiroUtils.randomSalt()); user.setPassword(passwordService.encryptPassword(user.getLoginName(), newPassword, user.getSalt())); if (userService.resetUserPwd(user) > 0) { setSysUser(userService.selectUserById(user.getUserId())); return success(); } return error("修改密码异常,请联系管理员"); } /** * 修改用户 */ @GetMapping("/edit") public String edit(ModelMap mmap) { SysUser user = getSysUser(); mmap.put("user", userService.selectUserById(user.getUserId())); return prefix + "/edit"; } /** * 修改头像 */ @GetMapping("/avatar") public String avatar(ModelMap mmap) { SysUser user = getSysUser(); mmap.put("user", userService.selectUserById(user.getUserId())); return prefix + "/avatar"; } /** * 修改用户 */ @Log(title = "个人信息", businessType = BusinessType.UPDATE) @PostMapping("/update") @ResponseBody public AjaxResult update(SysUser user) { SysUser currentUser = getSysUser(); currentUser.setUserName(user.getUserName()); currentUser.setEmail(user.getEmail()); currentUser.setPhonenumber(user.getPhonenumber()); currentUser.setSex(user.getSex()); if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) { return error("修改用户'" + currentUser.getLoginName() + "'失败,手机号码已存在"); } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) { return error("修改用户'" + currentUser.getLoginName() + "'失败,邮箱账号已存在"); } if (userService.updateUserInfo(currentUser) > 0) { setSysUser(userService.selectUserById(currentUser.getUserId())); return success(); } return error(); } /** * 保存头像 */ @Log(title = "个人信息", businessType = BusinessType.UPDATE) @PostMapping("/updateAvatar") @ResponseBody public AjaxResult updateAvatar(@RequestParam("avatarfile") MultipartFile file) { try { if (!file.isEmpty()) { SysUser currentUser = getSysUser(); String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true); if (userService.updateUserAvatar(currentUser.getUserId(), avatar)) { String oldAvatar = currentUser.getAvatar(); if (StringUtils.isNotEmpty(oldAvatar)) { FileUtils.deleteFile(RuoYiConfig.getProfile() + FileUtils.stripPrefix(oldAvatar)); } currentUser.setAvatar(avatar); setSysUser(currentUser); return success(); } } return error(); } catch (Exception e) { log.error("修改头像失败!", e); return error(e.getMessage()); } } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java ================================================ package com.ruoyi.web.controller.system; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.shiro.service.SysRegisterService; import com.ruoyi.system.service.ISysConfigService; /** * 注册验证 * * @author ruoyi */ @Controller public class SysRegisterController extends BaseController { @Autowired private SysRegisterService registerService; @Autowired private ISysConfigService configService; @GetMapping("/register") public String register() { return "register"; } @PostMapping("/register") @ResponseBody public AjaxResult ajaxRegister(SysUser user) { if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) { return error("当前系统没有开启注册功能!"); } String msg = registerService.register(user); return StringUtils.isEmpty(msg) ? success() : error(msg); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.shiro.util.AuthorizationUtils; import com.ruoyi.system.domain.SysUserRole; import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysUserService; /** * 角色信息 * * @author ruoyi */ @Controller @RequestMapping("/system/role") public class SysRoleController extends BaseController { private String prefix = "system/role"; @Autowired private ISysRoleService roleService; @Autowired private ISysUserService userService; @Autowired private ISysDeptService deptService; @Autowired private ISysMenuService menuService; @RequiresPermissions("system:role:view") @GetMapping() public String role() { return prefix + "/role"; } @RequiresPermissions("system:role:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysRole role) { startPage(); List list = roleService.selectRoleList(role); return getDataTable(list); } @Log(title = "角色管理", businessType = BusinessType.EXPORT) @RequiresPermissions("system:role:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysRole role) { List list = roleService.selectRoleList(role); ExcelUtil util = new ExcelUtil(SysRole.class); return util.exportExcel(list, "角色数据"); } /** * 新增角色 */ @RequiresPermissions("system:role:add") @GetMapping("/add") public String add() { return prefix + "/add"; } /** * 新增保存角色 */ @RequiresPermissions("system:role:add") @Log(title = "角色管理", businessType = BusinessType.INSERT) @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysRole role) { if (!roleService.checkRoleNameUnique(role)) { return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); } else if (!roleService.checkRoleKeyUnique(role)) { return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); } role.setCreateBy(getLoginName()); AuthorizationUtils.clearAllCachedAuthorizationInfo(); return toAjax(roleService.insertRole(role)); } /** * 修改角色 */ @RequiresPermissions("system:role:edit") @GetMapping("/edit/{roleId}") public String edit(@PathVariable("roleId") Long roleId, ModelMap mmap) { roleService.checkRoleDataScope(roleId); mmap.put("role", roleService.selectRoleById(roleId)); return prefix + "/edit"; } /** * 修改保存角色 */ @RequiresPermissions("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysRole role) { roleService.checkRoleAllowed(role); roleService.checkRoleDataScope(role.getRoleId()); if (!roleService.checkRoleNameUnique(role)) { return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); } else if (!roleService.checkRoleKeyUnique(role)) { return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); } role.setUpdateBy(getLoginName()); AuthorizationUtils.clearAllCachedAuthorizationInfo(); return toAjax(roleService.updateRole(role)); } /** * 角色分配数据权限 */ @GetMapping("/authDataScope/{roleId}") public String authDataScope(@PathVariable("roleId") Long roleId, ModelMap mmap) { roleService.checkRoleDataScope(roleId); mmap.put("role", roleService.selectRoleById(roleId)); return prefix + "/dataScope"; } /** * 保存角色分配数据权限 */ @RequiresPermissions("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.UPDATE) @PostMapping("/authDataScope") @ResponseBody public AjaxResult authDataScopeSave(SysRole role) { roleService.checkRoleAllowed(role); roleService.checkRoleDataScope(role.getRoleId()); role.setUpdateBy(getLoginName()); if (roleService.authDataScope(role) > 0) { setSysUser(userService.selectUserById(getUserId())); return success(); } return error(); } @RequiresPermissions("system:role:remove") @Log(title = "角色管理", businessType = BusinessType.DELETE) @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { return toAjax(roleService.deleteRoleByIds(ids)); } /** * 校验角色名称 */ @PostMapping("/checkRoleNameUnique") @ResponseBody public boolean checkRoleNameUnique(SysRole role) { return roleService.checkRoleNameUnique(role); } /** * 校验角色权限 */ @PostMapping("/checkRoleKeyUnique") @ResponseBody public boolean checkRoleKeyUnique(SysRole role) { return roleService.checkRoleKeyUnique(role); } /** * 选择菜单树 */ @GetMapping("/selectMenuTree") public String selectMenuTree() { return prefix + "/tree"; } /** * 角色状态修改 */ @Log(title = "角色管理", businessType = BusinessType.UPDATE) @RequiresPermissions("system:role:edit") @PostMapping("/changeStatus") @ResponseBody public AjaxResult changeStatus(SysRole role) { roleService.checkRoleAllowed(role); roleService.checkRoleDataScope(role.getRoleId()); return toAjax(roleService.changeStatus(role)); } /** * 分配用户 */ @RequiresPermissions("system:role:edit") @GetMapping("/authUser/{roleId}") public String authUser(@PathVariable("roleId") Long roleId, ModelMap mmap) { roleService.checkRoleDataScope(roleId); mmap.put("role", roleService.selectRoleById(roleId)); return prefix + "/authUser"; } /** * 查询已分配用户角色列表 */ @RequiresPermissions("system:role:list") @PostMapping("/authUser/allocatedList") @ResponseBody public TableDataInfo allocatedList(SysUser user) { startPage(); List list = userService.selectAllocatedList(user); return getDataTable(list); } /** * 取消授权 */ @RequiresPermissions("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.GRANT) @PostMapping("/authUser/cancel") @ResponseBody public AjaxResult cancelAuthUser(SysUserRole userRole) { return toAjax(roleService.deleteAuthUser(userRole)); } /** * 批量取消授权 */ @RequiresPermissions("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.GRANT) @PostMapping("/authUser/cancelAll") @ResponseBody public AjaxResult cancelAuthUserAll(Long roleId, String userIds) { return toAjax(roleService.deleteAuthUsers(roleId, userIds)); } /** * 选择用户 */ @RequiresPermissions("system:role:list") @GetMapping("/authUser/selectUser/{roleId}") public String selectUser(@PathVariable("roleId") Long roleId, ModelMap mmap) { mmap.put("role", roleService.selectRoleById(roleId)); return prefix + "/selectUser"; } /** * 查询未分配用户角色列表 */ @RequiresPermissions("system:role:list") @PostMapping("/authUser/unallocatedList") @ResponseBody public TableDataInfo unallocatedList(SysUser user) { startPage(); List list = userService.selectUnallocatedList(user); return getDataTable(list); } /** * 批量选择用户授权 */ @RequiresPermissions("system:role:edit") @Log(title = "角色管理", businessType = BusinessType.GRANT) @PostMapping("/authUser/selectAll") @ResponseBody public AjaxResult selectAuthUserAll(Long roleId, String userIds) { roleService.checkRoleDataScope(roleId); return toAjax(roleService.insertAuthUsers(roleId, userIds)); } /** * 加载角色部门(数据权限)列表树 */ @RequiresPermissions("system:role:edit") @GetMapping("/deptTreeData") @ResponseBody public List deptTreeData(SysRole role) { List ztrees = deptService.roleDeptTreeData(role); return ztrees; } /** * 查看角色详情 */ @RequiresPermissions("system:role:list") @GetMapping("/view/{roleId}") public String view(@PathVariable("roleId") Long roleId, ModelMap mmap) { roleService.checkRoleDataScope(roleId); SysRole role = roleService.selectRoleById(roleId); mmap.put("role", role); // 菜单权限 mmap.put("menuTree", menuService.roleMenuTreeData(role, getUserId())); // 数据权限部门:仅自定义数据权限时传已勾选部门节点 if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(role.getDataScope())) { List deptTree = deptService.roleDeptTreeData(role); mmap.put("deptTree", deptTree); } // 关联用户数量 mmap.put("userCount", roleService.countUserRoleByRoleId(roleId)); return prefix + "/view"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java ================================================ package com.ruoyi.web.controller.system; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.ArrayUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.shiro.service.SysPasswordService; import com.ruoyi.framework.shiro.util.AuthorizationUtils; import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysPostService; import com.ruoyi.system.service.ISysRoleService; import com.ruoyi.system.service.ISysUserService; /** * 用户信息 * * @author ruoyi */ @Controller @RequestMapping("/system/user") public class SysUserController extends BaseController { private String prefix = "system/user"; @Autowired private ISysUserService userService; @Autowired private ISysRoleService roleService; @Autowired private ISysDeptService deptService; @Autowired private ISysPostService postService; @Autowired private SysPasswordService passwordService; @RequiresPermissions("system:user:view") @GetMapping() public String user() { return prefix + "/user"; } @RequiresPermissions("system:user:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysUser user) { startPage(); List list = userService.selectUserList(user); return getDataTable(list); } @Log(title = "用户管理", businessType = BusinessType.EXPORT) @RequiresPermissions("system:user:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysUser user) { List list = userService.selectUserList(user); ExcelUtil util = new ExcelUtil(SysUser.class); return util.exportExcel(list, "用户数据"); } @Log(title = "用户管理", businessType = BusinessType.IMPORT) @RequiresPermissions("system:user:import") @PostMapping("/importData") @ResponseBody public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { ExcelUtil util = new ExcelUtil(SysUser.class); List userList = util.importExcel(file.getInputStream()); String message = userService.importUser(userList, updateSupport, getLoginName()); return AjaxResult.success(message); } @RequiresPermissions("system:user:view") @GetMapping("/importTemplate") @ResponseBody public AjaxResult importTemplate() { ExcelUtil util = new ExcelUtil(SysUser.class); return util.importTemplateExcel("用户数据"); } /** * 新增用户 */ @RequiresPermissions("system:user:add") @GetMapping("/add") public String add(ModelMap mmap) { mmap.put("roles", roleService.selectRoleAll().stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); mmap.put("posts", postService.selectPostAll()); return prefix + "/add"; } /** * 新增保存用户 */ @RequiresPermissions("system:user:add") @Log(title = "用户管理", businessType = BusinessType.INSERT) @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysUser user) { deptService.checkDeptDataScope(user.getDeptId()); roleService.checkRoleDataScope(user.getRoleIds()); if (!userService.checkLoginNameUnique(user)) { return error("新增用户'" + user.getLoginName() + "'失败,登录账号已存在"); } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { return error("新增用户'" + user.getLoginName() + "'失败,手机号码已存在"); } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { return error("新增用户'" + user.getLoginName() + "'失败,邮箱账号已存在"); } user.setSalt(ShiroUtils.randomSalt()); user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt())); user.setPwdUpdateDate(DateUtils.getNowDate()); user.setCreateBy(getLoginName()); return toAjax(userService.insertUser(user)); } /** * 修改用户 */ @RequiresPermissions("system:user:edit") @GetMapping("/edit/{userId}") public String edit(@PathVariable("userId") Long userId, ModelMap mmap) { userService.checkUserDataScope(userId); List roles = roleService.selectRolesByUserId(userId); mmap.put("user", userService.selectUserById(userId)); mmap.put("roles", ShiroUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); mmap.put("posts", postService.selectPostsByUserId(userId)); return prefix + "/edit"; } /** * 查询用户详细 */ @RequiresPermissions("system:user:list") @GetMapping("/view/{userId}") public String view(@PathVariable("userId") Long userId, ModelMap mmap) { userService.checkUserDataScope(userId); mmap.put("user", userService.selectUserById(userId)); mmap.put("roleGroup", userService.selectUserRoleGroup(userId)); mmap.put("postGroup", userService.selectUserPostGroup(userId)); return prefix + "/view"; } /** * 修改保存用户 */ @RequiresPermissions("system:user:edit") @Log(title = "用户管理", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysUser user) { userService.checkUserAllowed(user); userService.checkUserDataScope(user.getUserId()); deptService.checkDeptDataScope(user.getDeptId()); roleService.checkRoleDataScope(user.getRoleIds()); if (!userService.checkLoginNameUnique(user)) { return error("修改用户'" + user.getLoginName() + "'失败,登录账号已存在"); } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { return error("修改用户'" + user.getLoginName() + "'失败,手机号码已存在"); } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { return error("修改用户'" + user.getLoginName() + "'失败,邮箱账号已存在"); } user.setUpdateBy(getLoginName()); AuthorizationUtils.clearAllCachedAuthorizationInfo(); return toAjax(userService.updateUser(user)); } @RequiresPermissions("system:user:resetPwd") @GetMapping("/resetPwd/{userId}") public String resetPwd(@PathVariable("userId") Long userId, ModelMap mmap) { userService.checkUserDataScope(userId); mmap.put("user", userService.selectUserById(userId)); return prefix + "/resetPwd"; } @RequiresPermissions("system:user:resetPwd") @Log(title = "重置密码", businessType = BusinessType.UPDATE) @PostMapping("/resetPwd") @ResponseBody public AjaxResult resetPwdSave(SysUser user) { userService.checkUserAllowed(user); userService.checkUserDataScope(user.getUserId()); user.setSalt(ShiroUtils.randomSalt()); user.setPassword(passwordService.encryptPassword(user.getLoginName(), user.getPassword(), user.getSalt())); if (userService.resetUserPwd(user) > 0) { if (ShiroUtils.getUserId().longValue() == user.getUserId().longValue()) { setSysUser(userService.selectUserById(user.getUserId())); } return success(); } return error(); } /** * 进入授权角色页 */ @RequiresPermissions("system:user:edit") @GetMapping("/authRole/{userId}") public String authRole(@PathVariable("userId") Long userId, ModelMap mmap) { userService.checkUserDataScope(userId); SysUser user = userService.selectUserById(userId); // 获取用户所属的角色列表 List roles = roleService.selectRolesByUserId(userId); mmap.put("user", user); mmap.put("roles", ShiroUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); return prefix + "/authRole"; } /** * 用户授权角色 */ @RequiresPermissions("system:user:edit") @Log(title = "用户管理", businessType = BusinessType.GRANT) @PostMapping("/authRole/insertAuthRole") @ResponseBody public AjaxResult insertAuthRole(Long userId, Long[] roleIds) { userService.checkUserDataScope(userId); roleService.checkRoleDataScope(roleIds); userService.insertUserAuth(userId, roleIds); AuthorizationUtils.clearAllCachedAuthorizationInfo(); return success(); } @RequiresPermissions("system:user:remove") @Log(title = "用户管理", businessType = BusinessType.DELETE) @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { if (ArrayUtils.contains(Convert.toLongArray(ids), getUserId())) { return error("当前用户不能删除"); } return toAjax(userService.deleteUserByIds(ids)); } /** * 校验用户名 */ @PostMapping("/checkLoginNameUnique") @ResponseBody public boolean checkLoginNameUnique(SysUser user) { return userService.checkLoginNameUnique(user); } /** * 校验手机号码 */ @PostMapping("/checkPhoneUnique") @ResponseBody public boolean checkPhoneUnique(SysUser user) { return userService.checkPhoneUnique(user); } /** * 校验email邮箱 */ @PostMapping("/checkEmailUnique") @ResponseBody public boolean checkEmailUnique(SysUser user) { return userService.checkEmailUnique(user); } /** * 用户状态修改 */ @Log(title = "用户管理", businessType = BusinessType.UPDATE) @RequiresPermissions("system:user:edit") @PostMapping("/changeStatus") @ResponseBody public AjaxResult changeStatus(SysUser user) { userService.checkUserAllowed(user); userService.checkUserDataScope(user.getUserId()); return toAjax(userService.changeStatus(user)); } /** * 加载部门列表树 */ @RequiresPermissions("system:user:list") @GetMapping("/deptTreeData") @ResponseBody public List deptTreeData() { List ztrees = deptService.selectDeptTree(new SysDept()); return ztrees; } /** * 选择部门树 * * @param deptId 部门ID */ @RequiresPermissions("system:user:list") @GetMapping("/selectDeptTree/{deptId}") public String selectDeptTree(@PathVariable("deptId") Long deptId, ModelMap mmap) { mmap.put("dept", deptService.selectDeptById(deptId)); return prefix + "/deptTree"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BuildController.java ================================================ package com.ruoyi.web.controller.tool; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.ruoyi.common.core.controller.BaseController; /** * build 表单构建 * * @author ruoyi */ @Controller @RequestMapping("/tool/build") public class BuildController extends BaseController { private String prefix = "tool/build"; @RequiresPermissions("tool:build:view") @GetMapping() public String build() { return prefix + "/build"; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java ================================================ package com.ruoyi.web.controller.tool; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.ruoyi.common.core.controller.BaseController; /** * swagger 接口 * * @author ruoyi */ @Controller @RequestMapping("/tool/swagger") public class SwaggerController extends BaseController { @RequiresPermissions("tool:swagger:view") @GetMapping() public String index() { return redirect("/swagger-ui/index.html"); } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java ================================================ package com.ruoyi.web.controller.tool; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.utils.StringUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; /** * swagger 用户测试方法 * * @author ruoyi */ @Tag(name = "用户信息管理") @RestController @RequestMapping("/test/user") public class TestController extends BaseController { private final static Map users = new LinkedHashMap(); { users.put(1, new UserEntity(1, "admin", "admin123", "15888888888")); users.put(2, new UserEntity(2, "ry", "admin123", "15666666666")); } @Operation(summary = "获取用户列表") @GetMapping("/list") public R> userList() { List userList = new ArrayList(users.values()); return R.ok(userList); } @Operation(summary = "获取用户详细") @GetMapping("/{userId}") public R getUser(@PathVariable(name = "userId") Integer userId) { if (!users.isEmpty() && users.containsKey(userId)) { return R.ok(users.get(userId)); } else { return R.fail("用户不存在"); } } @Operation(summary = "新增用户") @PostMapping("/save") public R save(UserEntity user) { if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) { return R.fail("用户ID不能为空"); } users.put(user.getUserId(), user); return R.ok(); } @Operation(summary = "更新用户") @PutMapping("/update") public R update(@RequestBody UserEntity user) { if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) { return R.fail("用户ID不能为空"); } if (users.isEmpty() || !users.containsKey(user.getUserId())) { return R.fail("用户不存在"); } users.remove(user.getUserId()); users.put(user.getUserId(), user); return R.ok(); } @Operation(summary = "删除用户信息") @DeleteMapping("/{userId}") public R delete(@PathVariable(name = "userId") Integer userId) { if (!users.isEmpty() && users.containsKey(userId)) { users.remove(userId); return R.ok(); } else { return R.fail("用户不存在"); } } } @Schema(description = "用户实体") class UserEntity { @Schema(title = "用户ID") private Integer userId; @Schema(title = "用户名称") private String username; @Schema(title = "用户密码") private String password; @Schema(title = "用户手机") private String mobile; public UserEntity() { } public UserEntity(Integer userId, String username, String password, String mobile) { this.userId = userId; this.username = username; this.password = password; this.mobile = mobile; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } } ================================================ FILE: ruoyi-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java ================================================ package com.ruoyi.web.core.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.ruoyi.common.config.RuoYiConfig; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; /** * Swagger2的接口配置 * * @author ruoyi */ @Configuration public class SwaggerConfig { /** 系统基础配置 */ @Autowired private RuoYiConfig ruoyiConfig; /** * 自定义的 OpenAPI 对象 */ @Bean public OpenAPI customOpenApi() { return new OpenAPI().components(new Components() // 设置认证的请求头 .addSecuritySchemes("apikey", securityScheme())) .addSecurityItem(new SecurityRequirement().addList("apikey")) .info(getApiInfo()); } @Bean public SecurityScheme securityScheme() { return new SecurityScheme() .type(SecurityScheme.Type.APIKEY) .name("Authorization") .in(SecurityScheme.In.HEADER) .scheme("Bearer"); } /** * 添加摘要信息 */ @SuppressWarnings("static-access") public Info getApiInfo() { return new Info() // 设置标题 .title("标题:若依管理系统_接口文档") // 描述 .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") // 作者信息 .contact(new Contact().name(ruoyiConfig.getName())) // 版本 .version("版本号:" + ruoyiConfig.getVersion()); } } ================================================ FILE: ruoyi-admin/src/main/resources/application-druid.yml ================================================ # 数据源配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver druid: # 主库数据源 master: url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: password # 从库数据源 slave: # 从数据源开关/默认关闭 enabled: false url: username: password: # 初始连接数 initialSize: 5 # 最小连接池数量 minIdle: 10 # 最大连接池数量 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置连接超时时间 connectTimeout: 30000 # 配置网络超时时间 socketTimeout: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最大生存的时间,单位是毫秒 maxEvictableIdleTimeMillis: 900000 # 配置检测连接是否有效 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false webStatFilter: enabled: true statViewServlet: enabled: true # 设置白名单,不填则允许所有访问 allow: url-pattern: /druid/* # 控制台管理用户名和密码 login-username: ruoyi login-password: 123456 filter: stat: enabled: true # 慢SQL记录 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true wall: config: multi-statement-allow: true ================================================ FILE: ruoyi-admin/src/main/resources/application.yml ================================================ # 项目相关配置 ruoyi: # 名称 name: RuoYi # 版本 version: 4.8.2 # 版权年份 copyrightYear: 2026 # 实例演示开关 demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: D:/ruoyi/uploadPath # 获取ip地址开关 addressEnabled: false # 开发环境配置 server: # 服务器的HTTP端口,默认为80 port: 80 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 accept-count: 1000 threads: # tomcat最大线程数,默认为200 max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 # 日志配置 logging: level: com.ruoyi: debug org.springframework: warn # 用户配置 user: password: # 密码错误{maxRetryCount}次锁定10分钟 maxRetryCount: 5 # Spring配置 spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 # 模板引擎 thymeleaf: mode: HTML encoding: utf-8 # 禁用缓存 cache: false # 资源信息 messages: # 国际化资源文件路径 basename: static/i18n/messages profiles: active: druid # 文件上传 servlet: multipart: # 单个文件大小 max-file-size: 10MB # 设置总上传的文件大小 max-request-size: 20MB # 服务模块 devtools: restart: # 热部署开关 enabled: true # MyBatis mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml # PageHelper分页插件 pagehelper: helperDialect: mysql supportMethodsArguments: true params: count=countSql # Shiro shiro: user: # 登录地址 loginUrl: /login # 权限认证失败地址 unauthorizedUrl: /unauth # 首页地址 indexUrl: /index # 验证码开关 captchaEnabled: true # 验证码类型 math 数字计算 char 字符验证 captchaType: math cookie: # 设置Cookie的域名 默认空,即当前访问的域名 domain: # 设置cookie的有效访问路径 path: / # 设置HttpOnly属性 httpOnly: true # 设置Cookie的过期时间,天为单位 maxAge: 30 # 设置密钥,务必保持唯一性(生成方式,直接拷贝到main运行即可)Base64.encodeToString(CipherUtils.generateNewKey(128, "AES").getEncoded()) (默认启动生成随机秘钥,随机秘钥会导致之前客户端RememberMe Cookie无效,如设置固定秘钥RememberMe Cookie则有效) cipherKey: session: # Session超时时间,-1代表永不过期(默认30分钟) expireTime: 30 # 同步session到数据库的周期(默认1分钟) dbSyncPeriod: 1 # 相隔多久检查一次session的有效性,默认就是10分钟 validationInterval: 10 # 同一个用户最大会话数,比如2的意思是同一个账号允许最多同时两个人登录(默认-1不限制) maxSession: -1 # 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户 kickoutAfter: false rememberMe: # 是否开启记住我 enabled: true # Springdoc配置 springdoc: api-docs: path: /v3/api-docs swagger-ui: enabled: true path: /swagger-ui.html tags-sorter: alpha group-configs: - group: 'default' display-name: '测试模块' paths-to-match: '/**' packages-to-scan: com.ruoyi.web.controller.tool # 防止XSS攻击 xss: # 过滤开关 enabled: true # 排除链接(多个用逗号分隔) excludes: /system/notice/* # 匹配链接 urlPatterns: /system/*,/monitor/*,/tool/* # 防止csrf攻击 csrf: # 过滤开关 enabled: false # 白名单(多个用逗号分隔) whites: /druid ================================================ FILE: ruoyi-admin/src/main/resources/banner.txt ================================================ Application Version: ${ruoyi.version} Spring Boot Version: ${spring-boot.version} //////////////////////////////////////////////////////////////////// // _ooOoo_ // // o8888888o // // 88" . "88 // // (| ^_^ |) // // O\ = /O // // ____/`---'\____ // // .' \\| |// `. // // / \\||| : |||// \ // // / _||||| -:- |||||- \ // // | | \\\ - /// | | // // | \_| ''\---/'' | | // // \ .-\__ `-` ___/-. / // // ___`. .' /--.--\ `. . ___ // // ."" '< `.___\_<|>_/___.' >'"". // // | | : `- \`.;`\ _ /`;.`/ - ` : | | // // \ \ `-. \_ __\ /__ _/ .-` / / // // ========`-.____`-.___\_____/___.-`____.-'======== // // `=---=' // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // // 佛祖保佑 永不宕机 永无BUG // //////////////////////////////////////////////////////////////////// ================================================ FILE: ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml ================================================ ================================================ FILE: ruoyi-admin/src/main/resources/logback.xml ================================================ ${log.pattern} ${log.path}/sys-info.log ${log.path}/sys-info.%d{yyyy-MM-dd}.log 60 ${log.pattern} INFO ACCEPT DENY ${log.path}/sys-error.log ${log.path}/sys-error.%d{yyyy-MM-dd}.log 60 ${log.pattern} ERROR ACCEPT DENY ${log.path}/sys-user.log ${log.path}/sys-user.%d{yyyy-MM-dd}.log 60 ${log.pattern} ================================================ FILE: ruoyi-admin/src/main/resources/mybatis/mybatis-config.xml ================================================ ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/beautifyhtml/beautifyhtml.js ================================================ /*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */ /* The MIT License (MIT) Copyright (c) 2007-2013 Einar Lielmanis and contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Style HTML --------------- Written by Nochum Sossonko, (nsossonko@hotmail.com) Based on code initially developed by: Einar Lielmanis, http://jsbeautifier.org/ Usage: style_html(html_source); style_html(html_source, options); The options are: indent_size (default 4) — indentation size, indent_char (default space) — character to indent with, max_char (default 250) - maximum amount of characters per line (0 = disable) brace_style (default "collapse") - "collapse" | "expand" | "end-expand" put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line. unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted indent_scripts (default normal) - "keep"|"separate"|"normal" e.g. style_html(html_source, { 'indent_size': 2, 'indent_char': ' ', 'max_char': 78, 'brace_style': 'expand', 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'] }); */ (function() { function style_html(html_source, options, js_beautify, css_beautify) { //Wrapper function to invoke all the necessary constructors and deal with the output. var multi_parser, indent_size, indent_character, max_char, brace_style, unformatted; options = options || {}; indent_size = options.indent_size || 4; indent_character = options.indent_char || ' '; brace_style = options.brace_style || 'collapse'; max_char = options.max_char === 0 ? Infinity : options.max_char || 250; unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; function Parser() { this.pos = 0; //Parser position this.token = ''; this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values parent: 'parent1', parentcount: 1, parent1: '' }; this.tag_type = ''; this.token_text = this.last_token = this.last_text = this.token_type = ''; this.Utils = { //Uilities made available to the various functions whitespace: "\n\r\t ".split(''), single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them in_array: function (what, arr) { for (var i=0; i= this.input.length) { return content.length?content.join(''):['', 'TK_EOF']; } input_char = this.input.charAt(this.pos); this.pos++; this.line_char_count++; if (this.Utils.in_array(input_char, this.Utils.whitespace)) { if (content.length) { space = true; } this.line_char_count--; continue; //don't want to insert unnecessary space } else if (space) { if (this.line_char_count >= this.max_char) { //insert a line when the max_char is reached content.push('\n'); for (var i=0; i', 'igm'); reg_match.lastIndex = this.pos; var reg_array = reg_match.exec(this.input); var end_script = reg_array?reg_array.index:this.input.length; //absolute end of script if(this.pos < end_script) { //get everything in between the script tags content = this.input.substring(this.pos, end_script); this.pos = end_script; } return content; }; this.record_tag = function (tag){ //function to record a tag and its parent in this.tags Object if (this.tags[tag + 'count']) { //check for the existence of this tag type this.tags[tag + 'count']++; this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level } else { //otherwise initialize this tag type this.tags[tag + 'count'] = 1; this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level } this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent) this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1') }; this.retrieve_tag = function (tag) { //function to retrieve the opening tag to the corresponding closer if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it var temp_parent = this.tags.parent; //check to see if it's a closable tag. while (temp_parent) { //till we reach '' (the initial value); if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it break; } temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree } if (temp_parent) { //if we caught something this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent } delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference... delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself if (this.tags[tag + 'count'] === 1) { delete this.tags[tag + 'count']; } else { this.tags[tag + 'count']--; } } }; this.get_tag = function (peek) { //function to get a full tag and parse its type var input_char = '', content = [], comment = '', space = false, tag_start, tag_end, orig_pos = this.pos, orig_line_char_count = this.line_char_count; peek = peek !== undefined ? peek : false; do { if (this.pos >= this.input.length) { if (peek) { this.pos = orig_pos; this.line_char_count = orig_line_char_count; } return content.length?content.join(''):['', 'TK_EOF']; } input_char = this.input.charAt(this.pos); this.pos++; this.line_char_count++; if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space space = true; this.line_char_count--; continue; } if (input_char === "'" || input_char === '"') { if (!content[1] || content[1] !== '!') { //if we're in a comment strings don't get treated specially input_char += this.get_unformatted(input_char); space = true; } } if (input_char === '=') { //no space before = space = false; } if (content.length && content[content.length-1] !== '=' && input_char !== '>' && space) { //no space after = or before > if (this.line_char_count >= this.max_char) { this.print_newline(false, content); this.line_char_count = 0; } else { content.push(' '); this.line_char_count++; } space = false; } if (input_char === '<') { tag_start = this.pos - 1; } content.push(input_char); //inserts character at-a-time (or string) } while (input_char !== '>'); var tag_complete = content.join(''); var tag_index; if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends tag_index = tag_complete.indexOf(' '); } else { //otherwise go with the tag ending tag_index = tag_complete.indexOf('>'); } var tag_check = tag_complete.substring(1, tag_index).toLowerCase(); if (tag_complete.charAt(tag_complete.length-2) === '/' || this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /) if ( ! peek) { this.tag_type = 'SINGLE'; } } else if (tag_check === 'script') { //for later script handling if ( ! peek) { this.record_tag(tag_check); this.tag_type = 'SCRIPT'; } } else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content) if ( ! peek) { this.record_tag(tag_check); this.tag_type = 'STYLE'; } } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags comment = this.get_unformatted('', tag_complete); //...delegate to get_unformatted function content.push(comment); // Preserve collapsed whitespace either before or after this tag. if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){ content.splice(0, 0, this.input.charAt(tag_start - 1)); } tag_end = this.pos - 1; if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){ content.push(this.input.charAt(tag_end + 1)); } this.tag_type = 'SINGLE'; } else if (tag_check.charAt(0) === '!') { //peek for so... comment = this.get_unformatted('-->', tag_complete); //...delegate to get_unformatted content.push(comment); } if ( ! peek) { this.tag_type = 'START'; } } else if (tag_check.indexOf('[endif') !== -1) {//peek for ', tag_complete); content.push(comment); this.tag_type = 'SINGLE'; } } else if ( ! peek) { if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors this.tag_type = 'END'; } else { //otherwise it's a start-tag this.record_tag(tag_check); //push it on the tag stack this.tag_type = 'START'; } if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line this.print_newline(true, this.output); } } if (peek) { this.pos = orig_pos; this.line_char_count = orig_line_char_count; } return content.join(''); //returns fully formatted tag }; this.get_unformatted = function (delimiter, orig_tag) { //function to return unformatted content in its entirety if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) { return ''; } var input_char = ''; var content = ''; var space = true; do { if (this.pos >= this.input.length) { return content; } input_char = this.input.charAt(this.pos); this.pos++; if (this.Utils.in_array(input_char, this.Utils.whitespace)) { if (!space) { this.line_char_count--; continue; } if (input_char === '\n' || input_char === '\r') { content += '\n'; /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect
 tags if they are specified in the 'unformatted array'
                for (var i=0; i]*>\s*$/);

            // if next_tag comes back but is not an isolated tag, then
            // let's treat the 'a' tag as having content
            // and respect the unformatted option
            if (!tag || this.Utils.in_array(tag, unformatted)){
                return true;
            } else {
                return false;
            }
        };

        this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) { //handles input/output and some other printing functions

          this.input = js_source || ''; //gets the input for the Parser
          this.output = [];
          this.indent_character = indent_character;
          this.indent_string = '';
          this.indent_size = indent_size;
          this.brace_style = brace_style;
          this.indent_level = 0;
          this.max_char = max_char;
          this.line_char_count = 0; //count to see if max_char was exceeded

          for (var i=0; i 0) {
              this.indent_level--;
            }
          };
        };
        return this;
      }

      /*_____________________--------------------_____________________*/

      multi_parser = new Parser(); //wrapping functions Parser
      multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style); //initialize starting values

      while (true) {
          var t = multi_parser.get_token();
          multi_parser.token_text = t[0];
          multi_parser.token_type = t[1];

        if (multi_parser.token_type === 'TK_EOF') {
          break;
        }

        switch (multi_parser.token_type) {
          case 'TK_TAG_START':
            multi_parser.print_newline(false, multi_parser.output);
            multi_parser.print_token(multi_parser.token_text);
            multi_parser.indent();
            multi_parser.current_mode = 'CONTENT';
            break;
          case 'TK_TAG_STYLE':
          case 'TK_TAG_SCRIPT':
            multi_parser.print_newline(false, multi_parser.output);
            multi_parser.print_token(multi_parser.token_text);
            multi_parser.current_mode = 'CONTENT';
            break;
          case 'TK_TAG_END':
            //Print new line only if the tag has no content and has child
            if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
                var tag_name = multi_parser.token_text.match(/\w+/)[0];
                var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/);
                if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name) {
                    multi_parser.print_newline(true, multi_parser.output);
                }
            }
            multi_parser.print_token(multi_parser.token_text);
            multi_parser.current_mode = 'CONTENT';
            break;
          case 'TK_TAG_SINGLE':
            // Don't add a newline before elements that should remain unformatted.
            var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
            if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){
                multi_parser.print_newline(false, multi_parser.output);
            }
            multi_parser.print_token(multi_parser.token_text);
            multi_parser.current_mode = 'CONTENT';
            break;
          case 'TK_CONTENT':
            if (multi_parser.token_text !== '') {
              multi_parser.print_token(multi_parser.token_text);
            }
            multi_parser.current_mode = 'TAG';
            break;
          case 'TK_STYLE':
          case 'TK_SCRIPT':
            if (multi_parser.token_text !== '') {
              multi_parser.output.push('\n');
              var text = multi_parser.token_text,
                  _beautifier,
                  script_indent_level = 1;
              if (multi_parser.token_type === 'TK_SCRIPT') {
                _beautifier = typeof js_beautify === 'function' && js_beautify;
              } else if (multi_parser.token_type === 'TK_STYLE') {
                _beautifier = typeof css_beautify === 'function' && css_beautify;
              }

              if (options.indent_scripts === "keep") {
                script_indent_level = 0;
              } else if (options.indent_scripts === "separate") {
                script_indent_level = -multi_parser.indent_level;
              }

              var indentation = multi_parser.get_full_indent(script_indent_level);
              if (_beautifier) {
                // call the Beautifier if avaliable
                text = _beautifier(text.replace(/^\s*/, indentation), options);
              } else {
                // simply indent the string otherwise
                var white = text.match(/^\s*/)[0];
                var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
                var reindent = multi_parser.get_full_indent(script_indent_level -_level);
                text = text.replace(/^\s*/, indentation)
                       .replace(/\r\n|\r|\n/g, '\n' + reindent)
                       .replace(/\s*$/, '');
              }
              if (text) {
                multi_parser.print_token(text);
                multi_parser.print_newline(true, multi_parser.output);
              }
            }
            multi_parser.current_mode = 'TAG';
            break;
        }
        multi_parser.last_token = multi_parser.token_type;
        multi_parser.last_text = multi_parser.token_text;
      }
      return multi_parser.output.join('');
    }

    // If we're running a web page and don't have either of the above, add our one global
    window.html_beautify = function(html_source, options) {
        return style_html(html_source, options, window.js_beautify, window.css_beautify);
    };

}());


================================================
FILE: ruoyi-admin/src/main/resources/static/ajax/libs/blockUI/jquery.blockUI.js
================================================
/*!
 * jQuery blockUI plugin
 * Version 2.70.0-2014.11.23
 * Requires jQuery v1.7 or later
 *
 * Examples at: http://malsup.com/jquery/block/
 * Copyright (c) 2007-2013 M. Alsup
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * Thanks to Amir-Hossein Sobhi for some excellent contributions!
 */

;(function() {
/*jshint eqeqeq:false curly:false latedef:false */
"use strict";

	function setup($) {
		$.fn._fadeIn = $.fn.fadeIn;

		var noOp = $.noop || function() {};

		// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
		// confusing userAgent strings on Vista)
		var msie = /MSIE/.test(navigator.userAgent);
		var ie6  = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
		var mode = document.documentMode || 0;
		var setExpr = $.isFunction( document.createElement('div').style.setExpression );

		// global $ methods for blocking/unblocking the entire page
		$.blockUI   = function(opts) { install(window, opts); };
		$.unblockUI = function(opts) { remove(window, opts); };

		// convenience method for quick growl-like notifications  (http://www.google.com/search?q=growl)
		$.growlUI = function(title, message, timeout, onClose) {
			var $m = $('
'); if (title) $m.append('

'+title+'

'); if (message) $m.append('

'+message+'

'); if (timeout === undefined) timeout = 3000; // Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications var callBlock = function(opts) { opts = opts || {}; $.blockUI({ message: $m, fadeIn : typeof opts.fadeIn !== 'undefined' ? opts.fadeIn : 700, fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000, timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout, centerY: false, showOverlay: false, onUnblock: onClose, css: $.blockUI.defaults.growlCSS }); }; callBlock(); var nonmousedOpacity = $m.css('opacity'); $m.mouseover(function() { callBlock({ fadeIn: 0, timeout: 30000 }); var displayBlock = $('.blockMsg'); displayBlock.stop(); // cancel fadeout if it has started displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency }).mouseout(function() { $('.blockMsg').fadeOut(1000); }); // End konapun additions }; // plugin method for blocking element content $.fn.block = function(opts) { if ( this[0] === window ) { $.blockUI( opts ); return this; } var fullOpts = $.extend({}, $.blockUI.defaults, opts || {}); this.each(function() { var $el = $(this); if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked')) return; $el.unblock({ fadeOut: 0 }); }); return this.each(function() { if ($.css(this,'position') == 'static') { this.style.position = 'relative'; $(this).data('blockUI.static', true); } this.style.zoom = 1; // force 'hasLayout' in ie install(this, opts); }); }; // plugin method for unblocking element content $.fn.unblock = function(opts) { if ( this[0] === window ) { $.unblockUI( opts ); return this; } return this.each(function() { remove(this, opts); }); }; $.blockUI.version = 2.70; // 2nd generation blocking at no extra cost! // override these in your code to change the default behavior and style $.blockUI.defaults = { // message displayed when blocking (use null for no message) message: '
加载中......
', title: null, // title string; only used when theme == true draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) theme: false, // set to true to use with jQuery UI themes // styles for the message when blocking; if you wish to disable // these and use an external stylesheet then do this in your code: // $.blockUI.defaults.css = {}; css: { padding: 0, margin: 0, width: '30%', top: '40%', left: '35%', textAlign: 'center', color: '#000', border: '0px', backgroundColor:'transparent', cursor: 'wait' }, // minimal style set used when themes are used themedCSS: { width: '30%', top: '40%', left: '35%' }, // styles for the overlay overlayCSS: { backgroundColor: '#000', opacity: 0.6, cursor: 'wait' }, // style to replace wait cursor before unblocking to correct issue // of lingering wait cursor cursorReset: 'default', // styles applied when using $.growlUI growlCSS: { width: '350px', top: '10px', left: '', right: '10px', border: 'none', padding: '5px', opacity: 0.6, cursor: 'default', color: '#fff', backgroundColor: '#000', '-webkit-border-radius':'10px', '-moz-border-radius': '10px', 'border-radius': '10px' }, // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w // (hat tip to Jorge H. N. de Vasconcelos) /*jshint scripturl:true */ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', // force usage of iframe in non-IE browsers (handy for blocking applets) forceIframe: false, // z-index for the blocking overlay baseZ: 1000, // set these to true to have the message automatically centered centerX: true, // <-- only effects element blocking (page block controlled via css above) centerY: true, // allow body element to be stetched in ie6; this makes blocking look better // on "short" pages. disable if you wish to prevent changes to the body height allowBodyStretch: true, // enable if you want key and mouse events to be disabled for content that is blocked bindEvents: true, // be default blockUI will supress tab navigation from leaving blocking content // (if bindEvents is true) constrainTabKey: true, // fadeIn time in millis; set to 0 to disable fadeIn on block fadeIn: 200, // fadeOut time in millis; set to 0 to disable fadeOut on unblock fadeOut: 400, // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock timeout: 0, // disable if you don't want to show the overlay showOverlay: true, // if true, focus will be placed in the first available input field when // page blocking focusInput: true, // elements that can receive focus focusableElements: ':input:enabled:visible', // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) // no longer needed in 2012 // applyPlatformOpacityRules: true, // callback method invoked when fadeIn has completed and blocking message is visible onBlock: null, // callback method invoked when unblocking has completed; the callback is // passed the element that has been unblocked (which is the window object for page // blocks) and the options that were passed to the unblock call: // onUnblock(element, options) onUnblock: null, // callback method invoked when the overlay area is clicked. // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used. onOverlayClick: null, // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 quirksmodeOffsetHack: 4, // class name of the message block blockMsgClass: 'blockMsg', // if it is already blocked, then ignore it (don't unblock and reblock) ignoreIfBlocked: false }; // private data and functions follow... var pageBlock = null; var pageBlockEls = []; function install(el, opts) { var css, themedCSS; var full = (el == window); var msg = (opts && opts.message !== undefined ? opts.message : undefined); opts = $.extend({}, $.blockUI.defaults, opts || {}); if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked')) return; opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); if (opts.onOverlayClick) opts.overlayCSS.cursor = 'pointer'; themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); msg = msg === undefined ? opts.message : msg; // remove the current block (if there is one) if (full && pageBlock) remove(window, {fadeOut:0}); // if an existing element is being used as the blocking content then we capture // its current place in the DOM (and current display style) so we can restore // it when we unblock if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { var node = msg.jquery ? msg[0] : msg; var data = {}; $(el).data('blockUI.history', data); data.el = node; data.parent = node.parentNode; data.display = node.style.display; data.position = node.style.position; if (data.parent) data.parent.removeChild(node); } $(el).data('blockUI.onUnblock', opts.onUnblock); var z = opts.baseZ; // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; // layer1 is the iframe layer which is used to supress bleed through of underlying content // layer2 is the overlay layer which has opacity and a wait cursor (by default) // layer3 is the message content that is displayed while blocking var lyr1, lyr2, lyr3, s; if (msie || opts.forceIframe) lyr1 = $(''); else lyr1 = $(''); if (opts.theme) lyr2 = $(''); else lyr2 = $(''); if (opts.theme && full) { s = ''; } else if (opts.theme) { s = ''; } else if (full) { s = ''; } else { s = ''; } lyr3 = $(s); // if we have a message, style it if (msg) { if (opts.theme) { lyr3.css(themedCSS); lyr3.addClass('ui-widget-content'); } else lyr3.css(css); } // style the overlay if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/) lyr2.css(opts.overlayCSS); lyr2.css('position', full ? 'fixed' : 'absolute'); // make iframe layer transparent in IE if (msie || opts.forceIframe) lyr1.css('opacity',0.0); //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); $.each(layers, function() { this.appendTo($par); }); if (opts.theme && opts.draggable && $.fn.draggable) { lyr3.draggable({ handle: '.ui-dialog-titlebar', cancel: 'li' }); } // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0); if (ie6 || expr) { // give body 100% height if (full && opts.allowBodyStretch && $.support.boxModel) $('html,body').css('height','100%'); // fix ie6 issue when blocked element has a border width if ((ie6 || !$.support.boxModel) && !full) { var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); var fixT = t ? '(0 - '+t+')' : 0; var fixL = l ? '(0 - '+l+')' : 0; } // simulate fixed position $.each(layers, function(i,o) { var s = o[0].style; s.position = 'absolute'; if (i < 2) { if (full) s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"'); else s.setExpression('height','this.parentNode.offsetHeight + "px"'); if (full) s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'); else s.setExpression('width','this.parentNode.offsetWidth + "px"'); if (fixL) s.setExpression('left', fixL); if (fixT) s.setExpression('top', fixT); } else if (opts.centerY) { if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); s.marginTop = 0; } else if (!opts.centerY && full) { var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0; var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; s.setExpression('top',expression); } }); } // show the message if (msg) { if (opts.theme) lyr3.find('.ui-widget-content').append(msg); else lyr3.append(msg); if (msg.jquery || msg.nodeType) $(msg).show(); } if ((msie || opts.forceIframe) && opts.showOverlay) lyr1.show(); // opacity is zero if (opts.fadeIn) { var cb = opts.onBlock ? opts.onBlock : noOp; var cb1 = (opts.showOverlay && !msg) ? cb : noOp; var cb2 = msg ? cb : noOp; if (opts.showOverlay) lyr2._fadeIn(opts.fadeIn, cb1); if (msg) lyr3._fadeIn(opts.fadeIn, cb2); } else { if (opts.showOverlay) lyr2.show(); if (msg) lyr3.show(); if (opts.onBlock) opts.onBlock.bind(lyr3)(); } // bind key and mouse events bind(1, el, opts); if (full) { pageBlock = lyr3[0]; pageBlockEls = $(opts.focusableElements,pageBlock); if (opts.focusInput) setTimeout(focus, 20); } else center(lyr3[0], opts.centerX, opts.centerY); if (opts.timeout) { // auto-unblock var to = setTimeout(function() { if (full) $.unblockUI(opts); else $(el).unblock(opts); }, opts.timeout); $(el).data('blockUI.timeout', to); } } // remove the block function remove(el, opts) { var count; var full = (el == window); var $el = $(el); var data = $el.data('blockUI.history'); var to = $el.data('blockUI.timeout'); if (to) { clearTimeout(to); $el.removeData('blockUI.timeout'); } opts = $.extend({}, $.blockUI.defaults, opts || {}); bind(0, el, opts); // unbind events if (opts.onUnblock === null) { opts.onUnblock = $el.data('blockUI.onUnblock'); $el.removeData('blockUI.onUnblock'); } var els; if (full) // crazy selector to handle odd field errors in ie6/7 els = $('body').children().filter('.blockUI').add('body > .blockUI'); else els = $el.find('>.blockUI'); // fix cursor issue if ( opts.cursorReset ) { if ( els.length > 1 ) els[1].style.cursor = opts.cursorReset; if ( els.length > 2 ) els[2].style.cursor = opts.cursorReset; } if (full) pageBlock = pageBlockEls = null; if (opts.fadeOut) { count = els.length; els.stop().fadeOut(opts.fadeOut, function() { if ( --count === 0) reset(els,data,opts,el); }); } else reset(els, data, opts, el); } // move blocking element back into the DOM where it started function reset(els,data,opts,el) { var $el = $(el); if ( $el.data('blockUI.isBlocked') ) return; els.each(function(i,o) { // remove via DOM calls so we don't lose event handlers if (this.parentNode) this.parentNode.removeChild(this); }); if (data && data.el) { data.el.style.display = data.display; data.el.style.position = data.position; data.el.style.cursor = 'default'; // #59 if (data.parent) data.parent.appendChild(data.el); $el.removeData('blockUI.history'); } if ($el.data('blockUI.static')) { $el.css('position', 'static'); // #22 } if (typeof opts.onUnblock == 'function') opts.onUnblock(el,opts); // fix issue in Safari 6 where block artifacts remain until reflow var body = $(document.body), w = body.width(), cssW = body[0].style.width; body.width(w-1).width(w); body[0].style.width = cssW; } // bind/unbind the handler function bind(b, el, opts) { var full = el == window, $el = $(el); // don't bother unbinding if there is nothing to unbind if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) return; $el.data('blockUI.isBlocked', b); // don't bind events when overlay is not in use or if bindEvents is false if (!full || !opts.bindEvents || (b && !opts.showOverlay)) return; // bind anchors and inputs for mouse and key events var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove'; if (b) $(document).bind(events, opts, handler); else $(document).unbind(events, handler); // former impl... // var $e = $('a,:input'); // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); } // event handler to suppress keyboard/mouse events when blocking function handler(e) { // allow tab navigation (conditionally) if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) { if (pageBlock && e.data.constrainTabKey) { var els = pageBlockEls; var fwd = !e.shiftKey && e.target === els[els.length-1]; var back = e.shiftKey && e.target === els[0]; if (fwd || back) { setTimeout(function(){focus(back);},10); return false; } } } var opts = e.data; var target = $(e.target); if (target.hasClass('blockOverlay') && opts.onOverlayClick) opts.onOverlayClick(e); // allow events within the message content if (target.parents('div.' + opts.blockMsgClass).length > 0) return true; // allow events for content that is not being blocked return target.parents().children().filter('div.blockUI').length === 0; } function focus(back) { if (!pageBlockEls) return; var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; if (e) e.focus(); } function center(el, x, y) { var p = el.parentNode, s = el.style; var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); if (x) s.left = l > 0 ? (l+'px') : '0'; if (y) s.top = t > 0 ? (t+'px') : '0'; } function sz(el, p) { return parseInt($.css(el,p),10)||0; } } /*global define:true */ if (typeof define === 'function' && define.amd && define.amd.jQuery) { define(['jquery'], setup); } else { setup(jQuery); } })(); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.css ================================================ /*! * bootstrap-fileinput v5.5.4 * http://plugins.krajee.com/file-input * * Krajee default styling for bootstrap-fileinput. * * Author: Kartik Visweswaran * Copyright: 2014 - 2022, Kartik Visweswaran, Krajee.com * * Licensed under the BSD-3-Clause * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md */ .file-loading input[type=file], input[type=file].file-loading { width: 0; height: 0; } .file-no-browse { position: absolute; left: 50%; bottom: 20%; width: 1px; height: 1px; font-size: 0; opacity: 0; border: none; background: none; outline: none; box-shadow: none; } .kv-hidden, .file-caption-icon, .file-zoom-dialog .modal-header:before, .file-zoom-dialog .modal-header:after, .file-input-new .file-preview, .file-input-new .close, .file-input-new .glyphicon-file, .file-input-new .fileinput-remove-button, .file-input-new .fileinput-upload-button, .file-input-new .no-browse .input-group-btn, .file-input-ajax-new .fileinput-remove-button, .file-input-ajax-new .fileinput-upload-button, .file-input-ajax-new .no-browse .input-group-btn, .hide-content .kv-file-content, .is-locked .fileinput-upload-button, .is-locked .fileinput-remove-button { display: none; } .file-caption .input-group { align-items: center; } .btn-file input[type=file], .file-caption-icon, .file-preview .fileinput-remove, .krajee-default .file-thumb-progress, .file-zoom-dialog .btn-navigate, .file-zoom-dialog .floating-buttons { position: absolute; } .file-caption-icon .kv-caption-icon { line-height: inherit; } .file-input, .file-loading:before, .btn-file, .file-caption, .file-preview, .krajee-default.file-preview-frame, .krajee-default .file-thumbnail-footer, .file-zoom-dialog .modal-dialog { position: relative; } .file-error-message pre, .file-error-message ul, .krajee-default .file-actions, .krajee-default .file-other-error { text-align: left; } .file-error-message pre, .file-error-message ul { margin: 0; } .krajee-default .file-drag-handle, .krajee-default .file-upload-indicator { float: left; margin-top: 10px; width: 16px; height: 16px; } .file-thumb-progress .progress, .file-thumb-progress .progress-bar { font-family: Verdana, Helvetica, sans-serif; font-size: 0.7rem; } .krajee-default .file-thumb-progress .progress, .kv-upload-progress .progress { background-color: #ccc; } .krajee-default .file-caption-info, .krajee-default .file-size-info { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 160px; height: 15px; margin: auto; } .file-zoom-content > .file-object.type-video, .file-zoom-content > .file-object.type-flash, .file-zoom-content > .file-object.type-image { max-width: 100%; max-height: 100%; width: auto; } .file-zoom-content > .file-object.type-video, .file-zoom-content > .file-object.type-flash { height: 100%; } .file-zoom-content > .file-object.type-pdf, .file-zoom-content > .file-object.type-html, .file-zoom-content > .file-object.type-text, .file-zoom-content > .file-object.type-default { width: 100%; } .file-loading:before { content: " Loading..."; display: inline-block; padding-left: 20px; line-height: 16px; font-size: 13px; font-variant: small-caps; color: #999; background: transparent url(loading.gif) top left no-repeat; } .file-object { margin: 0 0 -5px 0; padding: 0; } .btn-file { overflow: hidden; } .btn-file input[type=file] { top: 0; left: 0; min-width: 100%; min-height: 100%; text-align: right; opacity: 0; background: none repeat scroll 0 0 transparent; cursor: inherit; display: block; } .btn-file ::-ms-browse { font-size: 10000px; width: 100%; height: 100%; } .file-caption.icon-visible .file-caption-icon { display: inline-block; } .file-caption.icon-visible .file-caption-name { padding-left: 25px; } .file-caption.icon-visible > .input-group-lg .file-caption-name { padding-left: 30px; } .file-caption.icon-visible > .input-group-sm .file-caption-name { padding-left: 22px; } .file-caption-name:not(.file-caption-disabled) { background-color: transparent; } .file-caption-name.file-processing { font-style: italic; border-color: #bbb; opacity: 0.5; } .file-caption-icon { padding: 7px 5px; left: 4px; } .input-group-lg .file-caption-icon { font-size: 1.25rem; } .input-group-sm .file-caption-icon { font-size: 0.875rem; padding: 0.25rem; } .file-error-message { color: #a94442; background-color: #f2dede; margin: 5px; border: 1px solid #ebccd1; border-radius: 4px; padding: 15px; } .file-error-message pre { margin: 5px 0; } .file-caption-disabled { background-color: #eee; cursor: not-allowed; opacity: 1; } .file-preview { border-radius: 5px; border: 1px solid #ddd; padding: 8px; width: 100%; margin-bottom: 5px; } .file-preview .btn-xs { padding: 1px 5px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .file-preview .fileinput-remove { top: 1px; right: 1px; line-height: 10px; } .file-preview .clickable { cursor: pointer; } .file-preview-image { font: 40px Impact, Charcoal, sans-serif; color: #008000; width: auto; height: auto; max-width: 100%; max-height: 100%; } .krajee-default.file-preview-frame { margin: 8px; border: 1px solid rgba(0, 0, 0, 0.2); box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2); padding: 6px; float: left; text-align: center; } .krajee-default.file-preview-frame .kv-file-content { width: 213px; height: 160px; } .krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered { width: 400px; } .krajee-default.file-preview-frame[data-template="audio"] .kv-file-content { width: 240px; height: 55px; } .krajee-default.file-preview-frame .file-thumbnail-footer { height: 70px; } .krajee-default.file-preview-frame:not(.file-preview-error):hover { border: 1px solid rgba(0, 0, 0, 0.3); box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.4); } .krajee-default .file-preview-text { color: #428bca; border: 1px solid #ddd; outline: none; resize: none; } .krajee-default .file-preview-html { border: 1px solid #ddd; } .krajee-default .file-other-icon { font-size: 6em; line-height: 1; } .krajee-default .file-footer-buttons { float: right; } .krajee-default .file-footer-caption { display: block; text-align: center; padding-top: 4px; font-size: 11px; color: #999; margin-bottom: 30px; } .file-upload-stats { font-size: 10px; text-align: center; width: 100%; } .kv-upload-progress .file-upload-stats { font-size: 12px; margin: -10px 0 5px; } .krajee-default .file-preview-error { opacity: 0.65; box-shadow: none; } .krajee-default .file-thumb-progress { top: 37px; left: 0; right: 0; } .krajee-default.kvsortable-ghost { background: #e1edf7; border: 2px solid #a1abff; } .krajee-default .file-preview-other:hover { opacity: 0.8; } .krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover { color: #000; } .kv-upload-progress .progress { height: 20px; margin: 10px 0; overflow: hidden; } .kv-upload-progress .progress-bar { height: 20px; font-family: Verdana, Helvetica, sans-serif; } /*noinspection CssOverwrittenProperties*/ .file-zoom-dialog .file-other-icon { font-size: 22em; font-size: 50vmin; } .file-zoom-dialog .modal-dialog { width: auto; } .file-zoom-dialog .modal-header { display: flex; align-items: center; justify-content: space-between; } .file-zoom-dialog .btn-navigate { margin: 0 0.1rem; padding: 0; font-size: 1.2rem; width: 2.4rem; height: 2.4rem; top: 50%; border-radius: 50%; text-align: center; } .btn-navigate * { width: auto; } .file-zoom-dialog .floating-buttons { top: 5px; right: 10px; } .file-zoom-dialog .btn-kv-prev { left: 0; } .file-zoom-dialog .btn-kv-next { right: 0; } .file-zoom-dialog .kv-zoom-header { padding: 0.5rem; } .file-zoom-dialog .kv-zoom-body { padding: 0.25rem; } .file-zoom-dialog .kv-zoom-description { position: absolute; opacity: 0.8; font-size: 0.8rem; background-color: #1a1a1a; padding: 1rem; text-align: center; border-radius: 0.5rem; color: #fff; left: 15%; right: 15%; bottom: 15%; } .file-zoom-dialog .kv-desc-hide { float: right; color: #fff; padding: 0 0.1rem; background: none; border: none; } .file-zoom-dialog .kv-desc-hide:hover { opacity: 0.7; } .file-zoom-dialog .kv-desc-hide:focus { opacity: 0.9; } .file-input-new .no-browse .form-control { border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .file-input-ajax-new .no-browse .form-control { border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .file-caption { width: 100%; position: relative; } .file-thumb-loading { background: transparent url(loading.gif) no-repeat scroll center center content-box !important; } .file-drop-zone { border: 1px dashed #aaa; min-height: 260px; border-radius: 4px; text-align: center; vertical-align: middle; margin: 12px 15px 12px 12px; padding: 5px; } .file-drop-zone.clickable:hover { border: 2px dashed #999; } .file-drop-zone.clickable:focus { border: 2px solid #5acde2; } .file-drop-zone .file-preview-thumbnails { cursor: default; } .file-drop-zone-title { color: #aaa; font-size: 1.6em; text-align: center; padding: 85px 10px; cursor: default; } .file-highlighted { border: 2px dashed #999 !important; background-color: #eee; } .file-uploading { background: url(loading-sm.gif) no-repeat center bottom 10px; opacity: 0.65; } .file-zoom-fullscreen .modal-dialog { min-width: 100%; margin: 0; } .file-zoom-fullscreen .modal-content { border-radius: 0; box-shadow: none; min-height: 100vh; } .file-zoom-fullscreen .kv-zoom-body { overflow-y: auto; } .floating-buttons { z-index: 3000; } .floating-buttons .btn-kv { margin-left: 3px; z-index: 3000; } .kv-zoom-actions { min-width: 140px; } .kv-zoom-actions .btn-kv { margin-left: 3px; } .file-zoom-content { text-align: center; white-space: nowrap; min-height: 300px; } .file-zoom-content:hover { background: transparent; } .file-zoom-content .file-preview-image { max-height: 100%; } .file-zoom-content .file-preview-video { max-height: 100%; } .file-zoom-content > .file-object.type-image { height: auto; min-height: inherit; } .file-zoom-content > .file-object.type-audio { width: auto; height: 30px; } @media (min-width: 576px) { .file-zoom-dialog .modal-dialog { max-width: 500px; } } @media (min-width: 992px) { .file-zoom-dialog .modal-lg { max-width: 800px; } } @media (max-width: 767px) { .file-preview-thumbnails { display: flex; justify-content: center; align-items: center; flex-direction: column; } .file-zoom-dialog .modal-header { flex-direction: column; } } @media (max-width: 350px) { .krajee-default.file-preview-frame:not([data-template="audio"]) .kv-file-content { width: 160px; } } @media (max-width: 420px) { .krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered { width: 100%; } } .file-loading[dir=rtl]:before { background: transparent url(loading.gif) top right no-repeat; padding-left: 0; padding-right: 20px; } .clickable .file-drop-zone-title { cursor: pointer; } .file-sortable .file-drag-handle:hover { opacity: 0.7; } .file-sortable .file-drag-handle { cursor: grab; opacity: 1; } .file-grabbing, .file-grabbing * { cursor: not-allowed !important; } .file-grabbing .file-preview-thumbnails * { cursor: grabbing !important; } .file-preview-frame.sortable-chosen { background-color: #d9edf7; border-color: #17a2b8; box-shadow: none !important; } .file-preview .kv-zoom-cache { display: none; } .file-preview-other-frame, .file-preview-object, .kv-file-content, .kv-zoom-body { display: flex; align-items: center; justify-content: center; } .btn-kv-rotate, .kv-file-rotate { display: none; } .rotatable:not(.hide-rotate) .btn-kv-rotate, .rotatable:not(.hide-rotate) .kv-file-rotate { display: inline-block; } .rotatable .file-zoom-detail, .rotatable .kv-file-content, .rotatable .kv-file-content > :first-child { transform-origin: center center; } .rotate-animate { transition: transform 0.3s ease; } .kv-overflow-hidden { overflow: hidden; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-fileinput/fileinput.js ================================================ /*! * bootstrap-fileinput v5.5.4 * http://plugins.krajee.com/file-input * * Author: Kartik Visweswaran * Copyright: 2014 - 2024, Kartik Visweswaran, Krajee.com * * Licensed under the BSD-3-Clause * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md */ (function (factory) { "use strict"; if (typeof define === "function" && define.amd) { define(["jquery"], factory); } else if (typeof module === "object" && typeof module.exports === "object") { factory(require("jquery")); } else { factory(window.jQuery); } })(function ($) { "use strict"; $.fn.fileinputLocales = {}; $.fn.fileinputThemes = {}; if (!$.fn.fileinputBsVersion) { $.fn.fileinputBsVersion = (window.bootstrap && window.bootstrap.Alert && window.bootstrap.Alert.VERSION) || (window.Alert && window.Alert.VERSION) || "3.x.x"; } String.prototype.setTokens = function (replacePairs) { var str = this.toString(), key, re; for (key in replacePairs) { if (replacePairs.hasOwnProperty(key)) { re = new RegExp("{" + key + "}", "g"); str = str.replace(re, replacePairs[key]); } } return str; }; if (!Array.prototype.flatMap) { // polyfill flatMap Array.prototype.flatMap = function (lambda) { return [].concat(this.map(lambda)); }; } var $h, FileInput; // fileinput helper object for all global variables and internal helper methods $h = { FRAMES: ".kv-preview-thumb", SORT_CSS: "file-sortable", INIT_FLAG: "init-", SCRIPT_SRC: (document && document.currentScript && document.currentScript.src) || null, OBJECT_PARAMS: '\n' + '\n' + '\n' + '\n' + '\n' + '\n', DEFAULT_PREVIEW: '
\n' + '{previewFileIcon}\n' + "
", MODAL_ID: "kvFileinputModal", MODAL_EVENTS: ["show", "shown", "hide", "hidden", "loaded"], logMessages: { ajaxError: "{status}: {error}. Error Details: {text}.", badDroppedFiles: "Error scanning dropped files!", badExifParser: "Error loading the piexif.js library. {details}", badInputType: 'The input "type" must be set to "file" for initializing the "bootstrap-fileinput" plugin.', exifWarning: 'To avoid this warning, either set "autoOrientImage" to "false" OR ensure you have loaded ' + 'the "piexif.js" library correctly on your page before the "fileinput.js" script.', invalidChunkSize: 'Invalid upload chunk size: "{chunkSize}". Resumable uploads are disabled.', invalidThumb: 'Invalid thumb frame with id: "{id}".', noResumableSupport: "The browser does not support resumable or chunk uploads.", noUploadUrl: 'The "uploadUrl" is not set. Ajax uploads and resumable uploads have been disabled.', retryStatus: "Retrying upload for chunk # {chunk} for {filename}... retry # {retry}.", chunkQueueError: "Could not push task to ajax pool for chunk index # {index}.", resumableMaxRetriesReached: "Maximum resumable ajax retries ({n}) reached.", resumableRetryError: "Could not retry the resumable request (try # {n})... aborting.", resumableAborting: "Aborting / cancelling the resumable request.", resumableRequestError: "Error processing resumable request. {msg}", }, objUrl: window.URL || window.webkitURL, getZoomPlaceholder: function () { // used to prevent 404 errors in URL parsing var src = $h.SCRIPT_SRC, srcPath, zoomVar = "?kvTemp__2873389129__="; if (!src) { return zoomVar; } srcPath = src.substring(0, src.lastIndexOf("/")); return srcPath.substring(0, srcPath.lastIndexOf("/") + 1) + "img/loading.gif" + zoomVar; }, defaultButtonCss: function (fill) { return "btn-default btn-" + (fill ? "" : "outline-") + "secondary"; }, isBs: function (ver) { var chk = $h.trim(($.fn.fileinputBsVersion || "") + ""); ver = parseInt(ver, 10); if (!chk) { return ver === 4; } return ver === parseInt(chk.charAt(0), 10); }, isNumeric: function (n) { if (n === undefined) { return false; } return !isNaN(parseFloat(n)) && isFinite(n); }, trim: function (val) { if (val === undefined) { return ""; } return val.toString().trim(); }, now: function () { return new Date().getTime(); }, round: function (num) { num = parseFloat(num); return isNaN(num) ? 0 : Math.floor(Math.round(num)); }, getArray: function (obj) { var i, arr = [], len = (obj && obj.length) || 0; for (i = 0; i < len; i++) { arr.push(obj[i]); } return arr; }, getFileRelativePath: function (file) { /** @namespace file.relativePath */ /** @namespace file.webkitRelativePath */ return String(file.newPath || file.relativePath || file.webkitRelativePath || $h.getFileName(file) || null); }, getFileId: function (file, generateFileId) { var relativePath = $h.getFileRelativePath(file); if (typeof generateFileId === "function") { return generateFileId(file); } if (!file) { return null; } if (!relativePath) { return null; } return file.size + "_" + encodeURIComponent(relativePath).replace(/%/g, "_"); }, getFrameSelector: function (id, selector) { selector = selector || ""; return '[id="' + id + '"]' + selector; }, getZoomSelector: function (id, selector) { return $h.getFrameSelector("zoom-" + id, selector); }, getFrameElement: function ($element, id, selector) { return $element.find($h.getFrameSelector(id, selector)); }, getZoomElement: function ($element, id, selector) { return $element.find($h.getZoomSelector(id, selector)); }, getElapsed: function (seconds) { var delta = seconds, out = "", result = {}, structure = { year: 31536000, month: 2592000, week: 604800, // uncomment row to ignore day: 86400, // feel free to add your own row hour: 3600, minute: 60, second: 1, }; $h.getObjectKeys(structure).forEach(function (key) { result[key] = Math.floor(delta / structure[key]); delta -= result[key] * structure[key]; }); $.each(result, function (key, value) { if (value > 0) { out += (out ? " " : "") + value + key.substring(0, 1); } }); return out; }, debounce: function (func, delay) { var inDebounce; return function () { var args = arguments, context = this; clearTimeout(inDebounce); inDebounce = setTimeout(function () { func.apply(context, args); }, delay); }; }, stopEvent: function (e) { e.stopPropagation(); e.preventDefault(); }, getFileName: function (file) { /** @namespace file.fileName */ return file ? file.fileName || file.name || "" : ""; // some confusion in different versions of Firefox }, createObjectURL: function (data) { if ($h.objUrl && $h.objUrl.createObjectURL && data) { return $h.objUrl.createObjectURL(data); } return ""; }, revokeObjectURL: function (data) { if ($h.objUrl && $h.objUrl.revokeObjectURL && data) { $h.objUrl.revokeObjectURL(data); } }, compare: function (input, str, exact) { return input !== undefined && (exact ? input === str : input.match(str)); }, isIE: function (ver) { var div, status; // check for IE versions < 11 if (navigator.appName !== "Microsoft Internet Explorer") { return false; } if (ver === 10) { return new RegExp("msie\\s" + ver, "i").test(navigator.userAgent); } div = document.createElement("div"); div.innerHTML = ""; status = div.getElementsByTagName("i").length; document.body.appendChild(div); div.parentNode.removeChild(div); return status; }, canOrientImage: function ($el) { var $img = $(document.createElement("img")).css({ width: "1px", height: "1px" }).insertAfter($el), flag = $img.css("image-orientation"); $img.remove(); return !!flag; }, canAssignFilesToInput: function () { var input = document.createElement("input"); try { input.type = "file"; input.files = null; return true; } catch (err) { return false; } }, getDragDropFolders: function (items) { var i, item, len = items ? items.length : 0, folders = 0; if (len > 0 && items[0].webkitGetAsEntry()) { for (i = 0; i < len; i++) { item = items[i].webkitGetAsEntry(); if (item && item.isDirectory) { folders++; } } } return folders; }, initModal: function ($modal) { var $body = $("body"); if ($body.length) { $modal.appendTo($body); } }, isFunction: function (v) { return typeof v === "function"; }, isEmpty: function (value, trim) { if (value === undefined || value === null || value === "") { return true; } if ($h.isString(value) && trim) { return $h.trim(value) === ""; } if ($h.isArray(value)) { return value.length === 0; } if ($.isPlainObject(value) && $.isEmptyObject(value)) { return true; } return false; }, isArray: function (a) { return Array.isArray(a) || Object.prototype.toString.call(a) === "[object Array]"; }, isString: function (a) { return Object.prototype.toString.call(a) === "[object String]"; }, ifSet: function (needle, haystack, def) { def = def || ""; return haystack && typeof haystack === "object" && needle in haystack ? haystack[needle] : def; }, cleanArray: function (arr) { if (!(arr instanceof Array)) { arr = []; } return arr.filter(function (e) { return e !== undefined && e !== null; }); }, spliceArray: function (arr, index, reverseOrder) { var i, j = 0, out = [], newArr; if (!(arr instanceof Array)) { return []; } newArr = $.extend(true, [], arr); if (reverseOrder) { newArr.reverse(); } for (i = 0; i < newArr.length; i++) { if (i !== index) { out[j] = newArr[i]; j++; } } if (reverseOrder) { out.reverse(); } return out; }, getNum: function (num, def) { def = def || 0; if (typeof num === "number") { return num; } if (typeof num === "string") { num = parseFloat(num); } return isNaN(num) ? def : num; }, hasFileAPISupport: function () { return !!(window.File && window.FileReader); }, hasDragDropSupport: function () { var div = document.createElement("div"); /** @namespace div.draggable */ /** @namespace div.ondragstart */ /** @namespace div.ondrop */ return ( !$h.isIE(9) && (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined)) ); }, hasFileUploadSupport: function () { return $h.hasFileAPISupport() && window.FormData; }, hasBlobSupport: function () { try { return !!window.Blob && Boolean(new Blob()); } catch (e) { return false; } }, hasArrayBufferViewSupport: function () { try { return new Blob([new Uint8Array(100)]).size === 100; } catch (e) { return false; } }, hasResumableUploadSupport: function () { /** @namespace Blob.prototype.webkitSlice */ /** @namespace Blob.prototype.mozSlice */ return ( $h.hasFileUploadSupport() && $h.hasBlobSupport() && $h.hasArrayBufferViewSupport() && (!!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice || !!Blob.prototype.slice || false) ); }, dataURI2Blob: function (dataURI) { var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder, canBlob = $h.hasBlobSupport(), byteStr, arrayBuffer, intArray, i, mimeStr, bb, canProceed = (canBlob || BlobBuilder) && window.atob && window.ArrayBuffer && window.Uint8Array; if (!canProceed) { return null; } if (dataURI.split(",")[0].indexOf("base64") >= 0) { byteStr = atob(dataURI.split(",")[1]); } else { byteStr = decodeURIComponent(dataURI.split(",")[1]); } arrayBuffer = new ArrayBuffer(byteStr.length); intArray = new Uint8Array(arrayBuffer); for (i = 0; i < byteStr.length; i += 1) { intArray[i] = byteStr.charCodeAt(i); } mimeStr = dataURI.split(",")[0].split(":")[1].split(";")[0]; if (canBlob) { return new Blob([$h.hasArrayBufferViewSupport() ? intArray : arrayBuffer], { type: mimeStr }); } bb = new BlobBuilder(); bb.append(arrayBuffer); return bb.getBlob(mimeStr); }, arrayBuffer2String: function (buffer) { if (window.TextDecoder) { return new TextDecoder("utf-8").decode(buffer); } var array = Array.prototype.slice.apply(new Uint8Array(buffer)), out = "", i = 0, len, c, char2, char3; len = array.length; while (i < len) { c = array[i++]; switch ( c >> 4 // jshint ignore:line ) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // 0xxxxxxx out += String.fromCharCode(c); break; case 12: case 13: // 110x xxxx 10xx xxxx char2 = array[i++]; out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f)); // jshint ignore:line break; case 14: // 1110 xxxx 10xx xxxx 10xx xxxx char2 = array[i++]; char3 = array[i++]; out += String.fromCharCode( ((c & 0x0f) << 12) | // jshint ignore:line ((char2 & 0x3f) << 6) | // jshint ignore:line ((char3 & 0x3f) << 0) ); // jshint ignore:line break; } } return out; }, isSvg: function (str) { if ($h.isEmpty(str)) { return false; } str = $h.trim(str).replace(/\n/g, " "); if (str.length === 0) { return false; } return str.match(/^\s*<\?xml/i) && (str.match(/" + str + "")); }, uniqId: function () { return (new Date().getTime() + Math.floor(Math.random() * Math.pow(10, 15))).toString(36); }, cspBuffer: { CSP_ATTRIB: "data-csp-01928735", // a randomly named temporary attribute to store the CSP elem id domElementsStyles: {}, stash: function (htmlString) { var self = this, outerDom = $.parseHTML("
" + htmlString + "
"), $el = $(outerDom); $el.find("[style]").each(function (key, elem) { var $elem = $(elem), styleDeclaration = $elem[0].style, id = $h.uniqId(), styles = {}; if (styleDeclaration && styleDeclaration.length) { $(styleDeclaration).each(function () { styles[this] = styleDeclaration[this]; }); self.domElementsStyles[id] = styles; $elem.removeAttr("style").attr(self.CSP_ATTRIB, id); } }); $el.filter("*").removeAttr("style"); // make sure all style attr are removed var values = Object.values ? Object.values(outerDom) : Object.keys(outerDom).map(function (itm) { return outerDom[itm]; }); return values .flatMap(function (elem) { return elem.innerHTML; }) .join(""); }, apply: function (domElement) { var self = this, $el = $(domElement); $el.find("[" + self.CSP_ATTRIB + "]").each(function (key, elem) { var $elem = $(elem), id = $elem.attr(self.CSP_ATTRIB), styles = self.domElementsStyles[id]; if (styles) { $elem.css(styles); } $elem.removeAttr(self.CSP_ATTRIB); }); self.domElementsStyles = {}; }, }, setHtml: function ($elem, htmlString) { var buf = $h.cspBuffer; $elem.html(buf.stash(htmlString)); buf.apply($elem); return $elem; }, htmlEncode: function (str, undefVal) { if (str === undefined) { return undefVal || null; } return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }, replaceTags: function (str, tags) { var out = str; if (!tags) { return out; } $.each(tags, function (key, value) { if (typeof value === "function") { value = value(); } out = out.split(key).join(value); }); return out; }, cleanMemory: function ($thumb) { var data = $thumb.is("img") ? $thumb.attr("src") : $thumb.find("source").attr("src"); $h.revokeObjectURL(data); }, findFileName: function (filePath) { var sepIndex = filePath.lastIndexOf("/"); if (sepIndex === -1) { sepIndex = filePath.lastIndexOf("\\"); } return filePath.split(filePath.substring(sepIndex, sepIndex + 1)).pop(); }, checkFullScreen: function () { return ( document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement ); }, toggleFullScreen: function (maximize) { var doc = document, de = doc.documentElement, isFullScreen = $h.checkFullScreen(); if (de && maximize && !isFullScreen) { if (de.requestFullscreen) { de.requestFullscreen(); } else { if (de.msRequestFullscreen) { de.msRequestFullscreen(); } else { if (de.mozRequestFullScreen) { de.mozRequestFullScreen(); } else { if (de.webkitRequestFullscreen) { de.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); } } } } } else { if (isFullScreen) { if (doc.exitFullscreen) { doc.exitFullscreen(); } else { if (doc.msExitFullscreen) { doc.msExitFullscreen(); } else { if (doc.mozCancelFullScreen) { doc.mozCancelFullScreen(); } else { if (doc.webkitExitFullscreen) { doc.webkitExitFullscreen(); } } } } } } }, moveArray: function (arr, oldIndex, newIndex, reverseOrder) { var newArr = $.extend(true, [], arr); if (reverseOrder) { newArr.reverse(); } if (newIndex >= newArr.length) { var k = newIndex - newArr.length; while (k-- + 1) { newArr.push(undefined); } } newArr.splice(newIndex, 0, newArr.splice(oldIndex, 1)[0]); if (reverseOrder) { newArr.reverse(); } return newArr; }, closeButton: function (css) { css = ($h.isBs(5) ? "btn-close" : "close") + (css ? " " + css : ""); return ( '" ); }, getRotation: function (value) { switch (value) { case 2: return "rotateY(180deg)"; case 3: return "rotate(180deg)"; case 4: return "rotate(180deg) rotateY(180deg)"; case 5: return "rotate(270deg) rotateY(180deg)"; case 6: return "rotate(90deg)"; case 7: return "rotate(90deg) rotateY(180deg)"; case 8: return "rotate(270deg)"; default: return ""; } }, setTransform: function (el, val) { if (!el) { return; } el.style.transform = val; el.style.webkitTransform = val; el.style["-moz-transform"] = val; el.style["-ms-transform"] = val; el.style["-o-transform"] = val; }, getObjectKeys: function (obj) { var keys = []; if (obj) { $.each(obj, function (key) { keys.push(key); }); } return keys; }, getObjectSize: function (obj) { return $h.getObjectKeys(obj).length; }, /** * Small dependency injection for the task manager * https://gist.github.com/fearphage/4341799 */ whenAll: function (array) { var s = [].slice, resolveValues = arguments.length === 1 && $h.isArray(array) ? array : s.call(arguments), deferred = $.Deferred(), i, failed = 0, value, length = resolveValues.length, remaining = length, rejectContexts, rejectValues, resolveContexts, updateFunc; rejectContexts = rejectValues = resolveContexts = Array(length); updateFunc = function (index, contexts, values) { return function () { if (values !== resolveValues) { failed++; } deferred.notifyWith((contexts[index] = this), (values[index] = s.call(arguments))); if (!--remaining) { deferred[(!failed ? "resolve" : "reject") + "With"](contexts, values); } }; }; for (i = 0; i < length; i++) { if ((value = resolveValues[i]) && $h.isFunction(value.promise)) { value .promise() .done(updateFunc(i, resolveContexts, resolveValues)) .fail(updateFunc(i, rejectContexts, rejectValues)); } else { deferred.notifyWith(this, value); --remaining; } } if (!remaining) { deferred.resolveWith(resolveContexts, resolveValues); } return deferred.promise(); }, }; FileInput = function (element, options) { var self = this; self.$element = $(element); self.$parent = self.$element.parent(); if (!self._validate()) { return; } self.isPreviewable = $h.hasFileAPISupport(); self.isIE9 = $h.isIE(9); self.isIE10 = $h.isIE(10); if (self.isPreviewable || self.isIE9) { self._init(options); self._listen(); } self.$element.removeClass("file-loading"); }; FileInput.prototype = { constructor: FileInput, _cleanup: function () { var self = this; self.reader = null; self.clearFileStack(); self.fileBatchCompleted = true; self.isError = false; self.isDuplicateError = false; self.isPersistentError = false; self.cancelling = false; self.paused = false; self.lastProgress = 0; self._initAjax(); }, _isAborted: function () { var self = this; return self.cancelling || self.paused; }, _initAjax: function () { var self = this, tm = (self.taskManager = { pool: {}, addPool: function (id) { return (tm.pool[id] = new tm.TasksPool(id)); }, getPool: function (id) { return tm.pool[id]; }, addTask: function (id, logic) { // add standalone task directly from task manager return new tm.Task(id, logic); }, TasksPool: function (id) { var tp = this; tp.id = id; tp.cancelled = false; tp.cancelledDeferrer = $.Deferred(); tp.tasks = {}; tp.addTask = function (id, logic) { return (tp.tasks[id] = new tm.Task(id, logic)); }; tp.size = function () { return $h.getObjectSize(tp.tasks); }; tp.run = function (maxThreads) { var i = 0, failed = false, task, tasksList = $h.getObjectKeys(tp.tasks).map(function (key) { return tp.tasks[key]; }), tasksDone = [], deferred = $.Deferred(), enqueue, callback; if (tp.cancelled) { tp.cancelledDeferrer.resolve(); return deferred.reject(); } // if run all at once if (!maxThreads) { var tasksDeferredList = $h.getObjectKeys(tp.tasks).map(function (key) { return tp.tasks[key].deferred; }); // when all are done $h.whenAll(tasksDeferredList) .done(function () { var argv = $h.getArray(arguments); if (!tp.cancelled) { deferred.resolve.apply(null, argv); tp.cancelledDeferrer.reject(); } else { deferred.reject.apply(null, argv); tp.cancelledDeferrer.resolve(); } }) .fail(function () { var argv = $h.getArray(arguments); deferred.reject.apply(null, argv); if (!tp.cancelled) { tp.cancelledDeferrer.reject(); } else { tp.cancelledDeferrer.resolve(); } }); // run all tasks $.each(tp.tasks, function (id) { task = tp.tasks[id]; task.run(); }); return deferred; } enqueue = function (task) { $.when(task.deferred) .fail(function () { failed = true; callback.apply(null, arguments); }) .always(callback); }; callback = function () { var argv = $h.getArray(arguments); // notify a task just ended deferred.notify(argv); tasksDone.push(argv); if (tp.cancelled) { deferred.reject.apply(null, tasksDone); tp.cancelledDeferrer.resolve(); return; } if (tasksDone.length === tp.size()) { if (failed) { deferred.reject.apply(null, tasksDone); } else { deferred.resolve.apply(null, tasksDone); } } // if there are any tasks remaining if (tasksList.length) { task = tasksList.shift(); enqueue(task); task.run(); } }; // run the first "maxThreads" tasks while (tasksList.length && i++ < maxThreads) { task = tasksList.shift(); enqueue(task); task.run(); } return deferred; }; tp.cancel = function () { tp.cancelled = true; return tp.cancelledDeferrer; }; }, Task: function (id, logic) { var tk = this; tk.id = id; tk.deferred = $.Deferred(); tk.logic = logic; tk.context = null; tk.run = function () { var argv = $h.getArray(arguments); argv.unshift(tk.deferred); // add deferrer as first argument logic.apply(tk.context, argv); // run task return tk.deferred; // return deferrer }; tk.runWithContext = function (context) { tk.context = context; return tk.run(); }; }, }); self.ajaxQueue = []; self.ajaxRequests = []; self.ajaxPool = null; self.ajaxAborted = false; }, _init: function (options, refreshMode) { var self = this, f, $el = self.$element, $cont, t, tmp; self.options = options; self.zoomPlaceholder = $h.getZoomPlaceholder(); self.canOrientImage = $h.canOrientImage($el); $.each(options, function (key, value) { switch (key) { case "minFileCount": case "maxFileCount": case "maxTotalFileCount": case "minFileSize": case "maxFileSize": case "maxMultipleFileSize": case "maxFilePreviewSize": case "resizeQuality": case "resizeIfSizeMoreThan": case "progressUploadThreshold": case "initialPreviewCount": case "zoomModalHeight": case "minImageHeight": case "maxImageHeight": case "minImageWidth": case "maxImageWidth": case "bytesToKB": self[key] = $h.getNum(value); break; default: self[key] = value; break; } }); if (!self.bytesToKB || self.bytesToKB <= 0) { self.bytesToKB = 1024; } if (self.errorCloseButton === undefined) { self.errorCloseButton = $h.closeButton("kv-error-close" + ($h.isBs(5) ? " float-end" : "")); } if (self.maxTotalFileCount > 0 && self.maxTotalFileCount < self.maxFileCount) { self.maxTotalFileCount = self.maxFileCount; } if (self.rtl) { // swap buttons for rtl tmp = self.previewZoomButtonIcons.prev; self.previewZoomButtonIcons.prev = self.previewZoomButtonIcons.next; self.previewZoomButtonIcons.next = tmp; } // validate chunk threads to not exceed maxAjaxThreads if (!isNaN(self.maxAjaxThreads) && self.maxAjaxThreads < self.resumableUploadOptions.maxThreads) { self.resumableUploadOptions.maxThreads = self.maxAjaxThreads; } self._initFileManager(); if (typeof self.autoOrientImage === "function") { self.autoOrientImage = self.autoOrientImage(); } if (typeof self.autoOrientImageInitial === "function") { self.autoOrientImageInitial = self.autoOrientImageInitial(); } if (!refreshMode) { self._cleanup(); } self.duplicateErrors = []; self.$form = $el.closest("form"); self._initTemplateDefaults(); self.uploadFileAttr = !$h.isEmpty($el.attr("name")) ? $el.attr("name") : "file_data"; t = self._getLayoutTemplate("progress"); self.progressTemplate = t.replace("{class}", self.progressClass); self.progressInfoTemplate = t.replace("{class}", self.progressInfoClass); self.progressPauseTemplate = t.replace("{class}", self.progressPauseClass); self.progressCompleteTemplate = t.replace("{class}", self.progressCompleteClass); self.progressErrorTemplate = t.replace("{class}", self.progressErrorClass); self.isDisabled = $el.attr("disabled") || $el.attr("readonly"); if (self.isDisabled) { $el.attr("disabled", true); } self.isClickable = self.browseOnZoneClick && self.showPreview && (self.dropZoneEnabled || !$h.isEmpty(self.defaultPreviewContent)); self.isAjaxUpload = $h.hasFileUploadSupport() && !$h.isEmpty(self.uploadUrl); self.dropZoneEnabled = $h.hasDragDropSupport() && self.dropZoneEnabled; if (!self.isAjaxUpload) { self.dropZoneEnabled = self.dropZoneEnabled && $h.canAssignFilesToInput(); } self.slug = typeof options.slugCallback === "function" ? options.slugCallback : self._slugDefault; self.mainTemplate = self.showCaption ? self._getLayoutTemplate("main1") : self._getLayoutTemplate("main2"); self.captionTemplate = self._getLayoutTemplate("caption"); self.previewGenericTemplate = self._getPreviewTemplate("generic"); if (!self.imageCanvas && self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) { self.imageCanvas = document.createElement("canvas"); self.imageCanvasContext = self.imageCanvas.getContext("2d"); } if ($h.isEmpty($el.attr("id"))) { $el.attr("id", $h.uniqId()); } self.namespace = ".fileinput_" + $el.attr("id").replace(/-/g, "_"); if (self.$container === undefined) { self.$container = self._createContainer(); } else { self._refreshContainer(); } $cont = self.$container; self.$dropZone = $cont.find(".file-drop-zone"); self.$progress = $cont.find(".kv-upload-progress"); self.$btnUpload = $cont.find(".fileinput-upload"); self.$captionContainer = $h.getElement(options, "elCaptionContainer", $cont.find(".file-caption")); self.$caption = $h.getElement(options, "elCaptionText", $cont.find(".file-caption-name")); if (!$h.isEmpty(self.msgPlaceholder)) { f = $el.attr("multiple") ? self.filePlural : self.fileSingle; self.$caption.attr("placeholder", self.msgPlaceholder.replace("{files}", f)); } self.$captionIcon = self.$captionContainer.find(".file-caption-icon"); self.$previewContainer = $h.getElement(options, "elPreviewContainer", $cont.find(".file-preview")); self.$preview = $h.getElement(options, "elPreviewImage", $cont.find(".file-preview-thumbnails")); self.$previewStatus = $h.getElement(options, "elPreviewStatus", $cont.find(".file-preview-status")); self.$errorContainer = $h.getElement( options, "elErrorContainer", self.$previewContainer.find(".kv-fileinput-error") ); self._validateDisabled(); if (!$h.isEmpty(self.msgErrorClass)) { $h.addCss(self.$errorContainer, self.msgErrorClass); } if (!refreshMode) { self._resetErrors(); self.$errorContainer.hide(); self.previewInitId = "thumb-" + $el.attr("id"); self._initPreviewCache(); self._initPreview(true); self._initPreviewActions(); if (self.$parent.hasClass("file-loading")) { self.$container.insertBefore(self.$parent); self.$parent.remove(); } } else { if (!self._errorsExist()) { self.$errorContainer.hide(); } } self._setFileDropZoneTitle(); if ($el.attr("disabled")) { self.disable(); } self._initZoom(); if (self.hideThumbnailContent) { $h.addCss(self.$preview, "hide-content"); } }, _initFileManager: function () { var self = this; self.uploadStartTime = $h.now(); self.fileManager = { stack: {}, filesProcessed: [], errors: [], loadedImages: {}, totalImages: 0, totalFiles: null, totalSize: null, uploadedSize: 0, stats: {}, bpsLog: [], bps: 0, initStats: function (id) { var data = { started: $h.now() }; if (id) { self.fileManager.stats[id] = data; } else { self.fileManager.stats = data; } }, getUploadStats: function (id, loaded, total) { var fm = self.fileManager, started = id ? (fm.stats[id] && fm.stats[id].started) || $h.now() : self.uploadStartTime, elapsed = ($h.now() - started) / 1000, bps = Math.ceil(elapsed ? loaded / elapsed : 0), pendingBytes = total - loaded, out, delay = fm.bpsLog.length ? self.bitrateUpdateDelay : 0; setTimeout(function () { var i, j = 0, n = 0, len, beg; fm.bpsLog.push(bps); fm.bpsLog.sort(function (a, b) { return a - b; }); len = fm.bpsLog.length; beg = len > 10 ? len - 10 : Math.ceil(len / 2); for (i = len; i > beg; i--) { n = parseFloat(fm.bpsLog[i]); j++; } fm.bps = (j > 0 ? n / j : 0) * 64; }, delay); out = { fileId: id, started: started, elapsed: elapsed, loaded: loaded, total: total, bps: fm.bps, bitrate: self._getSize(fm.bps, false, self.bitRateUnits), pendingBytes: pendingBytes, }; if (id) { fm.stats[id] = out; } else { fm.stats = out; } return out; }, exists: function (id) { return $.inArray(id, self.fileManager.getIdList()) !== -1; }, count: function () { return self.fileManager.getIdList().length; }, total: function () { var fm = self.fileManager; if (!fm.totalFiles) { fm.totalFiles = fm.count(); } return fm.totalFiles; }, getTotalSize: function () { var fm = self.fileManager; if (fm.totalSize) { return fm.totalSize; } fm.totalSize = 0; $.each(self.getFileStack(), function (id, f) { var size = parseFloat(f.size); fm.totalSize += isNaN(size) ? 0 : size; }); return fm.totalSize; }, add: function (file, id) { if (!id) { id = self.fileManager.getId(file); } if (!id) { return; } self.fileManager.stack[id] = { file: file, name: $h.getFileName(file), relativePath: $h.getFileRelativePath(file), size: file.size, nameFmt: self._getFileName(file, ""), sizeFmt: self._getSize(file.size), }; }, remove: function ($thumb) { var id = self._getThumbFileId($thumb); self.fileManager.removeFile(id); }, removeFile: function (id) { var fm = self.fileManager; if (!id) { return; } delete fm.stack[id]; delete fm.loadedImages[id]; }, move: function (idFrom, idTo) { var result = {}, stack = self.fileManager.stack; if ((!idFrom && !idTo) || idFrom === idTo) { return; } $.each(stack, function (k, v) { if (k !== idFrom) { result[k] = v; } if (k === idTo) { result[idFrom] = stack[idFrom]; } }); self.fileManager.stack = result; }, list: function () { var files = []; $.each(self.getFileStack(), function (k, v) { if (v && v.file) { files.push(v.file); } }); return files; }, isPending: function (id) { return $.inArray(id, self.fileManager.filesProcessed) === -1 && self.fileManager.exists(id); }, isProcessed: function () { var filesProcessed = true, fm = self.fileManager; $.each(self.getFileStack(), function (id) { if (fm.isPending(id)) { filesProcessed = false; } }); return filesProcessed; }, clear: function () { var fm = self.fileManager; self.isDuplicateError = false; self.isPersistentError = false; fm.totalFiles = null; fm.totalSize = null; fm.uploadedSize = 0; fm.stack = {}; fm.errors = []; fm.filesProcessed = []; fm.stats = {}; fm.bpsLog = []; fm.bps = 0; fm.clearImages(); }, clearImages: function () { self.fileManager.loadedImages = {}; self.fileManager.totalImages = 0; }, addImage: function (id, config) { self.fileManager.loadedImages[id] = config; }, removeImage: function (id) { delete self.fileManager.loadedImages[id]; }, getImageIdList: function () { return $h.getObjectKeys(self.fileManager.loadedImages); }, getImageCount: function () { return self.fileManager.getImageIdList().length; }, getId: function (file) { return self._getFileId(file); }, getIndex: function (id) { return self.fileManager.getIdList().indexOf(id); }, getThumb: function (id) { var $thumb = null; self._getThumbs().each(function () { var $t = $(this); if (self._getThumbFileId($t) === id) { $thumb = $t; } }); return $thumb; }, getThumbIndex: function ($thumb) { var id = self._getThumbFileId($thumb); return self.fileManager.getIndex(id); }, getIdList: function () { return $h.getObjectKeys(self.fileManager.stack); }, getFile: function (id) { return self.fileManager.stack[id] || null; }, getFileName: function (id, fmt) { var file = self.fileManager.getFile(id); if (!file) { return ""; } return fmt ? file.nameFmt || "" : file.name || ""; }, getFirstFile: function () { var ids = self.fileManager.getIdList(), id = ids && ids.length ? ids[0] : null; return self.fileManager.getFile(id); }, setFile: function (id, file) { if (self.fileManager.getFile(id)) { self.fileManager.stack[id].file = file; } else { self.fileManager.add(file, id); } }, setProcessed: function (id) { self.fileManager.filesProcessed.push(id); }, getProgress: function () { var total = self.fileManager.total(), filesProcessed = self.fileManager.filesProcessed.length; if (!total) { return 0; } return Math.ceil((filesProcessed / total) * 100); }, setProgress: function (id, pct) { var f = self.fileManager.getFile(id); if (!isNaN(pct) && f) { f.progress = pct; } }, }; }, _setUploadData: function (fd, config) { var self = this; $.each(config, function (key, value) { var param = self.uploadParamNames[key] || key; if ($h.isArray(value)) { fd.append(param, value[0], value[1]); } else { fd.append(param, value); } }); }, _initResumableUpload: function () { var self = this, opts = self.resumableUploadOptions, logs = $h.logMessages, rm, fm = self.fileManager; if (!self.enableResumableUpload) { return; } if (opts.fallback !== false && typeof opts.fallback !== "function") { opts.fallback = function (s) { s._log(logs.noResumableSupport); s.enableResumableUpload = false; }; } if (!$h.hasResumableUploadSupport() && opts.fallback !== false) { opts.fallback(self); return; } if (!self.uploadUrl && self.enableResumableUpload) { self._log(logs.noUploadUrl); self.enableResumableUpload = false; return; } opts.chunkSize = parseFloat(opts.chunkSize); if (opts.chunkSize <= 0 || isNaN(opts.chunkSize)) { self._log(logs.invalidChunkSize, { chunkSize: opts.chunkSize }); self.enableResumableUpload = false; return; } rm = self.resumableManager = { init: function (id, f, index) { rm.logs = []; rm.stack = []; rm.error = ""; rm.id = id; rm.file = f.file; rm.fileName = f.name; rm.fileIndex = index; rm.completed = false; rm.lastProgress = 0; if (self.showPreview) { rm.$thumb = fm.getThumb(id) || null; rm.$progress = rm.$btnDelete = null; if (rm.$thumb && rm.$thumb.length) { rm.$progress = rm.$thumb.find(".file-thumb-progress"); rm.$btnDelete = rm.$thumb.find(".kv-file-remove"); } } rm.chunkSize = opts.chunkSize * self.bytesToKB; rm.chunkCount = rm.getTotalChunks(); }, setAjaxError: function (jqXHR, textStatus, errorThrown, isTest) { if (jqXHR.responseJSON && jqXHR.responseJSON.error) { errorThrown = jqXHR.responseJSON.error.toString(); } if (!isTest) { rm.error = errorThrown; } if (opts.showErrorLog) { self._log(logs.ajaxError, { status: jqXHR.status, error: errorThrown, text: jqXHR.responseText || "", }); } }, reset: function () { rm.stack = []; rm.chunksProcessed = {}; }, setProcessed: function (status) { var id = rm.id, msg, $thumb = rm.$thumb, $prog = rm.$progress, hasThumb = $thumb && $thumb.length, params = { id: hasThumb ? $thumb.attr("id") : "", index: fm.getIndex(id), fileId: id }, tokens, skipErrorsAndProceed = self.resumableUploadOptions.skipErrorsAndProceed; rm.completed = true; rm.lastProgress = 0; if (hasThumb) { $thumb.removeClass("file-uploading"); } if (status === "success") { fm.uploadedSize += rm.file.size; if (self.showPreview) { self._setProgress(101, $prog); self._setThumbStatus($thumb, "Success"); self._initUploadSuccess(rm.chunksProcessed[id].data, $thumb); } fm.removeFile(id); delete rm.chunksProcessed[id]; self._raise("fileuploaded", [params.id, params.index, params.fileId]); if (fm.isProcessed()) { self._setProgress(101); } } else { if (status !== "cancel") { if (self.showPreview) { self._setThumbStatus($thumb, "Error"); self._setPreviewError($thumb, true); self._setProgress(101, $prog, self.msgProgressError); self._setProgress(101, self.$progress, self.msgProgressError); self.cancelling = !skipErrorsAndProceed; } if (!self.$errorContainer.find('li[data-file-id="' + params.fileId + '"]').length) { tokens = { file: rm.fileName, max: opts.maxRetries, error: rm.error }; msg = self.msgResumableUploadRetriesExceeded.setTokens(tokens); $.extend(params, tokens); self._showFileError(msg, params, "filemaxretries"); if (skipErrorsAndProceed) { fm.removeFile(id); delete rm.chunksProcessed[id]; if (fm.isProcessed()) { self._setProgress(101); } } } } } if (fm.isProcessed()) { rm.reset(); } }, check: function () { var status = true; $.each(rm.logs, function (index, value) { if (!value) { status = false; return false; } }); }, processedResumables: function () { var logs = rm.logs, i, count = 0; if (!logs || !logs.length) { return 0; } for (i = 0; i < logs.length; i++) { if (logs[i] === true) { count++; } } return count; }, getUploadedSize: function () { var size = rm.processedResumables() * rm.chunkSize; return size > rm.file.size ? rm.file.size : size; }, getTotalChunks: function () { var chunkSize = parseFloat(rm.chunkSize); if (!isNaN(chunkSize) && chunkSize > 0) { return Math.ceil(rm.file.size / chunkSize); } return 0; }, getProgress: function () { var chunksProcessed = rm.processedResumables(), total = rm.chunkCount; if (total === 0) { return 0; } return Math.ceil((chunksProcessed / total) * 100); }, checkAborted: function (intervalId) { if (self._isAborted()) { clearInterval(intervalId); self.unlock(); } }, upload: function () { var ids = fm.getIdList(), flag = "new", intervalId; intervalId = setInterval(function () { var id; rm.checkAborted(intervalId); if (flag === "new") { self.lock(); flag = "processing"; id = ids.shift(); fm.initStats(id); if (fm.stack[id]) { rm.init(id, fm.stack[id], fm.getIndex(id)); rm.processUpload(); } } if (!fm.isPending(id) && rm.completed) { flag = "new"; } if (fm.isProcessed()) { var $initThumbs = self.$preview.find(".file-preview-initial"); if ($initThumbs.length) { $h.addCss($initThumbs, $h.SORT_CSS); self._initSortable(); } clearInterval(intervalId); self._clearFileInput(); self.unlock(); setTimeout(function () { var data = self.previewCache.data; if (data) { self.initialPreview = data.content; self.initialPreviewConfig = data.config; self.initialPreviewThumbTags = data.tags; } self._raise("filebatchuploadcomplete", [ self.initialPreview, self.initialPreviewConfig, self.initialPreviewThumbTags, self._getExtraData(), ]); }, self.processDelay); } }, self.processDelay); }, uploadResumable: function () { var i, pool, tm = self.taskManager, total = rm.chunkCount; pool = tm.addPool(rm.id); for (i = 0; i < total; i++) { rm.logs[i] = !!(rm.chunksProcessed[rm.id] && rm.chunksProcessed[rm.id][i]); if (!rm.logs[i]) { rm.pushAjax(i, 0); } } pool .run(opts.maxThreads) .done(function () { rm.setProcessed("success"); }) .fail(function () { rm.setProcessed(pool.cancelled ? "cancel" : "error"); }); }, processUpload: function () { var fd, f, id = rm.id, fnBefore, fnSuccess, fnError, fnComplete, outData; if (!opts.testUrl) { rm.uploadResumable(); return; } fd = new FormData(); f = fm.stack[id]; self._setUploadData(fd, { fileId: id, fileName: f.fileName, fileSize: f.size, fileRelativePath: f.relativePath, chunkSize: rm.chunkSize, chunkCount: rm.chunkCount, }); fnBefore = function (jqXHR) { outData = self._getOutData(fd, jqXHR); self._raise("filetestbeforesend", [id, fm, rm, outData]); }; fnSuccess = function (data, textStatus, jqXHR) { outData = self._getOutData(fd, jqXHR, data); var pNames = self.uploadParamNames, chunksUploaded = pNames.chunksUploaded || "chunksUploaded", params = [id, fm, rm, outData]; if (!data[chunksUploaded] || !$h.isArray(data[chunksUploaded])) { self._raise("filetesterror", params); } else { if (!rm.chunksProcessed[id]) { rm.chunksProcessed[id] = {}; } $.each(data[chunksUploaded], function (key, index) { rm.logs[index] = true; rm.chunksProcessed[id][index] = true; }); rm.chunksProcessed[id].data = data; self._raise("filetestsuccess", params); } rm.uploadResumable(); }; fnError = function (jqXHR, textStatus, errorThrown) { outData = self._getOutData(fd, jqXHR); self._raise("filetestajaxerror", [id, fm, rm, outData]); rm.setAjaxError(jqXHR, textStatus, errorThrown, true); rm.uploadResumable(); }; fnComplete = function () { self._raise("filetestcomplete", [id, fm, rm, self._getOutData(fd)]); }; self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, fd, id, rm.fileIndex, opts.testUrl); }, pushAjax: function (index, retry) { var tm = self.taskManager, pool = tm.getPool(rm.id); pool.addTask(pool.size() + 1, function (deferrer) { // use fifo chunk stack var arr = rm.stack.shift(), index; index = arr[0]; if (!rm.chunksProcessed[rm.id] || !rm.chunksProcessed[rm.id][index]) { rm.sendAjax(index, arr[1], deferrer); } else { self._log(logs.chunkQueueError, { index: index }); } }); rm.stack.push([index, retry]); }, sendAjax: function (index, retry, deferrer) { var f, chunkSize = rm.chunkSize, id = rm.id, file = rm.file, $thumb = rm.$thumb, msgs = $h.logMessages, $btnDelete = rm.$btnDelete, logError = function (msg, tokens) { if (tokens) { msg = msg.setTokens(tokens); } msg = msgs.resumableRequestError.setTokens({ msg: msg }); self._log(msg); deferrer.reject(msg); }; if (rm.chunksProcessed[id] && rm.chunksProcessed[id][index]) { return; } if (retry > opts.maxRetries) { logError(msgs.resumableMaxRetriesReached, { n: opts.maxRetries }); rm.setProcessed("error"); return; } var fd, outData, fnBefore, fnSuccess, fnError, fnComplete, slice = file.slice ? "slice" : file.mozSlice ? "mozSlice" : file.webkitSlice ? "webkitSlice" : "slice", blob = file[slice](chunkSize * index, chunkSize * (index + 1)); fd = new FormData(); f = fm.stack[id]; self._setUploadData(fd, { chunkCount: rm.chunkCount, chunkIndex: index, chunkSize: chunkSize, chunkSizeStart: chunkSize * index, fileBlob: [blob, rm.fileName], fileId: id, fileName: rm.fileName, fileRelativePath: f.relativePath, fileSize: file.size, retryCount: retry, }); if (rm.$progress && rm.$progress.length) { rm.$progress.show(); } fnBefore = function (jqXHR) { outData = self._getOutData(fd, jqXHR); if (self.showPreview) { if (!$thumb.hasClass("file-preview-success")) { self._setThumbStatus($thumb, "Loading"); $h.addCss($thumb, "file-uploading"); } $btnDelete.attr("disabled", true); } self._raise("filechunkbeforesend", [id, index, retry, fm, rm, outData]); }; fnSuccess = function (data, textStatus, jqXHR) { if (self._isAborted()) { logError(msgs.resumableAborting); return; } outData = self._getOutData(fd, jqXHR, data); var paramNames = self.uploadParamNames, chunkIndex = paramNames.chunkIndex || "chunkIndex", params = [id, index, retry, fm, rm, outData]; if (data.error) { if (opts.showErrorLog) { self._log(logs.retryStatus, { retry: retry + 1, filename: rm.fileName, chunk: index, }); } self._raise("filechunkerror", params); rm.pushAjax(index, retry + 1); rm.error = data.error; logError(data.error); } else { rm.logs[data[chunkIndex]] = true; if (!rm.chunksProcessed[id]) { rm.chunksProcessed[id] = {}; } rm.chunksProcessed[id][data[chunkIndex]] = true; rm.chunksProcessed[id].data = data; deferrer.resolve.call(null, data); self._raise("filechunksuccess", params); rm.check(); } }; fnError = function (jqXHR, textStatus, errorThrown) { if (self._isAborted()) { logError(msgs.resumableAborting); return; } outData = self._getOutData(fd, jqXHR); rm.setAjaxError(jqXHR, textStatus, errorThrown); self._raise("filechunkajaxerror", [id, index, retry, fm, rm, outData]); rm.pushAjax(index, retry + 1); // push another task logError(msgs.resumableRetryError, { n: retry - 1 }); // resolve the current task }; fnComplete = function () { if (!self._isAborted()) { self._raise("filechunkcomplete", [id, index, retry, fm, rm, self._getOutData(fd)]); } }; self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, fd, id, rm.fileIndex); }, }; rm.reset(); }, _initTemplateDefaults: function () { var self = this, tMain1, tMain2, tPreview, tFileIcon, tClose, tCaption, tBtnDefault, tBtnLink, tBtnBrowse, tModalMain, tModal, tProgress, tSize, tFooter, tActions, tActionDelete, tActionUpload, tActionDownload, tActionZoom, tActionDrag, tIndicator, tTagBef, tTagBef1, tTagBef2, tTagAft, tGeneric, tHtml, tImage, tText, tOffice, tGdocs, tVideo, tAudio, tFlash, tObject, tPdf, tOther, tStyle, tZoomCache, vDefaultDim, tActionRotate, tStats, tModalLabel, tDescClose, renderObject = function (type, mime) { return ( '\n" + $h.DEFAULT_PREVIEW + "\n\n" ); }, defBtnCss1 = "btn btn-sm btn-kv " + $h.defaultButtonCss(); tMain1 = "{preview}\n" + '
\n' + '
\n' + '
\n' + ' {caption}\n\n' + ($h.isBs(5) ? "" : '
\n') + " {remove}\n" + " {cancel}\n" + " {pause}\n" + " {upload}\n" + " {browse}\n" + ($h.isBs(5) ? "" : "
\n") + "
"; ("
"); tMain2 = '{preview}\n
\n
\n' + '{remove}\n{cancel}\n{upload}\n{browse}\n'; tPreview = '
\n' + " {close}" + '
\n' + '
\n' + "
\n" + '
\n' + '
\n' + "
\n" + "
"; tClose = $h.closeButton("fileinput-remove"); tFileIcon = ''; // noinspection HtmlUnknownAttribute tCaption = '\n'; //noinspection HtmlUnknownAttribute tBtnDefault = '"; //noinspection HtmlUnknownTarget,HtmlUnknownAttribute tBtnLink = '{icon} {label}'; //noinspection HtmlUnknownAttribute tBtnBrowse = '
{icon} {label}
'; tModalLabel = $h.MODAL_ID + "Label"; tModalMain = ''; tModal = '\n"; tDescClose = ''; tProgress = '
\n' + '
\n' + " {status}\n" + "
\n" + "
{stats}"; tStats = '
' + '{pendingTime} ' + '{uploadSpeed}' + "
"; tSize = " ({sizeText})"; tFooter = '"; tActions = '
\n' + ' \n" + "
\n" + "{drag}\n" + '
'; //noinspection HtmlUnknownAttribute tActionDelete = '\n'; tActionUpload = '"; tActionRotate = '"; tActionDownload = '{downloadIcon}'; tActionZoom = ''; tActionDrag = '{dragIcon}'; tIndicator = '
{indicator}
'; tTagBef = '
\n'; tTagBef2 = tTagBef + ' title="{caption}">
\n'; tTagAft = "
{footer}\n{zoomCache}
\n"; tGeneric = "{content}\n"; tStyle = " {style}"; tHtml = renderObject("html", "text/html"); tText = renderObject("text", "text/plain;charset=UTF-8"); tPdf = renderObject("pdf", "application/pdf"); tImage = '{alt}\n"; tOffice = '"; tGdocs = '"; tVideo = '\n"; tAudio = '\n"; tFlash = '\n"; tObject = '\n" + '\n' + $h.OBJECT_PARAMS + " " + $h.DEFAULT_PREVIEW + "\n\n"; tOther = '
\n" + $h.DEFAULT_PREVIEW + "\n
\n"; tZoomCache = '
{zoomContent}
'; vDefaultDim = { width: "100%", height: "100%", "min-height": "480px" }; if (self._isPdfRendered()) { tPdf = self.pdfRendererTemplate.replace("{renderer}", self._encodeURI(self.pdfRendererUrl)); } self.defaults = { layoutTemplates: { main1: tMain1, main2: tMain2, preview: tPreview, close: tClose, fileIcon: tFileIcon, caption: tCaption, modalMain: tModalMain, modal: tModal, descriptionClose: tDescClose, progress: tProgress, stats: tStats, size: tSize, footer: tFooter, indicator: tIndicator, actions: tActions, actionDelete: tActionDelete, actionRotate: tActionRotate, actionUpload: tActionUpload, actionDownload: tActionDownload, actionZoom: tActionZoom, actionDrag: tActionDrag, btnDefault: tBtnDefault, btnLink: tBtnLink, btnBrowse: tBtnBrowse, zoomCache: tZoomCache, }, previewMarkupTags: { tagBefore1: tTagBef1, tagBefore2: tTagBef2, tagAfter: tTagAft, }, previewContentTemplates: { generic: tGeneric, html: tHtml, image: tImage, text: tText, office: tOffice, gdocs: tGdocs, video: tVideo, audio: tAudio, flash: tFlash, object: tObject, pdf: tPdf, other: tOther, }, allowedPreviewTypes: ["image", "html", "text", "video", "audio", "flash", "pdf", "object"], previewTemplates: {}, previewSettings: { image: { width: "auto", height: "auto", "max-width": "100%", "max-height": "100%" }, html: { width: "213px", height: "160px" }, text: { width: "213px", height: "160px" }, office: { width: "213px", height: "160px" }, gdocs: { width: "213px", height: "160px" }, video: { width: "213px", height: "160px" }, audio: { width: "100%", height: "30px" }, flash: { width: "213px", height: "160px" }, object: { width: "213px", height: "160px" }, pdf: { width: "100%", height: "160px", position: "relative" }, other: { width: "213px", height: "160px" }, }, previewSettingsSmall: { image: { width: "auto", height: "auto", "max-width": "100%", "max-height": "100%" }, html: { width: "100%", height: "160px" }, text: { width: "100%", height: "160px" }, office: { width: "100%", height: "160px" }, gdocs: { width: "100%", height: "160px" }, video: { width: "100%", height: "auto" }, audio: { width: "100%", height: "30px" }, flash: { width: "100%", height: "auto" }, object: { width: "100%", height: "auto" }, pdf: { width: "100%", height: "160px" }, other: { width: "100%", height: "160px" }, }, previewZoomSettings: { image: { width: "auto", height: "auto", "max-width": "100%", "max-height": "100%" }, html: vDefaultDim, text: vDefaultDim, office: { width: "100%", height: "100%", "max-width": "100%", "min-height": "480px" }, gdocs: { width: "100%", height: "100%", "max-width": "100%", "min-height": "480px" }, video: { width: "auto", height: "100%", "max-width": "100%" }, audio: { width: "100%", height: "30px" }, flash: { width: "auto", height: "480px" }, object: { width: "auto", height: "100%", "max-width": "100%", "min-height": "480px" }, pdf: vDefaultDim, other: { width: "auto", height: "100%", "min-height": "480px" }, }, mimeTypeAliases: { "video/quicktime": "video/mp4", }, fileTypeSettings: { image: function (vType, vName) { return ( ($h.compare(vType, "image.*") && !$h.compare(vType, /(tiff?|wmf)$/i)) || $h.compare(vName, /\.(gif|png|jpe?g)$/i) ); }, html: function (vType, vName) { return $h.compare(vType, "text/html") || $h.compare(vName, /\.(htm|html)$/i); }, office: function (vType, vName) { return ( $h.compare(vType, /(word|excel|powerpoint|office)$/i) || $h.compare(vName, /\.(docx?|xlsx?|pptx?|pps|potx?)$/i) ); }, gdocs: function (vType, vName) { return ( $h.compare(vType, /(word|excel|powerpoint|office|iwork-pages|tiff?)$/i) || $h.compare(vName, /\.(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i) ); }, text: function (vType, vName) { return ( $h.compare(vType, "text.*") || $h.compare(vName, /\.(xml|javascript)$/i) || $h.compare(vName, /\.(txt|md|nfo|ini|json|php|js|css)$/i) ); }, video: function (vType, vName) { return ( $h.compare(vType, "video.*") && ($h.compare(vType, /(ogg|mp4|mp?g|mov|webm|3gp)$/i) || $h.compare(vName, /\.(og?|mp4|webm|mp?g|mov|3gp)$/i)) ); }, audio: function (vType, vName) { return ( $h.compare(vType, "audio.*") && ($h.compare(vName, /(ogg|mp3|mp?g|wav)$/i) || $h.compare(vName, /\.(og?|mp3|mp?g|wav)$/i)) ); }, flash: function (vType, vName) { return $h.compare(vType, "application/x-shockwave-flash", true) || $h.compare(vName, /\.(swf)$/i); }, pdf: function (vType, vName) { return $h.compare(vType, "application/pdf", true) || $h.compare(vName, /\.(pdf)$/i); }, object: function () { return true; }, other: function () { return true; }, }, fileActionSettings: { showRemove: true, showUpload: true, showDownload: true, showZoom: true, showDrag: true, showRotate: false, removeIcon: '', removeClass: defBtnCss1, removeErrorClass: "btn btn-sm btn-kv btn-danger", removeTitle: "Remove file", uploadIcon: '', uploadClass: defBtnCss1, uploadTitle: "Upload file", uploadRetryIcon: '', uploadRetryTitle: "Retry upload", downloadIcon: '', downloadClass: defBtnCss1, downloadTitle: "Download file", rotateIcon: '', rotateClass: defBtnCss1, rotateTitle: "Rotate 90 deg. clockwise", zoomIcon: '', zoomClass: defBtnCss1, zoomTitle: "View Details", dragIcon: '', dragClass: "text-primary", dragTitle: "Move / Rearrange", dragSettings: {}, indicatorNew: '', indicatorSuccess: '', indicatorError: '', indicatorLoading: '', indicatorPaused: '', indicatorNewTitle: "Not uploaded yet", indicatorSuccessTitle: "Uploaded", indicatorErrorTitle: "Upload Error", indicatorLoadingTitle: "Uploading …", indicatorPausedTitle: "Upload Paused", }, }; $.each(self.defaults, function (key, setting) { if (key === "allowedPreviewTypes") { if (self.allowedPreviewTypes === undefined) { self.allowedPreviewTypes = setting; } return; } self[key] = $.extend(true, {}, setting, self[key]); }); self._initPreviewTemplates(); }, _initPreviewTemplates: function () { var self = this, tags = self.previewMarkupTags, tagBef, tagAft = tags.tagAfter; $.each(self.previewContentTemplates, function (key, value) { if ($h.isEmpty(self.previewTemplates[key])) { tagBef = tags.tagBefore2; if (key === "generic" || key === "image") { tagBef = tags.tagBefore1; } if (self._isPdfRendered() && key === "pdf") { tagBef = tagBef.replace("kv-file-content", "kv-file-content kv-pdf-rendered"); } self.previewTemplates[key] = tagBef + value + tagAft; } }); }, _initPreviewCache: function () { var self = this; self.previewCache = { data: {}, init: function () { var content = self.initialPreview; if (content.length > 0 && !$h.isArray(content)) { content = content.split(self.initialPreviewDelimiter); } self.previewCache.data = { content: content, config: self.initialPreviewConfig, tags: self.initialPreviewThumbTags, }; }, count: function (skipNull) { if (!self.previewCache.data || !self.previewCache.data.content) { return 0; } if (skipNull) { var chk = self.previewCache.data.content.filter(function (n) { return n !== null; }); return chk.length; } return self.previewCache.data.content.length; }, get: function (i, isDisabled) { var ind = $h.INIT_FLAG + i, data = self.previewCache.data, config = data.config[i], content = data.content[i], out, $tmp, cat, ftr, fname, ftype, frameClass, asData = $h.ifSet("previewAsData", config, self.initialPreviewAsData), a = config ? { title: config.title || null, alt: config.alt || null } : { title: null, alt: null }, parseTemplate = function (cat, dat, fname, ftype, ftr, ind, fclass, t) { var fc = " file-preview-initial " + $h.SORT_CSS + (fclass ? " " + fclass : ""), id = self.previewInitId + "-" + ind, fileId = (config && config.fileId) || id; /** @namespace config.zoomData */ return self._generatePreviewTemplate( cat, dat, fname, ftype, id, fileId, false, null, null, fc, ftr, ind, t, a, (config && config.zoomData) || dat ); }; if (!content || !content.length) { return ""; } isDisabled = isDisabled === undefined ? true : isDisabled; cat = $h.ifSet("type", config, self.initialPreviewFileType || "generic"); fname = $h.ifSet("filename", config, $h.ifSet("caption", config)); ftype = $h.ifSet("filetype", config, cat); ftr = self.previewCache.footer(i, isDisabled, (config && config.size) || null); frameClass = $h.ifSet("frameClass", config); if (asData) { out = parseTemplate(cat, content, fname, ftype, ftr, ind, frameClass); } else { out = parseTemplate("generic", content, fname, ftype, ftr, ind, frameClass, cat).setTokens({ content: data.content[i], }); } if (data.tags.length && data.tags[i]) { out = $h.replaceTags(out, data.tags[i]); } /** @namespace config.frameAttr */ if (!$h.isEmpty(config) && !$h.isEmpty(config.frameAttr)) { $tmp = $h.createDiv(); $h.setHtml($tmp, out); $tmp.find(".file-preview-initial").attr(config.frameAttr); out = $tmp.html(); $tmp.remove(); } return out; }, clean: function (data) { data.content = $h.cleanArray(data.content); data.config = $h.cleanArray(data.config); data.tags = $h.cleanArray(data.tags); self.previewCache.data = data; }, add: function (content, config, tags, append) { var data = self.previewCache.data, index; if (!content || !content.length) { return 0; } index = content.length - 1; if (!$h.isArray(content)) { content = content.split(self.initialPreviewDelimiter); } if (append && data.content) { index = data.content.push(content[0]) - 1; data.config[index] = config; data.tags[index] = tags; } else { data.content = content; data.config = config; data.tags = tags; } self.previewCache.clean(data); return index; }, set: function (content, config, tags, append) { var data = self.previewCache.data, i, chk; if (!content || !content.length) { return; } if (!$h.isArray(content)) { content = content.split(self.initialPreviewDelimiter); } chk = content.filter(function (n) { return n !== null; }); if (!chk.length) { return; } if (data.content === undefined) { data.content = []; } if (data.config === undefined) { data.config = []; } if (data.tags === undefined) { data.tags = []; } if (append) { for (i = 0; i < content.length; i++) { if (content[i]) { data.content.push(content[i]); } } for (i = 0; i < config.length; i++) { if (config[i]) { data.config.push(config[i]); } } for (i = 0; i < tags.length; i++) { if (tags[i]) { data.tags.push(tags[i]); } } } else { data.content = content; data.config = config; data.tags = tags; } self.previewCache.clean(data); }, unset: function (index) { var chk = self.previewCache.count(), rev = self.reversePreviewOrder; if (!chk) { return; } if (chk === 1) { self.previewCache.data.content = []; self.previewCache.data.config = []; self.previewCache.data.tags = []; self.initialPreview = []; self.initialPreviewConfig = []; self.initialPreviewThumbTags = []; return; } self.previewCache.data.content = $h.spliceArray(self.previewCache.data.content, index, rev); self.previewCache.data.config = $h.spliceArray(self.previewCache.data.config, index, rev); self.previewCache.data.tags = $h.spliceArray(self.previewCache.data.tags, index, rev); var data = $.extend(true, {}, self.previewCache.data); self.previewCache.clean(data); }, out: function () { var html = "", caption, len = self.previewCache.count(), i, content; if (len === 0) { return { content: "", caption: "" }; } for (i = 0; i < len; i++) { content = self.previewCache.get(i); html = self.reversePreviewOrder ? content + html : html + content; } caption = self._getMsgSelected(len); return { content: html, caption: caption }; }, footer: function (i, isDisabled, size) { var data = self.previewCache.data || {}; if ($h.isEmpty(data.content)) { return ""; } if ($h.isEmpty(data.config) || $h.isEmpty(data.config[i])) { data.config[i] = {}; } isDisabled = isDisabled === undefined ? true : isDisabled; var config = data.config[i], caption = $h.ifSet("caption", config), a, width = $h.ifSet("width", config, "auto"), url = $h.ifSet("url", config, false), key = $h.ifSet("key", config, null), fileId = $h.ifSet("fileId", config, null), fs = self.fileActionSettings, initPreviewShowDel = self.initialPreviewShowDelete || false, downloadInitialUrl = !self.initialPreviewDownloadUrl ? "" : self.initialPreviewDownloadUrl + "?key=" + key + (fileId ? "&fileId=" + fileId : ""), dUrl = config.downloadUrl || downloadInitialUrl, dFil = config.filename || config.caption || "", initPreviewShowDwl = !!dUrl, sDel = $h.ifSet("showRemove", config, initPreviewShowDel), sRot = $h.ifSet("showRotate", config, $h.ifSet("showRotate", fs, true)), sDwl = $h.ifSet("showDownload", config, $h.ifSet("showDownload", fs, initPreviewShowDwl)), sZm = $h.ifSet("showZoom", config, $h.ifSet("showZoom", fs, true)), sDrg = $h.ifSet("showDrag", config, $h.ifSet("showDrag", fs, true)), dis = url === false && isDisabled; sDwl = sDwl && config.downloadUrl !== false && !!dUrl; a = self._renderFileActions(config, false, sDwl, sDel, sRot, sZm, sDrg, dis, url, key, true, dUrl, dFil); return self._getLayoutTemplate("footer").setTokens({ progress: self._renderThumbProgress(), actions: a, caption: caption, size: self._getSize(size), width: width, indicator: "", }); }, }; self.previewCache.init(); }, _isPdfRendered: function () { var self = this, useLib = self.usePdfRenderer, flag = typeof useLib === "function" ? useLib() : !!useLib; return flag && self.pdfRendererUrl; }, _handler: function ($el, event, callback) { var self = this, ns = self.namespace, ev = event.split(" ").join(ns + " ") + ns; if (!$el || !$el.length) { return; } $el.off(ev).on(ev, callback); }, _encodeURI: function (vUrl) { var self = this; return self.encodeUrl ? encodeURI(vUrl) : vUrl; }, _log: function (msg, tokens) { var self = this, id = self.$element.attr("id"); if (!self.showConsoleLogs) { return; } if (id) { msg = '"' + id + '": ' + msg; } msg = "bootstrap-fileinput: " + msg; if (typeof tokens === "object") { msg = msg.setTokens(tokens); } if (window.console && typeof window.console.log !== "undefined") { window.console.log(msg); } else { window.alert(msg); } }, _validate: function () { var self = this, status = self.$element.attr("type") === "file"; if (!status) { self._log($h.logMessages.badInputType); } return status; }, _errorsExist: function () { var self = this, $err, $errList = self.$errorContainer.find("li"); if ($errList.length) { return true; } $err = $h.createDiv(); $h.setHtml($err, self.$errorContainer.html()); $err.find(".kv-error-close").remove(); $err.find("ul").remove(); return !!$h.trim($err.text()).length; }, _errorHandler: function (evt, caption) { var self = this, err = evt.target.error, showError = function (msg) { self._showError(msg.replace("{name}", caption)); }; /** @namespace err.NOT_FOUND_ERR */ /** @namespace err.SECURITY_ERR */ /** @namespace err.NOT_READABLE_ERR */ if (err.code === err.NOT_FOUND_ERR) { showError(self.msgFileNotFound); } else { if (err.code === err.SECURITY_ERR) { showError(self.msgFileSecured); } else { if (err.code === err.NOT_READABLE_ERR) { showError(self.msgFileNotReadable); } else { if (err.code === err.ABORT_ERR) { showError(self.msgFilePreviewAborted); } else { showError(self.msgFilePreviewError); } } } } }, _addError: function (msg) { var self = this, $error = self.$errorContainer; if (msg && $error.length) { $h.setHtml($error, self.errorCloseButton + msg); self._handler($error.find(".kv-error-close"), "click", function () { setTimeout(function () { if (self.showPreview && !self.getFrames().length) { self.clear(); } $error.fadeOut("slow"); }, self.processDelay); }); } }, _setValidationError: function (css) { var self = this; css = (css ? css + " " : "") + "has-error"; self.$container.removeClass(css).addClass("has-error"); $h.addCss(self.$caption, "is-invalid"); }, _resetErrors: function (fade) { var self = this, $error = self.$errorContainer, history = self.resumableUploadOptions.retainErrorHistory; if (self.isPersistentError || (self.enableResumableUpload && history && !self.clearInput)) { return; } self.clearInput = false; self.isError = false; self.$container.removeClass("has-error"); self.$caption.removeClass("is-invalid is-valid file-processing"); $error.html(""); if (fade) { $error.fadeOut("slow"); } else { $error.hide(); } }, _showFolderError: function (folders) { var self = this, $error = self.$errorContainer, msg; if (!folders) { return; } if (!self.isAjaxUpload) { self._clearFileInput(); } msg = self.msgFoldersNotAllowed.replace("{n}", folders); self._addError(msg); self._setValidationError(); $error.fadeIn(self.fadeDelay); self._raise("filefoldererror", [folders, msg]); }, showUserError: function (msg, params, retainErrorHistory) { var self = this, fileName; if (!self.uploadInitiated) { return; } if (!params || !params.fileId) { if (!retainErrorHistory) { self.$errorContainer.html(""); } } else { if (!retainErrorHistory) { self.$errorContainer.find('[data-file-id="' + params.fileId + '"]').remove(); } fileName = self.fileManager.getFileName(params.fileId); if (fileName) { msg = "" + fileName + ": " + msg; } } self._showFileError(msg, params, "fileusererror"); }, _showFileError: function (msg, params, event) { var self = this, $error = self.$errorContainer, ev = event || "fileuploaderror", fId = (params && params.fileId) || "", e = params && params.id ? '
  • ' + msg + "
  • " : "
  • " + msg + "
  • "; if ($error.find("ul").length === 0) { self._addError("
      " + e + "
    "); } else { $error.find("ul").append($h.cspBuffer.stash(e)); $h.cspBuffer.apply($error); } $error.fadeIn(self.fadeDelay); self._raise(ev, [params, msg]); self._setValidationError("file-input-new"); return true; }, _showError: function (msg, params, event) { var self = this, $error = self.$errorContainer, ev = event || "fileerror"; params = params || {}; params.reader = self.reader; self._addError(msg); $error.fadeIn(self.fadeDelay); self._raise(ev, [params, msg]); if (!self.isAjaxUpload) { self._clearFileInput(); } self._setValidationError("file-input-new"); self.$btnUpload.attr("disabled", true); return true; }, _noFilesError: function (params) { var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle, msg = self.msgFilesTooLess.replace("{n}", self.minFileCount).replace("{files}", label), $error = self.$errorContainer; msg = "
  • " + msg + "
  • "; if ($error.find("ul").length === 0) { self._addError("
      " + msg + "
    "); } else { $error.find("ul").append(msg); } self.isError = true; self._updateFileDetails(0); $error.fadeIn(self.fadeDelay); self._raise("fileerror", [params, msg]); self._clearFileInput(); self._setValidationError(); }, _parseError: function (operation, jqXHR, errorThrown, fileName) { /** @namespace jqXHR.responseJSON */ var self = this, errMsg = $h.trim(errorThrown + ""), textPre, errText, text; errText = jqXHR.responseJSON && jqXHR.responseJSON.error ? jqXHR.responseJSON.error.toString() : ""; text = errText ? errText : jqXHR.responseText; if (self.cancelling && self.msgUploadAborted) { errMsg = self.msgUploadAborted; } if (self.showAjaxErrorDetails && text) { if (errText) { errMsg = $h.trim(errText + ""); } else { text = $h.trim(text.replace(/\n\s*\n/g, "\n")); textPre = text.length ? "
    " + text + "
    " : ""; errMsg += errMsg ? textPre : text; } } if (!errMsg) { errMsg = self.msgAjaxError.replace("{operation}", operation); } self.cancelling = false; return fileName ? "" + fileName + ": " + errMsg : errMsg; }, _parseFileType: function (type, name) { var self = this, isValid, vType, cat, i, types = self.allowedPreviewTypes || []; if (type === "application/text-plain") { return "text"; } for (i = 0; i < types.length; i++) { cat = types[i]; isValid = self.fileTypeSettings[cat]; vType = isValid(type, name) ? cat : ""; if (!$h.isEmpty(vType)) { return vType; } } return "other"; }, _getPreviewIcon: function (fname) { var self = this, ext, out = null; if (fname && fname.indexOf(".") > -1) { ext = fname.split(".").pop(); if (self.previewFileIconSettings) { out = self.previewFileIconSettings[ext] || self.previewFileIconSettings[ext.toLowerCase()] || null; } if (self.previewFileExtSettings) { $.each(self.previewFileExtSettings, function (key, func) { if (self.previewFileIconSettings[key] && func(ext)) { out = self.previewFileIconSettings[key]; //noinspection UnnecessaryReturnStatementJS return; } }); } } return out || self.previewFileIcon; }, _parseFilePreviewIcon: function (content, fname) { var self = this, icn = self._getPreviewIcon(fname), out = content; if (out.indexOf("{previewFileIcon}") > -1) { out = out.setTokens({ previewFileIconClass: self.previewFileIconClass, previewFileIcon: icn }); } return out; }, _raise: function (event, params) { var self = this, e = $.Event(event); if (params !== undefined) { self.$element.trigger(e, params); } else { self.$element.trigger(e); } var out = e.result, isAborted = out === false; if (e.isDefaultPrevented() || isAborted) { return false; } if (e.type === "filebatchpreupload" && (out || isAborted)) { self.ajaxAborted = out; return false; } switch (event) { // ignore these events case "filebatchuploadcomplete": case "filebatchuploadsuccess": case "fileuploaded": case "fileclear": case "filecleared": case "filereset": case "fileerror": case "filefoldererror": case "filecustomerror": case "filesuccessremove": break; // receive data response via `filecustomerror` event` default: if (!self.ajaxAborted) { self.ajaxAborted = out; } break; } return true; }, _listenFullScreen: function (isFullScreen) { var self = this, $modal = self.$modal, $btnFull, $btnBord; if (!$modal || !$modal.length) { return; } $btnFull = $modal && $modal.find(".btn-kv-fullscreen"); $btnBord = $modal && $modal.find(".btn-kv-borderless"); if (!$btnFull.length || !$btnBord.length) { return; } $btnFull.removeClass("active").attr("aria-pressed", "false"); $btnBord.removeClass("active").attr("aria-pressed", "false"); if (isFullScreen) { $btnFull.addClass("active").attr("aria-pressed", "true"); } else { $btnBord.addClass("active").attr("aria-pressed", "true"); } if ($modal.hasClass("file-zoom-fullscreen")) { self._maximizeZoomDialog(); } else { if (isFullScreen) { self._maximizeZoomDialog(); } else { $btnBord.removeClass("active").attr("aria-pressed", "false"); } } }, _listen: function () { var self = this, $el = self.$element, $form = self.$form, $cont = self.$container, fullScreenEv; self._handler($el, "click", function (e) { self._initFileSelected(); if ($el.hasClass("file-no-browse")) { if ($el.data("zoneClicked")) { $el.data("zoneClicked", false); } else { e.preventDefault(); } } }); self._handler($el, "change", $.proxy(self._change, self)); self._handler(self.$caption, "paste", $.proxy(self.paste, self)); if (self.showBrowse) { self._handler(self.$btnFile, "click", $.proxy(self._browse, self)); self._handler(self.$btnFile, "keypress", function (e) { var keycode = e.keyCode || e.which; if (keycode === 13) { $el.trigger("click"); self._browse(e); } }); } self._handler($cont.find(".fileinput-remove:not([disabled])"), "click", $.proxy(self.clear, self)); self._handler($cont.find(".fileinput-cancel"), "click", $.proxy(self.cancel, self)); self._handler($cont.find(".fileinput-pause"), "click", $.proxy(self.pause, self)); self._initDragDrop(); self._handler($form, "reset", $.proxy(self.clear, self)); if (!self.isAjaxUpload) { self._handler($form, "submit", $.proxy(self._submitForm, self)); } self._handler(self.$container.find(".fileinput-upload"), "click", $.proxy(self._uploadClick, self)); self._handler($(window), "resize", function () { self._listenFullScreen(screen.width === window.innerWidth && screen.height === window.innerHeight); }); fullScreenEv = "webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange"; self._handler($(document), fullScreenEv, function () { self._listenFullScreen($h.checkFullScreen()); }); self.$caption.on("focus", function () { self.$captionContainer.focus(); }); self._autoFitContent(); self._initClickable(); self._refreshPreview(); }, _autoFitContent: function () { var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, self = this, config = width < 400 ? self.previewSettingsSmall || self.defaults.previewSettingsSmall : self.previewSettings || self.defaults.previewSettings, sel; $.each(config, function (cat, settings) { sel = ".file-preview-frame .file-preview-" + cat; self.$preview.find(sel + ".kv-preview-data," + sel + " .kv-preview-data").css(settings); }); }, _scanDroppedItems: function (item, files, path) { path = path || ""; var self = this, i, dirReader, readDir, errorHandler = function (e) { self._log($h.logMessages.badDroppedFiles); self._log(e); }; if (item.isFile) { item.file(function (file) { if (path) { file.newPath = path + file.name; } files.push(file); }, errorHandler); } else { if (item.isDirectory) { dirReader = item.createReader(); readDir = function () { dirReader.readEntries(function (entries) { if (entries && entries.length > 0) { for (i = 0; i < entries.length; i++) { self._scanDroppedItems(entries[i], files, path + item.name + "/"); } // recursively call readDir() again, since browser can only handle first 100 entries. readDir(); } return null; }, errorHandler); }; readDir(); } } }, _initDragDrop: function () { var self = this, $zone = self.$dropZone; if (self.dropZoneEnabled && self.showPreview) { self._handler($zone, "dragenter dragover", $.proxy(self._zoneDragEnter, self)); self._handler($zone, "dragleave", $.proxy(self._zoneDragLeave, self)); self._handler($zone, "drop", $.proxy(self._zoneDrop, self)); self._handler($(document), "dragenter dragover drop", self._zoneDragDropInit); } }, _zoneDragDropInit: function (e) { e.stopPropagation(); e.preventDefault(); }, _zoneDragEnter: function (e) { var self = this, dt = e.originalEvent.dataTransfer, hasFiles = $.inArray("Files", dt.types) > -1; self._zoneDragDropInit(e); if (self.isDisabled || !hasFiles) { dt.effectAllowed = "none"; dt.dropEffect = "none"; return; } dt.dropEffect = "copy"; if (self._raise("fileDragEnter", { sourceEvent: e, files: dt.types.Files })) { $h.addCss(self.$dropZone, "file-highlighted"); } }, _zoneDragLeave: function (e) { var self = this; self._zoneDragDropInit(e); if (self.isDisabled) { return; } if (self._raise("fileDragLeave", { sourceEvent: e })) { self.$dropZone.removeClass("file-highlighted"); } }, _dropFiles: function (e, files) { var self = this, $el = self.$element; if (!self.isAjaxUpload) { self.changeTriggered = true; $el.get(0).files = files; setTimeout(function () { self.changeTriggered = false; $el.trigger("change" + self.namespace); }, self.processDelay); } else { self._change(e, files); } self.$dropZone.removeClass("file-highlighted"); }, _addFilesFromSystem: function (e, dt, type) { var self = this, files = dt.files, items = dt.items, folders = $h.getDragDropFolders(items); e.preventDefault(); if (self.isDisabled || $h.isEmpty(files) || !files.length) { console.log("No valid copied files found in clipboard for pasting."); return; } if (!self._raise(type, { sourceEvent: e, files: files })) { return; } if (folders > 0) { if (!self.isAjaxUpload) { self._showFolderError(folders); return; } files = []; for (i = 0; i < items.length; i++) { var item = items[i].webkitGetAsEntry(); if (item) { self._scanDroppedItems(item, files); } } setTimeout(function () { self._dropFiles(e, files); }, 500); } else { self._dropFiles(e, files); } }, _zoneDrop: function (e) { /** @namespace e.originalEvent.dataTransfer */ var self = this, i, $el = self.$element, dt = e.originalEvent.dataTransfer; self._addFilesFromSystem(e, dt, "fileDragDrop"); }, _uploadClick: function (e) { var self = this, $btn = self.$container.find(".fileinput-upload"), $form, isEnabled = !$btn.hasClass("disabled") && $h.isEmpty($btn.attr("disabled")); if (e && e.isDefaultPrevented()) { return; } if (!self.isAjaxUpload) { if (isEnabled && $btn.attr("type") !== "submit") { e.preventDefault(); $form = $btn.closest("form"); // downgrade to normal form submit if possible if ($form.length) { $form.trigger("submit"); } } return; } e.preventDefault(); if (isEnabled) { self.upload(); } }, _submitForm: function () { var self = this; return self._isFileSelectionValid() && !self._abort({}); }, _clearPreview: function () { var self = this, $thumbs = self.showUploadedThumbs ? self.getFrames(":not(.file-preview-success)") : self.getFrames(); $thumbs.each(function () { var $thumb = $(this); $thumb.remove(); }); if (!self.getFrames().length || !self.showPreview) { self._resetUpload(); } self._validateDefaultPreview(); }, _initSortable: function () { var self = this, $el = self.$preview, settings, selector = "." + $h.SORT_CSS, $cont, $body = $("body"), $html = $("html"), rev = self.reversePreviewOrder, Sortable = window.Sortable, beginGrab, endGrab; if (!Sortable || $el.find(selector).length === 0) { return; } $cont = $body.length ? $body : $html.length ? $html : self.$container; beginGrab = function () { $cont.addClass("file-grabbing"); }; endGrab = function () { $cont.removeClass("file-grabbing"); }; settings = { handle: ".drag-handle-init", dataIdAttr: "data-fileid", animation: 600, draggable: selector, scroll: false, forceFallback: true, onChoose: beginGrab, onStart: beginGrab, onUnchoose: endGrab, onEnd: endGrab, onSort: function (e) { var oldIndex = e.oldIndex, newIndex = e.newIndex, i = 0, len = self.initialPreviewConfig.length, exceedsLast = len > 0 && newIndex >= len, $item = $(e.item), $first; if (exceedsLast) { newIndex = len - 1; } self.initialPreview = $h.moveArray(self.initialPreview, oldIndex, newIndex, rev); self.initialPreviewConfig = $h.moveArray(self.initialPreviewConfig, oldIndex, newIndex, rev); self.previewCache.init(); self.getFrames(".file-preview-initial").each(function () { $(this).attr("data-fileindex", $h.INIT_FLAG + i); i++; }); if (exceedsLast) { $first = self.getFrames(":not(.file-preview-initial):first"); if ($first.length) { $item.slideUp(function () { $item.insertBefore($first).slideDown(); }); } } self._raise("filesorted", { previewId: $item.attr("id"), oldIndex: oldIndex, newIndex: newIndex, stack: self.initialPreviewConfig, }); }, }; $.extend(true, settings, self.fileActionSettings.dragSettings); if (self.sortable) { self.sortable.destroy(); } self.sortable = Sortable.create($el[0], settings); }, _setPreviewContent: function (content) { var self = this; $h.setHtml(self.$preview, content); self._autoFitContent(); }, _initPreviewImageOrientations: function () { var self = this, i = 0, canOrientImage = self.canOrientImage; if (!self.autoOrientImageInitial && !canOrientImage) { return; } self.getFrames(".file-preview-initial").each(function () { var $thumb = $(this), $img, $zoomImg, id, config = self.initialPreviewConfig[i]; /** @namespace config.exif */ if (config && config.exif && config.exif.Orientation) { id = $thumb.attr("id"); $img = $thumb.find(">.kv-file-content img"); $zoomImg = self._getZoom(id, " >.kv-file-content img"); if (canOrientImage) { $img.css("image-orientation", self.autoOrientImageInitial ? "from-image" : "none"); } else { self.setImageOrientation($img, $zoomImg, config.exif.Orientation, $thumb); } } i++; }); }, _initPreview: function (isInit) { var self = this, cap = self.initialCaption || "", out; if (!self.previewCache.count(true)) { self._clearPreview(); if (isInit) { self._setCaption(cap); } else { self._initCaption(); } return; } out = self.previewCache.out(); cap = isInit && self.initialCaption ? self.initialCaption : out.caption; self._setPreviewContent(out.content); self._setInitThumbAttr(); self._setCaption(cap); self._initSortable(); if (!$h.isEmpty(out.content)) { self.$container.removeClass("file-input-new"); } self._initPreviewImageOrientations(); }, _getZoomButton: function (type) { var self = this, label = self.previewZoomButtonIcons[type], css = self.previewZoomButtonClasses[type], title = ' title="' + (self.previewZoomButtonTitles[type] || "") + '" ', tag = $h.isBs(5) ? "bs-" : "", params = title + (type === "close" ? " data-" + tag + 'dismiss="modal" aria-hidden="true"' : ""); if (type === "fullscreen" || type === "borderless" || type === "toggleheader") { params += ' data-toggle="button" aria-pressed="false"'; } return '"; }, _getModalContent: function () { var self = this; return self._getLayoutTemplate("modal").setTokens({ rtl: self.rtl ? " kv-rtl" : "", zoomFrameClass: self.frameClass, prev: self._getZoomButton("prev"), next: self._getZoomButton("next"), rotate: self._getZoomButton("rotate"), toggleheader: self._getZoomButton("toggleheader"), fullscreen: self._getZoomButton("fullscreen"), borderless: self._getZoomButton("borderless"), close: self._getZoomButton("close"), }); }, _listenModalEvent: function (event) { var self = this, $modal = self.$modal, getParams = function (e) { return { sourceEvent: e, previewId: $modal.data("previewId"), modal: $modal, }; }; $modal.on(event + ".bs.modal", function (e) { if (e.namespace !== "bs.modal") { return; } var $btnFull = $modal.find(".btn-fullscreen"), $btnBord = $modal.find(".btn-borderless"); if ($modal.data("fileinputPluginId") === self.$element.attr("id")) { self._raise("filezoom" + event, getParams(e)); } if (event === "shown") { self._handleRotation($modal, $modal.find(".file-zoom-detail"), $modal.data("angle")); $btnBord.removeClass("active").attr("aria-pressed", "false"); $btnFull.removeClass("active").attr("aria-pressed", "false"); if ($modal.hasClass("file-zoom-fullscreen")) { self._maximizeZoomDialog(); if ($h.checkFullScreen()) { $btnFull.addClass("active").attr("aria-pressed", "true"); } else { $btnBord.addClass("active").attr("aria-pressed", "true"); } } } }); }, _initZoom: function () { var self = this, $dialog, modalMain = self._getLayoutTemplate("modalMain"), modalId = "#" + $h.MODAL_ID; modalMain = self._setTabIndex("modal", modalMain); if (!self.showPreview) { return; } self.$modal = $(modalId); if (!self.$modal || !self.$modal.length) { $dialog = $h.createElement($h.cspBuffer.stash(modalMain)).insertAfter(self.$container); self.$modal = $(modalId).insertBefore($dialog); $h.cspBuffer.apply(self.$modal); $dialog.remove(); } $h.initModal(self.$modal); self.$modal.html($h.cspBuffer.stash(self._getModalContent())); $h.cspBuffer.apply(self.$modal); $.each($h.MODAL_EVENTS, function (key, event) { self._listenModalEvent(event); }); }, _initZoomButtons: function () { var self = this, $modal = self.$modal, previewId = $modal.data("previewId") || "", $first, $last, thumbs = self.getFrames().toArray(), len = thumbs.length, $prev = $modal.find(".btn-kv-prev"), $next = $modal.find(".btn-kv-next"), $rotate = $modal.find(".btn-kv-rotate"); if (thumbs.length < 2) { $prev.hide(); $next.hide(); return; } else { $prev.show(); $next.show(); } if (!len) { return; } $first = $(thumbs[0]); $last = $(thumbs[len - 1]); $prev.removeAttr("disabled"); $next.removeAttr("disabled"); if (self.reversePreviewOrder) { [$prev, $next] = [$next, $prev]; // swap } if ($first.length && $first.attr("id") === previewId) { $prev.attr("disabled", true); } if ($last.length && $last.attr("id") === previewId) { $next.attr("disabled", true); } }, _maximizeZoomDialog: function () { var self = this, $modal = self.$modal, $head = $modal.find(".modal-header:visible"), $foot = $modal.find(".modal-footer:visible"), $body = $modal.find(".kv-zoom-body"), h = $(window).height(), diff = 0; $modal.addClass("file-zoom-fullscreen"); if ($head && $head.length) { h -= $head.outerHeight(true); } if ($foot && $foot.length) { h -= $foot.outerHeight(true); } if ($body && $body.length) { diff = $body.outerHeight(true) - $body.height(); h -= diff; } $modal.find(".kv-zoom-body").height(h); }, _resizeZoomDialog: function (fullScreen) { var self = this, $modal = self.$modal, $btnFull = $modal.find(".btn-kv-fullscreen"), $btnBord = $modal.find(".btn-kv-borderless"); if ($modal.hasClass("file-zoom-fullscreen")) { $h.toggleFullScreen(false); if (!fullScreen) { if (!$btnFull.hasClass("active")) { $modal.removeClass("file-zoom-fullscreen"); self.$modal.find(".kv-zoom-body").css("height", self.zoomModalHeight); } else { $btnFull.removeClass("active").attr("aria-pressed", "false"); } } else { if (!$btnFull.hasClass("active")) { $modal.removeClass("file-zoom-fullscreen"); self._resizeZoomDialog(true); if ($btnBord.hasClass("active")) { $btnBord.removeClass("active").attr("aria-pressed", "false"); } } } } else { if (!fullScreen) { self._maximizeZoomDialog(); return; } $h.toggleFullScreen(true); } $modal.focus(); }, _setZoomContent: function ($frame, navigate) { var self = this, $content, tmplt, body, $body, $dataEl, config, previewId = $frame.attr("id"), $zoomPreview = self._getZoom(previewId), $modal = self.$modal, $tmp, desc, $desc, $btnFull = $modal.find(".btn-kv-fullscreen"), $btnBord = $modal.find(".btn-kv-borderless"), cap, size, $btnTogh = $modal.find(".btn-kv-toggleheader"), parsed, zoomData = $frame.data("zoom"); if (zoomData) { zoomData = decodeURIComponent(zoomData); parsed = $zoomPreview.html().replace(self.zoomPlaceholder, "").setTokens({ zoomData: zoomData }); $h.setHtml($zoomPreview, parsed); $frame.data("zoom", ""); $zoomPreview.attr("data-zoom", zoomData); } tmplt = $zoomPreview.attr("data-template") || "generic"; $content = $zoomPreview.find(".kv-file-content"); body = $content.length ? $content.html() : ""; cap = $frame.data("caption") || self.msgZoomModalHeading; size = $frame.data("size") || ""; desc = $frame.data("description") || ""; $h.setHtml($modal.find(".kv-zoom-caption").attr("title", cap), cap); $h.setHtml($modal.find(".kv-zoom-size"), size); $desc = $modal.find(".kv-zoom-description").hide(); if (desc) { if (self.showDescriptionClose) { desc = self._getLayoutTemplate("descriptionClose").setTokens({ closeIcon: self.previewZoomButtonIcons.close, }) + "" + desc; } $h.setHtml($desc, desc).show(); if (self.showDescriptionClose) { self._handler($modal.find(".kv-desc-hide"), "click", function () { $(this) .parent() .fadeOut("fast", function () { $modal.focus(); }); }); } } $body = $modal.find(".kv-zoom-body"); $modal.removeClass("kv-single-content"); if (navigate) { $tmp = $body.addClass("file-thumb-loading").clone().insertAfter($body); $h.setHtml($body, body).hide(); $tmp.fadeOut("fast", function () { $body.fadeIn("fast", function () { $body.removeClass("file-thumb-loading"); }); $tmp.remove(); }); } else { $h.setHtml($body, body); } config = self.previewZoomSettings[tmplt]; if (config) { $dataEl = $body.find(".kv-preview-data"); $h.addCss($dataEl, "file-zoom-detail"); $.each(config, function (key, value) { $dataEl.css(key, value); if (($dataEl.attr("width") && key === "width") || ($dataEl.attr("height") && key === "height")) { $dataEl.removeAttr(key); } }); } $modal.data("previewId", previewId); self._handler($modal.find(".btn-kv-prev"), "click", function () { self._zoomSlideShow("prev", previewId); }); self._handler($modal.find(".btn-kv-next"), "click", function () { self._zoomSlideShow("next", previewId); }); self._handler($btnFull, "click", function () { self._resizeZoomDialog(true); }); self._handler($btnBord, "click", function () { self._resizeZoomDialog(false); }); self._handler($btnTogh, "click", function () { var $header = $modal.find(".modal-header"), $floatBar = $modal.find(".floating-buttons"), ht, $actions = $header.find(".kv-zoom-actions"), resize = function (height) { var $body = self.$modal.find(".kv-zoom-body"), h = self.zoomModalHeight; if ($modal.hasClass("file-zoom-fullscreen")) { h = $body.outerHeight(true); if (!height) { h = h - $header.outerHeight(true); } } $body.css("height", height ? h + height : h); }; if ($header.is(":visible")) { ht = $header.outerHeight(true); $header.slideUp("slow", function () { $actions.find(".btn").appendTo($floatBar); resize(ht); }); } else { $floatBar.find(".btn").appendTo($actions); $header.slideDown("slow", function () { resize(); }); } $modal.focus(); }); self._handler($modal, "keydown", function (e) { var key = e.which || e.keyCode, delay = self.processDelay + 1, $prev = $(this).find(".btn-kv-prev"), $next = $(this).find(".btn-kv-next"), vId = $(this).data("previewId"), vPrevKey, vNextKey; [vPrevKey, vNextKey] = self.rtl ? [39, 37] : [37, 39]; $.each({ prev: [$prev, vPrevKey], next: [$next, vNextKey] }, function (direction, config) { var $btn = config[0], vKey = config[1]; if (key === vKey && $btn.length) { $modal.focus(); if (!$btn.attr("disabled")) { $btn.blur(); setTimeout(function () { $btn.focus(); self._zoomSlideShow(direction, vId); setTimeout(function () { if ($btn.attr("disabled")) { $modal.focus(); } }, delay); }, delay); } } }); }); }, _showModal: function ($frame) { var self = this, $modal = self.$modal, $content, css, angle; if (!$frame || !$frame.length) { return; } $h.initModal($modal); $h.setHtml($modal, self._getModalContent()); self._setZoomContent($frame); $modal.removeClass("rotatable"); $modal.data({ backdrop: false, fileinputPluginId: self.$element.attr("id") }); $modal.find(".kv-zoom-body").css("height", self.zoomModalHeight); $content = $frame.find(".kv-file-content > :first-child"); if ($content.length) { css = $content.css("transform"); if (css) { $modal.find(".file-zoom-detail").css("transform", css); } } if ($frame.hasClass("rotatable")) { $modal.addClass("rotatable"); } if ($frame.data("angle")) { $modal.data("angle", $frame.data("angle")); } angle = $frame.data("angle") || 0; $modal.modal("show"); self._initZoomButtons(); self._initRotateZoom($frame, $content); }, _zoomPreview: function ($btn) { var self = this, $frame; if (!$btn.length) { throw "Cannot zoom to detailed preview!"; } $frame = $btn.closest($h.FRAMES); self._showModal($frame); }, _zoomSlideShow: function (dir, previewId) { var self = this, $modal = self.$modal, $btn = $modal.find(".kv-zoom-actions .btn-kv-" + dir), $targFrame, i, $thumb, thumbsData = self.getFrames().toArray(), thumbs = [], len = thumbsData.length, out, angle, $content; if (self.reversePreviewOrder) { dir = dir === "prev" ? "next" : "prev"; } if ($btn.attr("disabled")) { return; } for (i = 0; i < len; i++) { $thumb = $(thumbsData[i]); if ($thumb && $thumb.length && $thumb.find(".kv-file-zoom:visible").length) { thumbs.push(thumbsData[i]); } } len = thumbs.length; for (i = 0; i < len; i++) { if ($(thumbs[i]).attr("id") === previewId) { out = dir === "prev" ? i - 1 : i + 1; break; } } if (out < 0 || out >= len || !thumbs[out]) { return; } $targFrame = $(thumbs[out]); if ($targFrame.length) { self._setZoomContent($targFrame, dir); } self._initZoomButtons(); if ($targFrame.length && $targFrame.hasClass("rotatable")) { angle = $targFrame.data("angle") || 0; $modal.addClass("rotatable").data("angle", angle); $content = $targFrame.find(".kv-file-content > :first-child"); self._initRotateZoom($targFrame, $content); } else { $modal.removeClass("rotatable").removeData("angle"); } self._raise("filezoom" + dir, { previewId: previewId, modal: self.$modal }); }, _initZoomButton: function () { var self = this; self.$preview.find(".kv-file-zoom").each(function () { var $el = $(this); self._handler($el, "click", function () { self._zoomPreview($el); }); }); }, _inputFileCount: function () { return this.$element[0].files.length; }, _refreshPreview: function () { var self = this, files; if ((!self._inputFileCount() && !self.isAjaxUpload) || !self.showPreview || !self.isPreviewable) { return; } if (self.isAjaxUpload) { if (self.fileManager.count() > 0) { files = $.extend(true, [], self.getFileList()); self.fileManager.clear(); self._clearFileInput(); } else { files = self.$element[0].files; } } else { files = self.$element[0].files; } if (files && files.length) { self.readFiles(files); } }, _clearObjects: function ($el) { $el.find("video audio").each(function () { this.pause(); $(this).remove(); }); $el.find("img object div").each(function () { $(this).remove(); }); }, _clearFileInput: function () { var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl; if (!self._inputFileCount()) { return; } $srcFrm = $el.closest("form"); $tmpFrm = $(document.createElement("form")); $tmpEl = $(document.createElement("div")); $el.before($tmpEl); if ($srcFrm.length) { $srcFrm.after($tmpFrm); } else { $tmpEl.after($tmpFrm); } $tmpFrm.append($el).trigger("reset"); $tmpEl.before($el).remove(); $tmpFrm.remove(); }, _resetUpload: function () { var self = this; self.uploadInitiated = false; self.uploadStartTime = $h.now(); self.uploadCache = []; self.$btnUpload.removeAttr("disabled"); self._setProgress(0); self._hideProgress(); self._resetErrors(false); self._initAjax(); self.fileManager.clearImages(); self._resetCanvas(); if (self.overwriteInitial) { self.initialPreview = []; self.initialPreviewConfig = []; self.initialPreviewThumbTags = []; self.previewCache.data = { content: [], config: [], tags: [], }; } }, _resetCanvas: function () { var self = this; if (self.imageCanvas && self.imageCanvasContext) { self.imageCanvasContext.clearRect(0, 0, self.imageCanvas.width, self.imageCanvas.height); } }, _hasInitialPreview: function () { var self = this; return !self.overwriteInitial && self.previewCache.count(true); }, _resetPreview: function () { var self = this, out, cap, $div, hasSuc = self.showUploadedThumbs, hasErr = !self.removeFromPreviewOnError, includeProcessed = (hasSuc || hasErr) && self.isDuplicateError; if (self.previewCache.count(true)) { out = self.previewCache.out(); if (includeProcessed) { $div = $h.createDiv().insertAfter(self.$container); self.getFrames().each(function () { var $thumb = $(this); if ( (hasSuc && $thumb.hasClass("file-preview-success")) || (hasErr && $thumb.hasClass("file-preview-error")) ) { $div.append($thumb); } }); } self._setPreviewContent(out.content); self._setInitThumbAttr(); cap = self.initialCaption ? self.initialCaption : out.caption; self._setCaption(cap); if (includeProcessed) { $div.contents().appendTo(self.$preview); $div.remove(); } } else { self._clearPreview(); self._initCaption(); } if (self.showPreview) { self._initZoom(); self._initSortable(); } self.isDuplicateError = false; }, _clearDefaultPreview: function () { var self = this; self.$preview.find(".file-default-preview").remove(); }, _validateDefaultPreview: function () { var self = this; if (!self.showPreview || $h.isEmpty(self.defaultPreviewContent)) { return; } self._setPreviewContent('
    ' + self.defaultPreviewContent + "
    "); self.$container.removeClass("file-input-new"); self._initClickable(); }, _resetPreviewThumbs: function (isAjax) { var self = this, out; if (isAjax) { self._clearPreview(); self.clearFileStack(); return; } if (self._hasInitialPreview()) { out = self.previewCache.out(); self._setPreviewContent(out.content); self._setInitThumbAttr(); self._setCaption(out.caption); self._initPreviewActions(); } else { self._clearPreview(); } }, _getLayoutTemplate: function (t) { var self = this, template = self.layoutTemplates[t]; if ($h.isEmpty(self.customLayoutTags)) { return template; } return $h.replaceTags(template, self.customLayoutTags); }, _getPreviewTemplate: function (t) { var self = this, templates = self.previewTemplates, template = templates[t] || templates.other; if ($h.isEmpty(self.customPreviewTags)) { return template; } return $h.replaceTags(template, self.customPreviewTags); }, _getOutData: function (formdata, jqXHR, responseData, filesData) { var self = this; jqXHR = jqXHR || {}; responseData = responseData || {}; filesData = filesData || self.fileManager.list(); return { formdata: formdata, files: filesData, filenames: self.filenames, filescount: self.getFilesCount(), extra: self._getExtraData(), response: responseData, reader: self.reader, jqXHR: jqXHR, }; }, _getMsgSelected: function (n, processing) { var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural; return n > 0 ? self.msgSelected.replace("{n}", n).replace("{files}", strFiles) : processing ? self.msgProcessing : self.msgNoFilesSelected; }, _getFrame: function (id, skipWarning) { var self = this, $frame = $h.getFrameElement(self.$preview, id); if (self.showPreview && !skipWarning && !$frame.length) { self._log($h.logMessages.invalidThumb, { id: id }); } return $frame; }, _getZoom: function (id, selector) { var self = this, $frame = $h.getZoomElement(self.$preview, id, selector); if (self.showPreview && !$frame.length) { self._log($h.logMessages.invalidThumb, { id: id }); } return $frame; }, _getThumbs: function (css) { css = css || ""; return this.getFrames(":not(.file-preview-initial)" + css); }, _getThumbId: function (fileId) { var self = this; return self.previewInitId + "-" + fileId; }, _getExtraData: function (fileId, index) { var self = this, data = self.uploadExtraData; if (typeof self.uploadExtraData === "function") { data = self.uploadExtraData(fileId, index); } return data; }, _initXhr: function (xhrobj, fileId) { var self = this, fm = self.fileManager, func = function (event) { var pct = 0, total = event.total, loaded = event.loaded || event.position, stats = fm.getUploadStats(fileId, loaded, total); /** @namespace event.lengthComputable */ if (event.lengthComputable && !self.enableResumableUpload) { pct = $h.round((loaded / total) * 100); } if (fileId) { self._setFileUploadStats(fileId, pct, stats); } else { self._setProgress(pct, null, null, self._getStats(stats)); } self._raise("fileajaxprogress", [stats]); }; if (xhrobj.upload) { if (self.progressDelay) { func = $h.debounce(func, self.progressDelay); } xhrobj.upload.addEventListener("progress", func, false); } return xhrobj; }, _initAjaxSettings: function () { var self = this; self._ajaxSettings = $.extend(true, {}, self.ajaxSettings); self._ajaxDeleteSettings = $.extend(true, {}, self.ajaxDeleteSettings); }, _mergeAjaxCallback: function (funcName, srcFunc, type) { var self = this, settings = self._ajaxSettings, flag = self.mergeAjaxCallbacks, targFunc; if (type === "delete") { settings = self._ajaxDeleteSettings; flag = self.mergeAjaxDeleteCallbacks; } targFunc = settings[funcName]; if (flag && typeof targFunc === "function") { if (flag === "before") { settings[funcName] = function () { targFunc.apply(this, arguments); srcFunc.apply(this, arguments); }; } else { settings[funcName] = function () { srcFunc.apply(this, arguments); targFunc.apply(this, arguments); }; } } else { settings[funcName] = srcFunc; } }, _ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, formdata, fileId, index, vUrl) { var self = this, settings, defaults, data, tm = self.taskManager; if (!self._raise("filepreajax", [formdata, fileId, index])) { return; } formdata.append("initialPreview", JSON.stringify(self.initialPreview)); formdata.append("initialPreviewConfig", JSON.stringify(self.initialPreviewConfig)); formdata.append("initialPreviewThumbTags", JSON.stringify(self.initialPreviewThumbTags)); self._initAjaxSettings(); self._mergeAjaxCallback("beforeSend", fnBefore); self._mergeAjaxCallback("success", fnSuccess); self._mergeAjaxCallback("complete", fnComplete); self._mergeAjaxCallback("error", fnError); vUrl = vUrl || self.uploadUrlThumb || self.uploadUrl; if (typeof vUrl === "function") { vUrl = vUrl(); } data = self._getExtraData(fileId, index) || {}; if (typeof data === "object") { $.each(data, function (key, value) { formdata.append(key, value); }); } defaults = { xhr: function () { var xhrobj = $.ajaxSettings.xhr(); return self._initXhr(xhrobj, fileId); }, url: self._encodeURI(vUrl), type: "POST", dataType: "json", data: formdata, cache: false, processData: false, contentType: false, }; settings = $.extend(true, {}, defaults, self._ajaxSettings); self.ajaxQueue.push(settings); tm.addTask(fileId + "-" + index, function () { var self = this.self, config, xhr; config = self.ajaxQueue.shift(); xhr = $.ajax(config); self.ajaxRequests.push(xhr); }).runWithContext({ self: self }); }, _mergeArray: function (prop, content) { var self = this, arr1 = $h.cleanArray(self[prop]), arr2 = $h.cleanArray(content); self[prop] = arr1.concat(arr2); }, _initUploadSuccess: function (out, $thumb, allFiles) { var self = this, append, data, index, $div, content, config, tags, id, i; if (!self.showPreview || typeof out !== "object" || $.isEmptyObject(out)) { self._resetCaption(); return; } if (out.initialPreview !== undefined && out.initialPreview.length > 0) { self.hasInitData = true; content = out.initialPreview || []; config = out.initialPreviewConfig || []; tags = out.initialPreviewThumbTags || []; append = out.append === undefined || out.append; if (content.length > 0 && !$h.isArray(content)) { content = content.split(self.initialPreviewDelimiter); } if (content.length) { self._mergeArray("initialPreview", content); self._mergeArray("initialPreviewConfig", config); self._mergeArray("initialPreviewThumbTags", tags); } if ($thumb !== undefined) { if (!allFiles) { index = self.previewCache.add(content[0], config[0], tags[0], append); data = self.previewCache.get(index, false); $div = $h.createElement($h.cspBuffer.stash(data)).hide().appendTo($thumb); $h.cspBuffer.apply($thumb); $thumb.fadeOut("slow", function () { var $newThumb = $div.find("> .file-preview-frame"); if ($newThumb && $newThumb.length) { $newThumb.insertBefore($thumb).fadeIn("slow").css("display:inline-block"); } self._initPreviewActions(); self._clearFileInput(); $thumb.remove(); $div.remove(); self._initSortable(); }); } else { id = $thumb.attr("id"); i = self._getUploadCacheIndex(id); if (i !== null) { self.uploadCache[i] = { id: id, content: content[0], config: config[0] || [], tags: tags[0] || [], append: append, }; } } } else { self.previewCache.set(content, config, tags, append); self._initPreview(); self._initPreviewActions(); } } self._resetCaption(); }, _getUploadCacheIndex: function (id) { var self = this, i, len = self.uploadCache.length, config; for (i = 0; i < len; i++) { config = self.uploadCache[i]; if (config.id === id) { return i; } } return null; }, _initSuccessThumbs: function () { var self = this; if (!self.showPreview) { return; } setTimeout(function () { self._getThumbs($h.FRAMES + ".file-preview-success").each(function () { var $thumb = $(this), $remove = $thumb.find(".kv-file-remove"); $remove.removeAttr("disabled"); self._handler($remove, "click", function () { var id = $thumb.attr("id"), out = self._raise("filesuccessremove", [id, $thumb.attr("data-fileindex")]); $h.cleanMemory($thumb); if (out === false) { return; } self.$caption.attr("title", ""); $thumb.fadeOut("slow", function () { var fm = self.fileManager; $thumb.remove(); if (!self.getFrames().length) { self.reset(); } }); }); }); }, self.processDelay); }, _updateInitialPreview: function () { var self = this, u = self.uploadCache; if (self.showPreview) { $.each(u, function (key, setting) { self.previewCache.add(setting.content, setting.config, setting.tags, setting.append); }); if (self.hasInitData) { self._initPreview(); self._initPreviewActions(); } } }, _getThumbFileId: function ($thumb) { var self = this; if (self.showPreview && $thumb !== undefined) { return $thumb.attr("data-fileid"); } return null; }, _getThumbFile: function ($thumb) { var self = this, id = self._getThumbFileId($thumb); return id ? self.fileManager.getFile(id) : null; }, _uploadSingle: function (i, id, isBatch, deferrer) { var self = this, fm = self.fileManager, count = fm.count(), formdata = new FormData(), outData, previewId = self._getThumbId(id), $thumb, chkComplete, $btnUpload, $btnDelete, hasPostData = count > 0 || !$.isEmptyObject(self.uploadExtraData), uploadFailed, $prog, fnBefore, errMsg, fnSuccess, fnComplete, fnError, updateUploadLog, op = self.ajaxOperations.uploadThumb, fileObj = fm.getFile(id), params = { id: previewId, index: i, fileId: id }, fileName = self.fileManager.getFileName(id, true), resolve = function () { if (deferrer && deferrer.resolve) { deferrer.resolve(); } }, reject = function () { if (deferrer && deferrer.reject) { deferrer.reject(); } }; if (self.enableResumableUpload) { // not enabled for resumable uploads return; } self.uploadInitiated = true; if (self.showPreview) { $thumb = fm.getThumb(id); $prog = $thumb.find(".file-thumb-progress"); $btnUpload = $thumb.find(".kv-file-upload"); $btnDelete = $thumb.find(".kv-file-remove"); $prog.show(); } if ( count === 0 || !hasPostData || (self.showPreview && $btnUpload && $btnUpload.hasClass("disabled")) || self._abort(params) ) { return; } updateUploadLog = function () { if (!uploadFailed) { fm.removeFile(id); } else { fm.errors.push(id); } fm.setProcessed(id); if (fm.isProcessed()) { self.fileBatchCompleted = true; chkComplete(); } }; chkComplete = function () { var $initThumbs; if (!self.fileBatchCompleted) { return; } setTimeout(function () { var triggerReset = fm.count() === 0, errCount = fm.errors.length; self._updateInitialPreview(); self.unlock(triggerReset); if (triggerReset) { self._clearFileInput(); } $initThumbs = self.$preview.find(".file-preview-initial"); if (self.uploadAsync && $initThumbs.length) { $h.addCss($initThumbs, $h.SORT_CSS); self._initSortable(); } self._raise("filebatchuploadcomplete", [fm.stack, self._getExtraData()]); if (!self.retryErrorUploads || errCount === 0) { fm.clear(); } self._setProgress(101); self.ajaxAborted = false; self.uploadInitiated = false; }, self.processDelay); }; fnBefore = function (jqXHR) { outData = self._getOutData(formdata, jqXHR); fm.initStats(id); self.fileBatchCompleted = false; if (!isBatch) { self.ajaxAborted = false; } if (self.showPreview) { if (!$thumb.hasClass("file-preview-success")) { self._setThumbStatus($thumb, "Loading"); $h.addCss($thumb, "file-uploading"); } $btnUpload.attr("disabled", true); $btnDelete.attr("disabled", true); } if (!isBatch) { self.lock(); } if (fm.errors.indexOf(id) !== -1) { delete fm.errors[id]; } self._raise("filepreupload", [outData, previewId, i, self._getThumbFileId($thumb)]); $.extend(true, params, outData); if (self._abort(params)) { jqXHR.abort(); if (!isBatch) { self._setThumbStatus($thumb, "New"); $thumb.removeClass("file-uploading"); $btnUpload.removeAttr("disabled"); $btnDelete.removeAttr("disabled"); } self._setProgressCancelled(); } }; fnSuccess = function (data, textStatus, jqXHR) { var pid = self.showPreview && $thumb.attr("id") ? $thumb.attr("id") : previewId; outData = self._getOutData(formdata, jqXHR, data); $.extend(true, params, outData); setTimeout(function () { if ($h.isEmpty(data) || $h.isEmpty(data.error)) { if (self.showPreview) { self._setThumbStatus($thumb, "Success"); $btnUpload.hide(); self._initUploadSuccess(data, $thumb, isBatch); self._setProgress(101, $prog); } self._raise("fileuploaded", [outData, pid, i, self._getThumbFileId($thumb)]); if (!isBatch) { self.fileManager.remove($thumb); } else { updateUploadLog(); resolve(); } } else { uploadFailed = true; errMsg = self._parseError(op, jqXHR, self.msgUploadError, self.fileManager.getFileName(id)); self._showFileError(errMsg, params); self._setPreviewError($thumb, true); if (!self.retryErrorUploads) { $btnUpload.hide(); } if (isBatch) { updateUploadLog(); resolve(); } self._setProgress(101, self._getFrame(pid).find(".file-thumb-progress"), self.msgUploadError); } }, self.processDelay); }; fnComplete = function () { if (self.showPreview) { $btnUpload.removeAttr("disabled"); $btnDelete.removeAttr("disabled"); $thumb.removeClass("file-uploading"); } if (!isBatch) { self.unlock(false); self._clearFileInput(); } else { chkComplete(); } self._initSuccessThumbs(); }; fnError = function (jqXHR, textStatus, errorThrown) { errMsg = self._parseError(op, jqXHR, errorThrown, self.fileManager.getFileName(id)); uploadFailed = true; setTimeout(function () { var $prog; if (isBatch) { updateUploadLog(); reject(); } self.fileManager.setProgress(id, 100); self._setPreviewError($thumb, true); if (!self.retryErrorUploads) { $btnUpload.hide(); } $.extend(true, params, self._getOutData(formdata, jqXHR)); self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace("{operation}", op)); $prog = self.showPreview && $thumb ? $thumb.find(".file-thumb-progress") : ""; self._setProgress(101, $prog, self.msgUploadError); self._showFileError(errMsg, params); }, self.processDelay); }; self._setFileData(formdata, fileObj.file, fileName, id); self._setUploadData(formdata, { fileId: id }); self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata, id, i); }, _setFileData: function (formdata, file, fileName, fileId) { var self = this, preProcess = self.preProcessUpload; if (preProcess && typeof preProcess === "function") { formdata.append(self.uploadFileAttr, preProcess(fileId, file)); } else { formdata.append(self.uploadFileAttr, file, fileName); } }, _checkBatchPreupload: function (outData, jqXHR) { var self = this, out = self._raise("filebatchpreupload", [outData]); if (out) { return true; } self._abort(outData); if (jqXHR) { jqXHR.abort(); } self._getThumbs().each(function () { var $thumb = $(this), $btnUpload = $thumb.find(".kv-file-upload"), $btnDelete = $thumb.find(".kv-file-remove"); if ($thumb.hasClass("file-preview-loading")) { self._setThumbStatus($thumb, "New"); $thumb.removeClass("file-uploading"); } $btnUpload.removeAttr("disabled"); $btnDelete.removeAttr("disabled"); }); self._setProgressCancelled(); return false; }, _uploadBatch: function () { var self = this, fm = self.fileManager, total = fm.total(), params = {}, fnBefore, fnSuccess, fnError, fnComplete, hasPostData = total > 0 || !$.isEmptyObject(self.uploadExtraData), errMsg, setAllUploaded, formdata = new FormData(), op = self.ajaxOperations.uploadBatch; if (total === 0 || !hasPostData || self._abort(params)) { return; } setAllUploaded = function () { self.fileManager.clear(); self._clearFileInput(); }; fnBefore = function (jqXHR) { self.lock(); fm.initStats(); var outData = self._getOutData(formdata, jqXHR); self.ajaxAborted = false; if (self.showPreview) { self._getThumbs().each(function () { var $thumb = $(this), $btnUpload = $thumb.find(".kv-file-upload"), $btnDelete = $thumb.find(".kv-file-remove"); if (!$thumb.hasClass("file-preview-success")) { self._setThumbStatus($thumb, "Loading"); $h.addCss($thumb, "file-uploading"); } $btnUpload.attr("disabled", true); $btnDelete.attr("disabled", true); }); } self._checkBatchPreupload(outData, jqXHR); }; fnSuccess = function (data, textStatus, jqXHR) { /** @namespace data.errorkeys */ var outData = self._getOutData(formdata, jqXHR, data), key = 0, $thumbs = self._getThumbs(":not(.file-preview-success)"), keys = $h.isEmpty(data) || $h.isEmpty(data.errorkeys) ? [] : data.errorkeys; if ($h.isEmpty(data) || $h.isEmpty(data.error)) { self._raise("filebatchuploadsuccess", [outData]); setAllUploaded(); if (self.showPreview) { $thumbs.each(function () { var $thumb = $(this); self._setThumbStatus($thumb, "Success"); $thumb.removeClass("file-uploading"); $thumb.find(".kv-file-upload").hide().removeAttr("disabled"); }); self._initUploadSuccess(data); } else { self.reset(); } self._setProgress(101); } else { if (self.showPreview) { $thumbs.each(function () { var $thumb = $(this); $thumb.removeClass("file-uploading"); $thumb.find(".kv-file-upload").removeAttr("disabled"); $thumb.find(".kv-file-remove").removeAttr("disabled"); if (keys.length === 0 || $.inArray(key, keys) !== -1) { self._setPreviewError($thumb, true); if (!self.retryErrorUploads) { $thumb.find(".kv-file-upload").hide(); self.fileManager.remove($thumb); } } else { $thumb.find(".kv-file-upload").hide(); self._setThumbStatus($thumb, "Success"); self.fileManager.remove($thumb); } if (!$thumb.hasClass("file-preview-error") || self.retryErrorUploads) { key++; } }); self._initUploadSuccess(data); } errMsg = self._parseError(op, jqXHR, self.msgUploadError); self._showFileError(errMsg, outData, "filebatchuploaderror"); self._setProgress(101, self.$progress, self.msgUploadError); } }; fnComplete = function () { self.unlock(); self._initSuccessThumbs(); self._clearFileInput(); self._raise("filebatchuploadcomplete", [self.fileManager.stack, self._getExtraData()]); }; fnError = function (jqXHR, textStatus, errorThrown) { var outData = self._getOutData(formdata, jqXHR); errMsg = self._parseError(op, jqXHR, errorThrown); self._showFileError(errMsg, outData, "filebatchuploaderror"); self.uploadFileCount = total - 1; if (!self.showPreview) { return; } self._getThumbs().each(function () { var $thumb = $(this); $thumb.removeClass("file-uploading"); if (self._getThumbFile($thumb)) { self._setPreviewError($thumb); } }); self._getThumbs().removeClass("file-uploading"); self._getThumbs(" .kv-file-upload").removeAttr("disabled"); self._getThumbs(" .kv-file-delete").removeAttr("disabled"); self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace("{operation}", op)); }; var ctr = 0; $.each(self.fileManager.stack, function (key, data) { if (!$h.isEmpty(data.file)) { self._setFileData(formdata, data.file, data.nameFmt || "untitled_" + ctr, key); } ctr++; }); self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata); }, _uploadExtraOnly: function () { var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError, formdata = new FormData(), errMsg, op = self.ajaxOperations.uploadExtra; fnBefore = function (jqXHR) { self.lock(); var outData = self._getOutData(formdata, jqXHR); self._setProgress(50); params.data = outData; params.xhr = jqXHR; self._checkBatchPreupload(outData, jqXHR); }; fnSuccess = function (data, textStatus, jqXHR) { var outData = self._getOutData(formdata, jqXHR, data); if ($h.isEmpty(data) || $h.isEmpty(data.error)) { self._raise("filebatchuploadsuccess", [outData]); self._clearFileInput(); self._initUploadSuccess(data); self._setProgress(101); } else { errMsg = self._parseError(op, jqXHR, self.msgUploadError); self._showFileError(errMsg, outData, "filebatchuploaderror"); } }; fnComplete = function () { self.unlock(); self._clearFileInput(); self._raise("filebatchuploadcomplete", [self.fileManager.stack, self._getExtraData()]); }; fnError = function (jqXHR, textStatus, errorThrown) { var outData = self._getOutData(formdata, jqXHR); errMsg = self._parseError(op, jqXHR, errorThrown); params.data = outData; self._showFileError(errMsg, outData, "filebatchuploaderror"); self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace("{operation}", op)); }; self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata); }, _deleteFileIndex: function ($frame) { var self = this, ind = $frame.attr("data-fileindex"), rev = self.reversePreviewOrder; if (ind.substring(0, 5) === $h.INIT_FLAG) { ind = parseInt(ind.replace($h.INIT_FLAG, "")); self.initialPreview = $h.spliceArray(self.initialPreview, ind, rev); self.initialPreviewConfig = $h.spliceArray(self.initialPreviewConfig, ind, rev); self.initialPreviewThumbTags = $h.spliceArray(self.initialPreviewThumbTags, ind, rev); self.getFrames().each(function () { var $nFrame = $(this), nInd = $nFrame.attr("data-fileindex"); if (nInd.substring(0, 5) === $h.INIT_FLAG) { nInd = parseInt(nInd.replace($h.INIT_FLAG, "")); if (nInd > ind) { nInd--; $nFrame.attr("data-fileindex", $h.INIT_FLAG + nInd); } } }); } }, _resetCaption: function () { var self = this; setTimeout(function () { var cap = "", n, chk = self.previewCache.count(true), len = self.fileManager.count(), file, incomplete = ":not(.file-preview-success):not(.file-preview-error)", cfg, hasThumb = self.showPreview && self.getFrames(incomplete).length; if (len === 0 && chk === 0 && !hasThumb) { self.reset(); } else { n = chk + len; if (n > 1) { cap = self._getMsgSelected(n); } else { if (len === 0) { cfg = self.initialPreviewConfig[0]; cap = ""; if (cfg) { cap = cfg.caption || cfg.filename || ""; } if (!cap) { cap = self._getMsgSelected(n); } } else { file = self.fileManager.getFirstFile(); cap = file ? file.nameFmt : "_"; } } self._setCaption(cap); } }, self.processDelay); }, _handleRotation: function ($el, $content, angle) { var self = this, css, newCss, addCss = "", scale = 1, elContent = $content[0], quadrant, transform, h, w, wNew, $parent = $content.parent(), hParent, wParent, $body = $("body"), bodyExists = !!$body.length; if (bodyExists) { $body.addClass("kv-overflow-hidden"); } if (!$content.length || $el.hasClass("hide-rotate")) { if (bodyExists) { $body.removeClass("kv-overflow-hidden"); } return; } transform = $content.css("transform"); if (transform) { $content.css("transform", "none"); } if (transform) { $content.css("transform", transform); } angle = angle || 0; quadrant = angle % 360; css = "rotate(" + angle + "deg)"; newCss = "rotate(" + quadrant + "deg)"; addCss = ""; if (quadrant === 90 || quadrant === 270) { w = elContent.naturalWidth || $content.outerWidth() || 0; h = elContent.naturalHeight || $content.outerHeight() || 0; scale = w > h && w != 0 ? (h / w).toFixed(2) : 1; if ($parent.length) { hParent = $parent.height(); wParent = $parent.width(); wNew = Math.min(w, wParent); if (hParent > scale * wNew) { scale = wNew > hParent && wNew != 0 ? (hParent / wNew).toFixed(2) : 1; } } if (scale !== 1) { addCss = " scale(" + scale + ")"; } } $content.addClass("rotate-animate").css("transform", css + addCss); setTimeout(function () { $content.removeClass("rotate-animate").css("transform", newCss + addCss); if (bodyExists) { $body.removeClass("kv-overflow-hidden"); } $el.data("angle", quadrant); }, self.fadeDelay); }, _initRotateButton: function () { var self = this; self.getFrames(".rotatable .kv-file-rotate").each(function () { var $el = $(this), $frame = $el.closest($h.FRAMES), $content = $frame.find(".kv-file-content > :first-child"); self._handler($el, "click", function () { var angle = ($frame.data("angle") || 0) + 90; self._handleRotation($frame, $content, angle); }); }); }, _initRotateZoom: function ($frame, $content) { var self = this, $modal = self.$modal, $rotate = $modal.find(".btn-kv-rotate"), angle = $frame.data("angle"); $modal.data("angle", angle); if ($rotate.length) { $rotate.off("click"); if ($modal.hasClass("rotatable")) { $rotate.on("click", function () { angle = ($modal.data("angle") || 0) + 90; $modal.data("angle", angle); self._handleRotation($modal, $modal.find(".file-zoom-detail"), angle); self._handleRotation($frame, $content, angle); if ($frame.hasClass("hide-rotate")) { $frame.data("angle", angle); } }); } } }, _initFileActions: function () { var self = this; if (!self.showPreview) { return; } self._initZoomButton(); self._initRotateButton(); self.getFrames(" .kv-file-remove").each(function () { var $el = $(this), $frame = $el.closest($h.FRAMES), hasError, id = $frame.attr("id"), ind = $frame.attr("data-fileindex"), status, fm = self.fileManager; self._handler($el, "click", function () { status = self._raise("filepreremove", [id, ind]); if (status === false || !self._validateMinCount()) { return false; } hasError = $frame.hasClass("file-preview-error"); $h.cleanMemory($frame); $frame.fadeOut("slow", function () { self.fileManager.remove($frame); self._clearObjects($frame); $frame.remove(); if (id && hasError) { self.$errorContainer.find('li[data-thumb-id="' + id + '"]').fadeOut("fast", function () { $(this).remove(); if (!self._errorsExist()) { self._resetErrors(); } }); } self._clearFileInput(); self._resetCaption(); self._raise("fileremoved", [id, ind]); }); }); }); self.getFrames(" .kv-file-upload").each(function () { var $el = $(this); self._handler($el, "click", function () { var $frame = $el.closest($h.FRAMES), fileId = self._getThumbFileId($frame); self._hideProgress(); if ($frame.hasClass("file-preview-error") && !self.retryErrorUploads) { return; } self._uploadSingle(self.fileManager.getIndex(fileId), fileId, false); }); }); }, _initPreviewActions: function () { var self = this, $preview = self.$preview, deleteExtraData = self.deleteExtraData || {}, btnRemove = $h.FRAMES + " .kv-file-remove", settings = self.fileActionSettings, origClass = settings.removeClass, errClass = settings.removeErrorClass, resetProgress = function () { var hasFiles = self.isAjaxUpload ? self.previewCache.count(true) : self._inputFileCount(); if (!self.getFrames().length && !hasFiles) { self._setCaption(""); self.reset(); self.initialCaption = ""; } else { self._resetCaption(); } }; self._initZoomButton(); self._initRotateButton(); $preview.find(btnRemove).each(function () { var $el = $(this), vUrl = $el.data("url") || self.deleteUrl, vKey = $el.data("key"), errMsg, fnBefore, fnSuccess, fnError, op = self.ajaxOperations.deleteThumb; if ($h.isEmpty(vUrl) || vKey === undefined) { return; } if (typeof vUrl === "function") { vUrl = vUrl(); } var $frame = $el.closest($h.FRAMES), cache = self.previewCache.data, settings, params, config, fileName, extraData, index = $frame.attr("data-fileindex"); index = parseInt(index.replace($h.INIT_FLAG, "")); config = $h.isEmpty(cache.config) && $h.isEmpty(cache.config[index]) ? null : cache.config[index]; extraData = $h.isEmpty(config) || $h.isEmpty(config.extra) ? deleteExtraData : config.extra; fileName = (config && (config.filename || config.caption)) || ""; if (typeof extraData === "function") { extraData = extraData(); } params = { id: $el.attr("id"), key: vKey, extra: extraData }; fnBefore = function (jqXHR) { self.ajaxAborted = false; self._raise("filepredelete", [vKey, jqXHR, extraData]); if (self._abort()) { jqXHR.abort(); } else { $el.removeClass(errClass); $h.addCss($frame, "file-uploading"); $h.addCss($el, "disabled " + origClass); } }; fnSuccess = function (data, textStatus, jqXHR) { var n, cap; if (!$h.isEmpty(data) && !$h.isEmpty(data.error)) { params.jqXHR = jqXHR; params.response = data; errMsg = self._parseError(op, jqXHR, self.msgDeleteError, fileName); self._showFileError(errMsg, params, "filedeleteerror"); $frame.removeClass("file-uploading"); $el.removeClass("disabled " + origClass).addClass(errClass); resetProgress(); return; } $frame.removeClass("file-uploading").addClass("file-deleted"); $frame.fadeOut("slow", function () { index = parseInt($frame.attr("data-fileindex").replace($h.INIT_FLAG, "")); self.previewCache.unset(index); self._deleteFileIndex($frame); n = self.previewCache.count(true); cap = n > 0 ? self._getMsgSelected(n) : ""; self._setCaption(cap); self._raise("filedeleted", [vKey, jqXHR, extraData]); self._clearObjects($frame); $frame.remove(); resetProgress(); }); }; fnError = function (jqXHR, textStatus, errorThrown) { var errMsg = self._parseError(op, jqXHR, errorThrown, fileName); params.jqXHR = jqXHR; params.response = {}; self._showFileError(errMsg, params, "filedeleteerror"); $frame.removeClass("file-uploading"); $el.removeClass("disabled " + origClass).addClass(errClass); resetProgress(); }; self._initAjaxSettings(); self._mergeAjaxCallback("beforeSend", fnBefore, "delete"); self._mergeAjaxCallback("success", fnSuccess, "delete"); self._mergeAjaxCallback("error", fnError, "delete"); settings = $.extend( true, {}, { url: self._encodeURI(vUrl), type: "POST", dataType: "json", data: $.extend(true, {}, { key: vKey }, extraData), }, self._ajaxDeleteSettings ); self._handler($el, "click", function () { if (!self._validateMinCount()) { return false; } self.ajaxAborted = false; self._raise("filebeforedelete", [vKey, extraData]); if (self.ajaxAborted instanceof Promise) { self.ajaxAborted.then(function (result) { if (!result) { $.ajax(settings); } }); } else { if (!self.ajaxAborted) { $.ajax(settings); } } }); }); }, _hideFileIcon: function () { var self = this; if (self.overwriteInitial) { self.$captionContainer.removeClass("icon-visible"); } }, _showFileIcon: function () { var self = this; $h.addCss(self.$captionContainer, "icon-visible"); }, _getSize: function (bytes, skipTemplate, sizeUnits) { var self = this, size = parseFloat(bytes), i = 0, factor = self.bytesToKB, func = self.fileSizeGetter, out, sizeHuman = size, newSize; if (!$h.isNumeric(bytes) || !$h.isNumeric(size)) { return ""; } if (typeof func === "function") { out = func(size); } else { if (!sizeUnits) { sizeUnits = self.sizeUnits; } if (size > 0) { while (sizeHuman >= factor) { sizeHuman /= factor; ++i; } if (!sizeUnits[i]) { sizeHuman = size; i = 0; } } newSize = sizeHuman.toFixed(2); if (newSize == sizeHuman) { newSize = sizeHuman; } out = newSize + " " + sizeUnits[i]; } return skipTemplate ? out : self._getLayoutTemplate("size").replace("{sizeText}", out); }, _getFileType: function (ftype) { var self = this; return self.mimeTypeAliases[ftype] || ftype; }, _generatePreviewTemplate: function ( cat, data, fname, ftype, previewId, fileId, isError, size, fnameUpdated, frameClass, foot, ind, templ, attrs, zoomData ) { var self = this, caption = self.slug(fname), prevContent, zoomContent = "", styleAttribs = "", filename = fnameUpdated || fname, isIconic, ext = filename.split(".").pop().toLowerCase(), screenW = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, config, title = caption, alt = caption, typeCss = "type-default", getContent, addFrameCss, footer = foot || self._renderFileFooter(cat, caption, size, "auto", isError), isRotatable, alwaysPreview = $.inArray(ext, self.alwaysPreviewFileExtensions) !== -1, forcePrevIcon = self.preferIconicPreview && !alwaysPreview, forceZoomIcon = self.preferIconicZoomPreview && !alwaysPreview, newCat = forcePrevIcon ? "other" : cat; config = screenW < 400 ? self.previewSettingsSmall[newCat] || self.defaults.previewSettingsSmall[newCat] : self.previewSettings[newCat] || self.defaults.previewSettings[newCat]; if (config) { $.each(config, function (key, val) { styleAttribs += key + ":" + val + ";"; }); } getContent = function (vCat, vData, zoom, frameCss, vZoomData) { var id = zoom ? "zoom-" + previewId : previewId, tmplt = self._getPreviewTemplate(vCat), css = (frameClass || "") + " " + frameCss, tokens; if (self.frameClass) { css = self.frameClass + " " + css; } if (zoom) { css = css.replace(" " + $h.SORT_CSS, ""); } tmplt = self._parseFilePreviewIcon(tmplt, fname); if (cat === "object" && !ftype) { $.each(self.defaults.fileTypeSettings, function (key, func) { if (key === "object" || key === "other") { return; } if (func(fname, ftype)) { typeCss = "type-" + key; } }); } if (!$h.isEmpty(attrs)) { if (attrs.title !== undefined && attrs.title !== null) { title = attrs.title; } if (attrs.alt !== undefined && attrs.alt !== null) { alt = title = attrs.alt; } } tokens = { previewId: id, caption: caption, title: title, alt: alt, frameClass: css, type: self._getFileType(ftype), fileindex: ind, fileid: fileId || "", filename: filename, typeCss: typeCss, footer: footer, data: vData, // data: zoom && vZoomData ? self.zoomPlaceholder + "{zoomData}" : vData, template: templ || cat, style: styleAttribs ? 'style="' + styleAttribs + '"' : "", zoomData: vZoomData ? encodeURIComponent(vZoomData) : "", }; if (zoom) { tokens.zoomCache = ""; tokens.zoomData = "{zoomData}"; } return tmplt.setTokens(tokens); }; ind = ind || previewId.slice(previewId.lastIndexOf("-") + 1); isRotatable = self.fileActionSettings.showRotate && $.inArray(ext, self.rotatableFileExtensions) !== -1; if (self.fileActionSettings.showZoom) { addFrameCss = "kv-zoom-thumb"; if (isRotatable) { addFrameCss += " rotatable" + (forceZoomIcon ? " hide-rotate" : ""); } zoomContent = getContent(forceZoomIcon ? "other" : cat, data, true, addFrameCss, zoomData); } zoomContent = "\n" + self._getLayoutTemplate("zoomCache").replace("{zoomContent}", zoomContent); if (typeof self.sanitizeZoomCache === "function") { zoomContent = self.sanitizeZoomCache(zoomContent); } addFrameCss = "kv-preview-thumb"; if (isRotatable) { isIconic = forcePrevIcon || self.hideThumbnailContent || !!self.previewFileIconSettings[ext]; addFrameCss += " rotatable" + (isIconic ? " hide-rotate" : ""); } prevContent = getContent(forcePrevIcon ? "other" : cat, data, false, addFrameCss, zoomData); return prevContent.setTokens({ zoomCache: zoomContent }); }, _addToPreview: function ($preview, content) { var self = this, $el; content = $h.cspBuffer.stash(content); $el = self.reversePreviewOrder ? $preview.prepend(content) : $preview.append(content); $h.cspBuffer.apply($preview); return $el; }, _previewDefault: function (file, isDisabled) { var self = this, $preview = self.$preview; if (!self.showPreview) { return; } var fname = $h.getFileName(file), ftype = file ? file.type : "", content, size = file.size || 0, caption = self._getFileName(file, ""), isError = isDisabled === true && !self.isAjaxUpload, data = $h.createObjectURL(file), fileId = self.fileManager.getId(file), previewId = self._getThumbId(fileId); self._clearDefaultPreview(); content = self._generatePreviewTemplate("other", data, fname, ftype, previewId, fileId, isError, size); self._addToPreview($preview, content); self._setThumbAttr(previewId, caption, size); if (isDisabled === true && self.isAjaxUpload) { self._setThumbStatus(self._getFrame(previewId), "Error"); } }, _previewFile: function (i, file, theFile, data, fileInfo) { if (!this.showPreview) { return; } var self = this, fname = $h.getFileName(file), ftype = fileInfo.type, content, caption = fileInfo.name, cat = self._parseFileType(ftype, fname), $preview = self.$preview, fsize = file.size || 0, iData = cat === "image" ? theFile.target.result : data, fm = self.fileManager, fileId = fm.getId(file), previewId = self._getThumbId(fileId); /** @namespace window.DOMPurify */ content = self._generatePreviewTemplate( cat, iData, fname, ftype, previewId, fileId, false, fsize, fileInfo.filename ); self._clearDefaultPreview(); self._addToPreview($preview, content); var $thumb = self._getFrame(previewId); self._validateImageOrientation($thumb.find("img"), file, previewId, fileId, caption, ftype, fsize, iData); self._setThumbAttr(previewId, caption, fsize); self._initSortable(); }, _setThumbAttr: function (id, caption, size, description) { var self = this, $frame = self._getFrame(id); if ($frame.length) { size = size && size > 0 ? self._getSize(size) : ""; $frame.data({ caption: caption, size: size, description: description || "" }); } }, _setInitThumbAttr: function () { var self = this, data = self.previewCache.data, len = self.previewCache.count(true), config, caption, size, description, previewId; if (len === 0) { return; } for (var i = 0; i < len; i++) { config = data.config[i]; previewId = self.previewInitId + "-" + $h.INIT_FLAG + i; caption = $h.ifSet("caption", config, $h.ifSet("filename", config)); size = $h.ifSet("size", config); description = $h.ifSet("description", config); self._setThumbAttr(previewId, caption, size, description); } }, _slugDefault: function (text) { // noinspection RegExpRedundantEscape return $h.isEmpty(text, true) ? "" : String(text).replace(/[\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g, "_"); }, _updateFileDetails: function (numFiles) { var self = this, $el = self.$element, label, n, log, nFiles, file, name = ($h.isIE(9) && $h.findFileName($el.val())) || ($el[0].files[0] && $el[0].files[0].name); if (!name && self.fileManager.count() > 0) { file = self.fileManager.getFirstFile(); label = file.nameFmt; } else { label = name ? self.slug(name) : "_"; } n = self.isAjaxUpload ? self.fileManager.count() : numFiles; nFiles = self.previewCache.count(true) + n; log = n === 1 ? label : self._getMsgSelected(nFiles, !self.isAjaxUpload && !self.isError); if (self.isError) { self.$previewContainer.removeClass("file-thumb-loading"); self._initCapStatus(); self.$previewStatus.html(""); self.$captionContainer.removeClass("icon-visible"); } else { self._showFileIcon(); } self._setCaption(log, self.isError); self.$container.removeClass("file-input-new file-input-ajax-new"); self._raise("fileselect", [numFiles, label]); if (self.previewCache.count(true)) { self._initPreviewActions(); } }, _setThumbStatus: function ($thumb, status) { var self = this; if (!self.showPreview) { return; } var icon = "indicator" + status, msg = icon + "Title", css = "file-preview-" + status.toLowerCase(), $indicator = $thumb.find(".file-upload-indicator"), config = self.fileActionSettings; $thumb.removeClass("file-preview-success file-preview-error file-preview-paused file-preview-loading"); if (status === "Success") { $thumb.find(".file-drag-handle").remove(); } $h.setHtml($indicator, config[icon]); $indicator.attr("title", config[msg]); $thumb.addClass(css); if (status === "Error" && !self.retryErrorUploads) { $thumb.find(".kv-file-upload").attr("disabled", true); } }, _setProgressCancelled: function () { var self = this; self._setProgress(101, self.$progress, self.msgCancelled); }, _setProgress: function (p, $el, error, stats) { var self = this; $el = $el || self.$progress; if (!$el.length) { return; } var pct = Math.min(p, 100), out, pctLimit = self.progressUploadThreshold, t = p <= 100 ? self.progressTemplate : self.progressCompleteTemplate, template = pct < 100 ? self.progressTemplate : error ? self.paused ? self.progressPauseTemplate : self.progressErrorTemplate : t; if (p >= 100) { stats = ""; } if (!$h.isEmpty(template)) { if (pctLimit && pct > pctLimit && p <= 100) { out = template.setTokens({ percent: pctLimit, status: self.msgUploadThreshold }); } else { out = template.setTokens({ percent: pct, status: p > 100 ? self.msgUploadEnd : pct + "%" }); } stats = stats || ""; out = out.setTokens({ stats: stats }); $h.setHtml($el, out); if (error) { $h.setHtml($el.find('[role="progressbar"]'), error); } } }, _hasFiles: function () { var el = this.$element[0]; return !!(el && el.files && el.files.length); }, _setFileDropZoneTitle: function () { var self = this, $zone = self.$container.find(".file-drop-zone"), title = self.dropZoneTitle, strFiles; if (self.isClickable) { strFiles = $h.isEmpty(self.$element.attr("multiple")) ? self.fileSingle : self.filePlural; title += self.dropZoneClickTitle.replace("{files}", strFiles); } $zone.find("." + self.dropZoneTitleClass).remove(); if ( !self.showPreview || $zone.length === 0 || self.fileManager.count() > 0 || !self.dropZoneEnabled || self.previewCache.count() > 0 || (!self.isAjaxUpload && self._hasFiles()) ) { return; } if ($zone.find($h.FRAMES).length === 0 && $h.isEmpty(self.defaultPreviewContent)) { $zone.prepend($h.cspBuffer.stash('
    ' + title + "
    ")); $h.cspBuffer.apply($zone); } self.$container.removeClass("file-input-new"); if (self.isAjaxUpload) { $h.addCss(self.$container, "file-input-ajax-new"); } }, _getStats: function (stats) { var self = this, pendingTime, t; if (!self.showUploadStats || !stats || !stats.bitrate) { return ""; } t = self._getLayoutTemplate("stats"); pendingTime = !stats.elapsed || !stats.bps ? self.msgCalculatingTime : self.msgPendingTime.setTokens({ time: $h.getElapsed(Math.ceil(stats.pendingBytes / stats.bps)) }); return t.setTokens({ uploadSpeed: stats.bitrate, pendingTime: pendingTime, }); }, _setResumableProgress: function (pct, stats, $thumb) { var self = this, rm = self.resumableManager, obj = $thumb ? rm : self, $prog = $thumb ? $thumb.find(".file-thumb-progress") : null; if (obj.lastProgress === 0) { obj.lastProgress = pct; } if (pct < obj.lastProgress) { pct = obj.lastProgress; } self._setProgress(pct, $prog, null, self._getStats(stats)); obj.lastProgress = pct; }, _toggleResumableProgress: function (template, message) { var self = this, $progress = self.$progress; if ($progress && $progress.length) { $h.setHtml( $progress, template.setTokens({ percent: 101, status: message, stats: "", }) ); } }, _setFileUploadStats: function (id, pct, stats) { var self = this, $prog = self.$progress; if (!self.showPreview && (!$prog || !$prog.length)) { return; } var fm = self.fileManager, rm = self.resumableManager, $thumb = fm.getThumb(id), pctTot, totUpSize = 0, totSize = fm.getTotalSize(), totStats = $.extend(true, {}, stats); if (self.enableResumableUpload) { var loaded = stats.loaded, currUplSize = rm.getUploadedSize(), currTotSize = rm.file.size, totLoaded; loaded += currUplSize; totLoaded = fm.uploadedSize + loaded; pct = $h.round((100 * loaded) / currTotSize); stats.pendingBytes = currTotSize - currUplSize; self._setResumableProgress(pct, stats, $thumb); pctTot = Math.floor((100 * totLoaded) / totSize); totStats.pendingBytes = totSize - totLoaded; self._setResumableProgress(pctTot, totStats); } else { fm.setProgress(id, pct); $prog = $thumb && $thumb.length ? $thumb.find(".file-thumb-progress") : null; self._setProgress(pct, $prog, null, self._getStats(stats)); $.each(fm.stats, function (id, cfg) { totUpSize += cfg.loaded; }); totStats.pendingBytes = totSize - totUpSize; pctTot = $h.round((totUpSize / totSize) * 100); self._setProgress(pctTot, null, null, self._getStats(totStats)); } }, _validateMinCount: function () { var self = this, len = self.isAjaxUpload ? self.fileManager.count() : self._inputFileCount(); if (self.validateInitialCount && self.minFileCount > 0 && self._getFileCount(len - 1) < self.minFileCount) { self._noFilesError({}); return false; } return true; }, _getFileCount: function (fileCount, includeInitial) { var self = this, addCount = 0; if (includeInitial === undefined) { includeInitial = self.validateInitialCount && !self.overwriteInitial; } if (includeInitial) { addCount = self.previewCache.count(true); fileCount += addCount; } return fileCount; }, _getFileId: function (file) { return $h.getFileId(file, this.generateFileId); }, _getFileName: function (file, defaultValue) { var self = this, fileName = $h.getFileName(file); return fileName ? self.slug(fileName) : defaultValue; }, _getFileNames: function (skipNull) { var self = this; return self.filenames.filter(function (n) { return skipNull ? n !== undefined : n !== undefined && n !== null; }); }, _setPreviewError: function ($thumb, keepFile) { var self = this, removeFrame = self.removeFromPreviewOnError && !self.retryErrorUploads; if (!keepFile || removeFrame) { self.fileManager.remove($thumb); } if (!self.showPreview) { return; } if (removeFrame) { $thumb.remove(); return; } else { self._setThumbStatus($thumb, "Error"); } self._refreshUploadButton($thumb); }, _refreshUploadButton: function ($thumb) { var self = this, $btn = $thumb.find(".kv-file-upload"), cfg = self.fileActionSettings, icon = cfg.uploadIcon, title = cfg.uploadTitle; if (!$btn.length) { return; } if (self.retryErrorUploads) { icon = cfg.uploadRetryIcon; title = cfg.uploadRetryTitle; } $btn.attr("title", title); $h.setHtml($btn, icon); }, _isValidSize: function (size, type, $image, $thumb, filename, params) { var self = this, msg, dim, $img, tag = size === "Small" ? "min" : "max", limit = self[tag + "Image" + type]; if ($h.isEmpty(limit) || !$image.length) { return true; } $img = $image[0]; dim = type === "Width" ? $img.naturalWidth || $img.width : $img.naturalHeight || $img.height; if (size === "Small" ? dim >= limit : dim <= limit) { return true; } msg = self["msgImage" + type + size] || 'Image "{name}" has a size validation error (limit "{size}").'; self._showFileError(msg.setTokens({ name: filename, size: limit, dimension: dim }), params); self._setPreviewError($thumb); self.fileManager.remove($thumb); self._clearFileInput(); return false; }, _getExifObj: function (data) { var self = this, exifObj, error = $h.logMessages.exifWarning; if (data.slice(0, 23) !== "data:image/jpeg;base64," && data.slice(0, 22) !== "data:image/jpg;base64,") { exifObj = null; return; } try { exifObj = window.piexif ? window.piexif.load(data) : null; } catch (err) { exifObj = null; error = (err && err.message) || ""; } if (!exifObj && self.showExifErrorLog) { self._log($h.logMessages.badExifParser, { details: error }); } return exifObj; }, setImageOrientation: function ($img, $zoomImg, value, $thumb) { var self = this, invalidImg = !$img || !$img.length, invalidZoomImg = !$zoomImg || !$zoomImg.length, $mark, isHidden = false, $div, zoomOnly = invalidImg && $thumb && $thumb.attr("data-template") === "image", ev; if (invalidImg && invalidZoomImg) { return; } ev = "load.fileinputimageorient"; if (zoomOnly) { $img = $zoomImg; $zoomImg = null; $img.css(self.previewSettings.image); $div = $h.createDiv().appendTo($thumb.find(".kv-file-content")); $mark = $(document.createElement("span")).insertBefore($img); $img.css("visibility", "hidden").removeClass("file-zoom-detail").appendTo($div); } else { isHidden = !$img.is(":visible"); } $img.off(ev).on(ev, function () { if (isHidden) { self.$preview.removeClass("hide-content"); $thumb.find(".kv-file-content").css("visibility", "hidden"); } var img = $img[0], zoomImg = $zoomImg && $zoomImg.length ? $zoomImg[0] : null, h = img.offsetHeight, w = img.offsetWidth, r = $h.getRotation(value); if (isHidden) { $thumb.find(".kv-file-content").css("visibility", "visible"); self.$preview.addClass("hide-content"); } $img.data("orientation", value); if (zoomImg) { $zoomImg.data("orientation", value); } if (value < 5) { $h.setTransform(img, r); $h.setTransform(zoomImg, r); return; } var offsetAngle = Math.atan(w / h), origFactor = Math.sqrt(Math.pow(h, 2) + Math.pow(w, 2)), scale = !origFactor ? 1 : h / Math.cos(Math.PI / 2 + offsetAngle) / origFactor, s = " scale(" + Math.abs(scale) + ")"; $h.setTransform(img, r + s); $h.setTransform(zoomImg, r + s); if (zoomOnly) { $img.css("visibility", "visible").insertAfter($mark).addClass("file-zoom-detail"); $mark.remove(); $div.remove(); } }); }, _validateImageOrientation: function ($img, file, previewId, fileId, caption, ftype, fsize, iData) { var self = this, exifObj = null, value, autoOrientImage = self.autoOrientImage, selector; exifObj = self._getExifObj(iData); if (self.canOrientImage) { $img.css("image-orientation", autoOrientImage ? "from-image" : "none"); self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj); return; } selector = $h.getZoomSelector(previewId, " img"); value = exifObj ? exifObj["0th"][piexif.ImageIFD.Orientation] : null; // jshint ignore:line if (!value) { self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj); return; } self.setImageOrientation($img, $(selector), value, self._getFrame(previewId)); self._raise("fileimageoriented", { $img: $img, file: file }); self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj); }, _validateImage: function (previewId, fileId, fname, ftype, fsize, iData, exifObj) { var self = this, $preview = self.$preview, params, w1, w2, $thumb = self._getFrame(previewId), i = $thumb.attr("data-fileindex"), $img = $thumb.find("img"); fname = fname || "Untitled"; $img .one("load", function () { if ($img.data("validated")) { return; } $img.data("validated", true); w1 = $thumb.width(); w2 = $preview.width(); if (w1 > w2) { $img.css("width", "100%"); } params = { ind: i, id: previewId, fileId: fileId }; setTimeout(function () { var isValidWidth, isValidHeight; isValidWidth = self._isValidSize("Small", "Width", $img, $thumb, fname, params); isValidHeight = self._isValidSize("Small", "Height", $img, $thumb, fname, params); if (!self.resizeImage) { isValidWidth = isValidWidth && self._isValidSize("Large", "Width", $img, $thumb, fname, params); isValidHeight = isValidHeight && self._isValidSize("Large", "Height", $img, $thumb, fname, params); } self._raise("fileimageloaded", [previewId]); $thumb.data("exif", exifObj); if (isValidWidth && isValidHeight) { self.fileManager.addImage(fileId, { ind: i, img: $img, thumb: $thumb, pid: previewId, typ: ftype, siz: fsize, validated: false, imgData: iData, exifObj: exifObj, }); self._validateAllImages(); } }, self.processDelay); }) .one("error", function () { self._raise("fileimageloaderror", [previewId]); }); }, _validateAllImages: function () { var self = this, counter = { val: 0 }, numImgs = self.fileManager.getImageCount(), fsize, minSize = self.resizeIfSizeMoreThan; if (numImgs !== self.fileManager.totalImages) { return; } self._raise("fileimagesloaded"); if (!self.resizeImage) { return; } $.each(self.fileManager.loadedImages, function (id, config) { if (!config.validated) { fsize = config.siz; if (fsize && fsize > minSize * self.bytesToKB) { self._getResizedImage(id, config, counter, numImgs); } config.validated = true; } }); }, _getResizedImage: function (id, config, counter, numImgs) { var self = this, img = $(config.img)[0], width = img.naturalWidth, height = img.naturalHeight, blob, ratio = 1, maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height, isValidImage = !!(width && height), chkWidth, chkHeight, canvas = self.imageCanvas, dataURI, context = self.imageCanvasContext, type = config.typ, pid = config.pid, ind = config.ind, $thumb = config.thumb, throwError, msg, exifObj = config.exifObj, exifStr, file, params, evParams; throwError = function (msg, params, ev) { if (self.isAjaxUpload) { self._showFileError(msg, params, ev); } else { self._showError(msg, params, ev); } self._setPreviewError($thumb); }; file = self.fileManager.getFile(id); params = { id: pid, index: ind, fileId: id }; evParams = [id, pid, ind]; if (!file || !isValidImage || (width <= maxWidth && height <= maxHeight)) { if (isValidImage && file) { self._raise("fileimageresized", evParams); } counter.val++; if (counter.val === numImgs) { self._raise("fileimagesresized"); } if (!isValidImage) { throwError(self.msgImageResizeError, params, "fileimageresizeerror"); return; } } type = type || self.resizeDefaultImageType; chkWidth = width > maxWidth; chkHeight = height > maxHeight; if (self.resizePreference === "width") { ratio = chkWidth ? maxWidth / width : chkHeight ? maxHeight / height : 1; } else { ratio = chkHeight ? maxHeight / height : chkWidth ? maxWidth / width : 1; } self._resetCanvas(); width *= ratio; height *= ratio; canvas.width = width; canvas.height = height; try { context.drawImage(img, 0, 0, width, height); dataURI = canvas.toDataURL(type, self.resizeQuality); if (exifObj) { exifStr = window.piexif.dump(exifObj); dataURI = window.piexif.insert(exifStr, dataURI); } blob = $h.dataURI2Blob(dataURI); self.fileManager.setFile(id, blob); self._raise("fileimageresized", evParams); counter.val++; if (counter.val === numImgs) { self._raise("fileimagesresized", [undefined, undefined]); } if (!(blob instanceof Blob)) { throwError(self.msgImageResizeError, params, "fileimageresizeerror"); } } catch (err) { counter.val++; if (counter.val === numImgs) { self._raise("fileimagesresized", [undefined, undefined]); } msg = self.msgImageResizeException.replace("{errors}", err.message); throwError(msg, params, "fileimageresizeexception"); } }, _showProgress: function () { var self = this; if (self.$progress && self.$progress.length) { self.$progress.show(); } }, _hideProgress: function () { var self = this; if (self.$progress && self.$progress.length) { self.$progress.hide(); } }, _initBrowse: function ($container) { var self = this, $el = self.$element; if (self.showBrowse) { self.$btnFile = $container.find(".btn-file").append($el); } else { $el.appendTo($container).attr("tabindex", -1); $h.addCss($el, "file-no-browse"); } }, _initClickable: function () { var self = this, $zone, $tmpZone; if (!self.isClickable) { return; } $zone = self.$dropZone; if (!self.isAjaxUpload) { $tmpZone = self.$preview.find(".file-default-preview"); if ($tmpZone.length) { $zone = $tmpZone; } } $h.addCss($zone, "clickable"); $zone.attr("tabindex", -1); self._handler($zone, "click", function (e) { var $tar = $(e.target); if ( !self.$errorContainer.is(":visible") && (!$tar.parents(".file-preview-thumbnails").length || $tar.parents(".file-default-preview").length) ) { self.$element.data("zoneClicked", true).trigger("click"); $zone.blur(); } }); }, _initCaption: function () { var self = this, cap = self.initialCaption || ""; if (self.overwriteInitial || $h.isEmpty(cap)) { self.$caption.val(""); return false; } self._setCaption(cap); return true; }, _setCaption: function (content, isError) { var self = this, title, out, icon, n, cap, file; if (!self.$caption.length) { return; } self.$captionContainer.removeClass("icon-visible"); if (isError) { title = $("
    " + self.msgValidationError + "
    ").text(); n = self.fileManager.count(); if (n) { file = self.fileManager.getFirstFile(); cap = n === 1 && file ? file.nameFmt : self._getMsgSelected(n); } else { cap = self._getMsgSelected(self.msgNo); } out = $h.isEmpty(content) ? cap : content; icon = '' + self.msgValidationErrorIcon + ""; } else { if ($h.isEmpty(content)) { self.$caption.attr("title", ""); return; } title = $("
    " + content + "
    ").text(); out = title; icon = self._getLayoutTemplate("fileIcon"); } self.$captionContainer.addClass("icon-visible"); self.$caption.attr("title", title).val(out); $h.setHtml(self.$captionIcon, icon); }, _createContainer: function () { var self = this, attribs = { class: "file-input file-input-new" + (self.rtl ? " kv-rtl" : "") }, $container = $h.createElement($h.cspBuffer.stash(self._renderMain())); $h.cspBuffer.apply($container); $container.insertBefore(self.$element).attr(attribs); self._initBrowse($container); if (self.theme) { $container.addClass("theme-" + self.theme); } return $container; }, _refreshContainer: function () { var self = this, $container = self.$container, $el = self.$element; $el.insertAfter($container); $h.setHtml($container, self._renderMain()); self._initBrowse($container); self._validateDisabled(); }, _validateDisabled: function () { var self = this; self.$caption.attr({ readonly: self.isDisabled }); }, _setTabIndex: function (type, html) { var self = this, index = self.tabIndexConfig[type]; return html.setTokens({ tabIndexConfig: index === undefined || index === null ? "" : 'tabindex="' + index + '"', }); }, _renderMain: function () { var self = this, dropCss = self.dropZoneEnabled ? " file-drop-zone" : "file-drop-disabled", close = !self.showClose ? "" : self._getLayoutTemplate("close"), preview = !self.showPreview ? "" : self._getLayoutTemplate("preview").setTokens({ class: self.previewClass, dropClass: dropCss }), css = self.isDisabled ? self.captionClass + " file-caption-disabled" : self.captionClass, caption = self.captionTemplate.setTokens({ class: css + " kv-fileinput-caption" }); caption = self._setTabIndex("caption", caption); return self.mainTemplate.setTokens({ class: self.mainClass + (!self.showBrowse && self.showCaption ? " no-browse" : ""), inputGroupClass: self.inputGroupClass, preview: preview, close: close, caption: caption, upload: self._renderButton("upload"), remove: self._renderButton("remove"), cancel: self._renderButton("cancel"), pause: self._renderButton("pause"), browse: self._renderButton("browse"), }); }, _renderButton: function (type) { var self = this, tmplt = self._getLayoutTemplate("btnDefault"), css = self[type + "Class"], title = self[type + "Title"], icon = self[type + "Icon"], label = self[type + "Label"], status = self.isDisabled ? " disabled" : "", btnType = "button"; switch (type) { case "remove": if (!self.showRemove) { return ""; } break; case "cancel": if (!self.showCancel) { return ""; } css += " kv-hidden"; break; case "pause": if (!self.showPause) { return ""; } css += " kv-hidden"; break; case "upload": if (!self.showUpload) { return ""; } if (self.isAjaxUpload && !self.isDisabled) { tmplt = self._getLayoutTemplate("btnLink").replace("{href}", self.uploadUrl); } else { btnType = "submit"; } break; case "browse": if (!self.showBrowse) { return ""; } tmplt = self._getLayoutTemplate("btnBrowse"); break; default: return ""; } tmplt = self._setTabIndex(type, tmplt); css += type === "browse" ? " btn-file" : " fileinput-" + type + " fileinput-" + type + "-button"; if (!$h.isEmpty(label)) { label = ' ' + label + ""; } return tmplt.setTokens({ type: btnType, css: css, title: title, status: status, icon: icon, label: label, }); }, _renderThumbProgress: function () { var self = this; return ( '
    ' + self.progressInfoTemplate.setTokens({ percent: 101, status: self.msgUploadBegin, stats: "" }) + "
    " ); }, _renderFileFooter: function (cat, caption, size, width, isError) { var self = this, config = self.fileActionSettings, rem = config.showRemove, drg = config.showDrag, upl = config.showUpload, rot = config.showRotate, zoom = config.showZoom, out, params, template = self._getLayoutTemplate("footer"), tInd = self._getLayoutTemplate("indicator"), ind = isError ? config.indicatorError : config.indicatorNew, title = isError ? config.indicatorErrorTitle : config.indicatorNewTitle, indicator = tInd.setTokens({ indicator: ind, indicatorTitle: title }); size = self._getSize(size); params = { type: cat, caption: caption, size: size, width: width, progress: "", indicator: indicator }; if (self.isAjaxUpload) { params.progress = self._renderThumbProgress(); params.actions = self._renderFileActions(params, upl, false, rem, rot, zoom, drg, false, false, false); } else { params.actions = self._renderFileActions(params, false, false, false, false, zoom, drg, false, false, false); } out = template.setTokens(params); out = $h.replaceTags(out, self.previewThumbTags); return out; }, _renderFileActions: function ( cfg, showUpl, showDwn, showDel, showRot, showZoom, showDrag, disabled, url, key, isInit, dUrl, dFile ) { var self = this; if (!cfg.type && isInit) { cfg.type = "image"; } if (self.enableResumableUpload) { showUpl = false; } else { if (typeof showUpl === "function") { showUpl = showUpl(cfg); } } if (typeof showDwn === "function") { showDwn = showDwn(cfg); } if (typeof showDel === "function") { showDel = showDel(cfg); } if (typeof showZoom === "function") { showZoom = showZoom(cfg); } if (typeof showDrag === "function") { showDrag = showDrag(cfg); } if (typeof showRot === "function") { showRot = showRot(cfg); } if (!showUpl && !showDwn && !showDel && !showRot && !showZoom && !showDrag) { return ""; } var vUrl = url === false ? "" : ' data-url="' + url + '"', btnZoom = "", btnDrag = "", btnRotate = "", css, vKey = key === false ? "" : ' data-key="' + key + '"', btnDelete = "", btnUpload = "", btnDownload = "", template = self._getLayoutTemplate("actions"), config = self.fileActionSettings, otherButtons = self.otherActionButtons.setTokens({ dataKey: vKey, key: key }), removeClass = disabled ? config.removeClass + " disabled" : config.removeClass; if (showDel) { btnDelete = self._getLayoutTemplate("actionDelete").setTokens({ removeClass: removeClass, removeIcon: config.removeIcon, removeTitle: config.removeTitle, dataUrl: vUrl, dataKey: vKey, key: key, }); } if (showRot) { btnRotate = self._getLayoutTemplate("actionRotate").setTokens({ rotateClass: config.rotateClass, rotateIcon: config.rotateIcon, rotateTitle: config.rotateTitle, }); } if (showUpl) { btnUpload = self._getLayoutTemplate("actionUpload").setTokens({ uploadClass: config.uploadClass, uploadIcon: config.uploadIcon, uploadTitle: config.uploadTitle, }); } if (showDwn) { btnDownload = self._getLayoutTemplate("actionDownload").setTokens({ downloadClass: config.downloadClass, downloadIcon: config.downloadIcon, downloadTitle: config.downloadTitle, downloadUrl: dUrl || self.initialPreviewDownloadUrl, }); btnDownload = btnDownload.setTokens({ filename: dFile, key: key }); } if (showZoom) { btnZoom = self._getLayoutTemplate("actionZoom").setTokens({ zoomClass: config.zoomClass, zoomIcon: config.zoomIcon, zoomTitle: config.zoomTitle, }); } if (showDrag && isInit) { css = "drag-handle-init " + config.dragClass; btnDrag = self._getLayoutTemplate("actionDrag").setTokens({ dragClass: css, dragTitle: config.dragTitle, dragIcon: config.dragIcon, }); } return template.setTokens({ delete: btnDelete, upload: btnUpload, download: btnDownload, rotate: btnRotate, zoom: btnZoom, drag: btnDrag, other: otherButtons, }); }, _browse: function (e) { var self = this; if ((e && e.isDefaultPrevented()) || !self._raise("filebrowse")) { return; } if (self.isError && !self.isAjaxUpload) { self.clear(); } if (self.focusCaptionOnBrowse) { self.$captionContainer.focus(); } }, _change: function (e) { var self = this; $(document.body).off("focusin.fileinput focusout.fileinput"); if (self.changeTriggered) { self._toggleLoading("hide"); return; } self._toggleLoading("show"); var $el = self.$element, isDragDrop = arguments.length > 1, isAjaxUpload = self.isAjaxUpload, tfiles, files = isDragDrop ? arguments[1] : $el[0].files, ctr = self.fileManager.count(), total, initCount, len, isSingleUpl = $h.isEmpty($el.attr("multiple")), maxCount = !isAjaxUpload && isSingleUpl ? 1 : self.maxFileCount, maxTotCount = self.maxTotalFileCount, inclAll = maxTotCount > 0 && maxTotCount > maxCount, flagSingle = isSingleUpl && ctr > 0, throwError = function (mesg, file, previewId, index) { var p1 = $.extend(true, {}, self._getOutData(null, {}, {}, files), { id: previewId, index: index }), p2 = { id: previewId, index: index, file: file, files: files }; self.isPersistentError = true; self._toggleLoading("hide"); return isAjaxUpload ? self._showFileError(mesg, p1) : self._showError(mesg, p2); }, maxCountCheck = function (n, m, all) { var msg = all ? self.msgTotalFilesTooMany : self.msgFilesTooMany; msg = msg.replace("{m}", m).replace("{n}", n); self.isError = throwError(msg, null, null, null); self.$captionContainer.removeClass("icon-visible"); self._setCaption("", true); self.$container.removeClass("file-input-new file-input-ajax-new"); }; self.reader = null; self._resetUpload(); self._hideFileIcon(); if (self.dropZoneEnabled) { self.$container.find(".file-drop-zone ." + self.dropZoneTitleClass).remove(); } if (!isAjaxUpload) { if (e.target && e.target.files === undefined) { files = e.target.value ? [{ name: e.target.value.replace(/^.+\\/, "") }] : []; } else { files = e.target.files || {}; } } tfiles = files; if ($h.isEmpty(tfiles) || tfiles.length === 0) { if (!isAjaxUpload) { self.clear(); } self._raise("fileselectnone"); return; } self._resetErrors(); len = tfiles.length; initCount = isAjaxUpload ? self.fileManager.count() + len : len; total = self._getFileCount(initCount, inclAll ? false : undefined); if (maxCount > 0 && total > maxCount) { if (!self.autoReplace || len > maxCount) { maxCountCheck(self.autoReplace && len > maxCount ? len : total, maxCount); return; } if (total > maxCount) { self._resetPreviewThumbs(isAjaxUpload); } } else { if (inclAll) { total = self._getFileCount(initCount, true); if (maxTotCount > 0 && total > maxTotCount) { if (!self.autoReplace || len > maxCount) { maxCountCheck(self.autoReplace && len > maxTotCount ? len : total, maxTotCount, true); return; } if (total > maxCount) { self._resetPreviewThumbs(isAjaxUpload); } } } if (!isAjaxUpload || flagSingle) { self._resetPreviewThumbs(false); if (flagSingle) { self.clearFileStack(); } } else { if (isAjaxUpload && ctr === 0 && (!self.previewCache.count(true) || self.overwriteInitial)) { self._resetPreviewThumbs(true); } } } if (self.autoReplace) { self._getThumbs().each(function () { var $thumb = $(this); if ($thumb.hasClass("file-preview-success") || $thumb.hasClass("file-preview-error")) { $thumb.remove(); } }); } self.readFiles(tfiles); self._toggleLoading("hide"); }, _abort: function (params) { var self = this, data; if (self.ajaxAborted && typeof self.ajaxAborted === "object" && self.ajaxAborted.message !== undefined) { data = $.extend(true, {}, self._getOutData(null), params); data.abortData = self.ajaxAborted.data || {}; data.abortMessage = self.ajaxAborted.message; self._setProgress(101, self.$progress, self.msgCancelled); self._showFileError(self.ajaxAborted.message, data, "filecustomerror"); self.cancel(); self.unlock(); return true; } return !!self.ajaxAborted; }, _resetFileStack: function () { var self = this, i = 0; self._getThumbs().each(function () { var $thumb = $(this), ind = $thumb.attr("data-fileindex"), pid = $thumb.attr("id"); if (ind === "-1" || ind === -1) { return; } if (!self._getThumbFile($thumb)) { $thumb.attr({ "data-fileindex": i }); i++; } else { $thumb.attr({ "data-fileindex": "-1" }); } self._getZoom(pid).attr({ "data-fileindex": $thumb.attr("data-fileindex"), }); }); }, _isFileSelectionValid: function (cnt) { var self = this; cnt = cnt || 0; if (self.required && !self.getFilesCount()) { self.$errorContainer.html(""); self._showFileError(self.msgFileRequired); return false; } if (self.minFileCount > 0 && self._getFileCount(cnt) < self.minFileCount) { self._noFilesError({}); return false; } return true; }, _canPreview: function (file) { var self = this; if (!file || !self.showPreview || !self.$preview || !self.$preview.length) { return false; } var name = file.name || "", type = file.type || "", size = (file.size || 0) / self.bytesToKB, cat = self._parseFileType(type, name), allowedTypes, allowedMimes, allowedExts, skipPreview, types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes, exts = self.allowedPreviewExtensions || [], dTypes = self.disabledPreviewTypes, dMimes = self.disabledPreviewMimeTypes, dExts = self.disabledPreviewExtensions || [], maxSize = (self.maxFilePreviewSize && parseFloat(self.maxFilePreviewSize)) || 0, expAllExt = new RegExp("\\.(" + exts.join("|") + ")$", "i"), expDisExt = new RegExp("\\.(" + dExts.join("|") + ")$", "i"); allowedTypes = !types || types.indexOf(cat) !== -1; allowedMimes = !mimes || mimes.indexOf(type) !== -1; allowedExts = !exts.length || $h.compare(name, expAllExt); skipPreview = (dTypes && dTypes.indexOf(cat) !== -1) || (dMimes && dMimes.indexOf(type) !== -1) || (dExts.length && $h.compare(name, expDisExt)) || (maxSize && !isNaN(maxSize) && size > maxSize); return !skipPreview && (allowedTypes || allowedMimes || allowedExts); }, addToStack: function (file, id) { var self = this; self.stackIsUpdating = true; self.fileManager.add(file, id); self._refreshPreview(); self.stackIsUpdating = false; }, clearFileStack: function () { var self = this; self.fileManager.clear(); self._initResumableUpload(); if (self.enableResumableUpload) { if (self.showPause === null) { self.showPause = true; } if (self.showCancel === null) { self.showCancel = false; } } else { self.showPause = false; if (self.showCancel === null) { self.showCancel = true; } } return self.$element; }, getFileStack: function () { return this.fileManager.stack; }, getFileList: function () { return this.fileManager.list(); }, getFilesSize: function () { return this.fileManager.getTotalSize(); }, getFilesCount: function (includeInitial) { var self = this, len = self.isAjaxUpload ? self.fileManager.count() : self._inputFileCount(); if (includeInitial) { len += self.previewCache.count(true); } return self._getFileCount(len); }, _initCapStatus: function (status) { var self = this, $cap = self.$caption; $cap.removeClass("is-valid file-processing"); if (!status) { return; } if (status === "processing") { $cap.addClass("file-processing"); } else { $cap.addClass("is-valid"); } }, _toggleLoading: function (type) { var self = this; $h.setHtml(self.$previewStatus, type === "hide" ? "" : self.msgProcessing); self.$container.removeClass("file-thumb-loading"); self._initCapStatus(type === "hide" ? "" : "processing"); if (type !== "hide") { if (self.dropZoneEnabled) { self.$container.find(".file-drop-zone ." + self.dropZoneTitleClass).remove(); } self.$container.addClass("file-thumb-loading"); } }, _initFileSelected: function () { var self = this, $el = self.$element, $body = $(document.body), ev = "focusin.fileinput focusout.fileinput"; if ($body.length) { $body .off(ev) .on("focusout.fileinput", function () { self._toggleLoading("show"); }) .on("focusin.fileinput", function () { setTimeout(function () { if (!$el.val()) { self._setFileDropZoneTitle(); } $body.off(ev); self._toggleLoading("hide"); }, 2500); }); } else { self._toggleLoading("hide"); } }, readFiles: function (files) { this.reader = new FileReader(); var self = this, reader = self.reader, $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading, msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length, settings = self.fileTypeSettings, readFile, fileTypes = self.allowedFileTypes, typLen = fileTypes ? fileTypes.length : 0, fileExt = self.allowedFileExtensions, strExt = $h.isEmpty(fileExt) ? "" : fileExt.join(", "), throwError = function (msg, file, previewId, index, fileId) { var $thumb, p1 = $.extend(true, {}, self._getOutData(null, {}, {}, files), { id: previewId, index: index, fileId: fileId, }), p2 = { id: previewId, index: index, fileId: fileId, file: file, files: files }; Object.values(files).forEach((x) => { self._previewDefault(x, true); }); $thumb = self._getFrame(previewId, true); self._toggleLoading("hide"); if (self.isAjaxUpload) { setTimeout(function () { readFile(index + 1); }, self.processDelay); } else { self.unlock(); numFiles = 0; } if (self.removeFromPreviewOnError && $thumb.length) { $thumb.remove(); } else { self._initFileActions(); $thumb.find(".kv-file-upload").remove(); } self.isPersistentError = true; self.isError = self.isAjaxUpload ? self._showFileError(msg, p1) : self._showError(msg, p2); self._updateFileDetails(numFiles); }; self.fileManager.clearImages(); $.each(files, function (key, file) { var func = self.fileTypeSettings.image; if (func && func(file.type)) { self.fileManager.totalImages++; } }); readFile = function (i) { var $error = self.$errorContainer, errors, fm = self.fileManager; if (i >= numFiles) { self.unlock(); if (self.duplicateErrors.length) { errors = "
  • " + self.duplicateErrors.join("
  • ") + "
  • "; if ($error.find("ul").length === 0) { $h.setHtml($error, self.errorCloseButton + "
      " + errors + "
    "); } else { $error.find("ul").append($h.cspBuffer.stash(errors)); $h.cspBuffer.apply($error); } $error.fadeIn(self.fadeDelay); self._handler($error.find(".kv-error-close"), "click", function () { $error.fadeOut(self.fadeDelay); }); self.duplicateErrors = []; } if (self.isAjaxUpload) { self._raise("filebatchselected", [fm.stack]); if (fm.count() === 0 && !self.isError) { self.reset(); } } else { self._raise("filebatchselected", [files]); } $container.removeClass("file-thumb-loading"); self._initCapStatus("valid"); $status.html(""); return; } self.lock(true); var file = files[i], id, previewId, fileProcessed, fSize = (file && file.size) || 0, sizeHuman = self._getSize(fSize, true), j, msg, fnImage = settings.image, chk, typ, typ1, typ2, caption, fileSize = fSize / self.bytesToKB, fileExtExpr = "", previewData, fileCount = 0, strTypes = "", fileId, canLoad, fileReaderAborted = false, func, knownTypes = 0, isImage, processFileLoaded, initFileData; initFileData = function (dataSource) { dataSource = dataSource || file; id = fileId = self._getFileId(file); previewId = previewInitId + "-" + id; previewData = $h.createObjectURL(dataSource); caption = self._getFileName(file, ""); }; processFileLoaded = function () { var isImageResized = !!fm.loadedImages[id], msg = msgProgress.setTokens({ index: i + 1, files: numFiles, percent: 50, name: caption, }); setTimeout(function () { $h.setHtml($status, msg); self._updateFileDetails(numFiles); if (self.getFilesCount(true) > 0 && self.getFrames(":visible")) { self.$dropZone.find("." + self.dropZoneTitleClass).remove(); } readFile(i + 1); }, self.processDelay); if (self._raise("fileloaded", [file, previewId, id, i, reader]) && self.isAjaxUpload) { if (!isImageResized) { fm.add(file); } } else { if (isImageResized) { fm.removeFile(id); } } }; if (!file) { return; } initFileData(); if (typLen > 0) { for (j = 0; j < typLen; j++) { typ1 = fileTypes[j]; typ2 = self.msgFileTypes[typ1] || typ1; strTypes += j === 0 ? typ2 : ", " + typ2; } } if (caption === false) { readFile(i + 1); return; } if (caption.length === 0) { msg = self.msgInvalidFileName.replace("{name}", $h.htmlEncode($h.getFileName(file), "[unknown]")); throwError(msg, file, previewId, i, fileId); return; } if (!$h.isEmpty(fileExt)) { fileExtExpr = new RegExp("\\.(" + fileExt.join("|") + ")$", "i"); } if ((self.isAjaxUpload && fm.exists(fileId)) || self._getFrame(previewId, true).length) { var p2 = { id: previewId, index: i, fileId: fileId, file: file, files: files }; msg = self.msgDuplicateFile.setTokens({ name: caption, size: sizeHuman }); if (self.isAjaxUpload) { if (!self.stackIsUpdating) { self.duplicateErrors.push(msg); self.isDuplicateError = true; self._raise("fileduplicateerror", [file, fileId, caption, sizeHuman, previewId, i]); } readFile(i + 1); self._updateFileDetails(numFiles); } else { self._showError(msg, p2); self.unlock(); numFiles = 0; self._clearFileInput(); self.reset(); self._updateFileDetails(numFiles); } return; } if (self.maxMultipleFileSize > 0 && files.length > 1) { var captionGroup = []; var fileSizeGroup = 0; Object.values(files).forEach((file) => { fileSizeGroup = fileSizeGroup + file.size / self.bytesToKB; captionGroup.push(file.name); }); if (fileSizeGroup > self.maxMultipleFileSize) { msg = self.msgMultipleSizeTooLarge.setTokens({ name: captionGroup, size: self._getSize(fileSizeGroup, true), maxSize: self._getSize(self.maxMultipleFileSize * self.bytesToKB, true), }); throwError(msg, file, previewId, i, fileId); return; } } else if (self.maxFileSize > 0 && fileSize > self.maxFileSize) { msg = self.msgSizeTooLarge.setTokens({ name: caption, size: sizeHuman, maxSize: self._getSize(self.maxFileSize * self.bytesToKB, true), }); throwError(msg, file, previewId, i, fileId); return; } if (self.minFileSize !== null && fileSize <= $h.getNum(self.minFileSize)) { msg = self.msgSizeTooSmall.setTokens({ name: caption, size: sizeHuman, minSize: self._getSize(self.minFileSize * self.bytesToKB, true), }); throwError(msg, file, previewId, i, fileId); return; } if (!$h.isEmpty(fileTypes) && $h.isArray(fileTypes)) { for (j = 0; j < fileTypes.length; j += 1) { typ = fileTypes[j]; func = settings[typ]; fileCount += !func || typeof func !== "function" ? 0 : func(file.type, $h.getFileName(file)) ? 1 : 0; } if (fileCount === 0) { msg = self.msgInvalidFileType.setTokens({ name: caption, types: strTypes }); throwError(msg, file, previewId, i, fileId); return; } } if (fileCount === 0 && !$h.isEmpty(fileExt) && $h.isArray(fileExt) && !$h.isEmpty(fileExtExpr)) { chk = $h.compare(caption, fileExtExpr); fileCount += $h.isEmpty(chk) ? 0 : chk.length; if (fileCount === 0) { msg = self.msgInvalidFileExtension.setTokens({ name: caption, extensions: strExt }); throwError(msg, file, previewId, i, fileId); return; } } if (!self._canPreview(file)) { canLoad = self._raise("filebeforeload", [file, i, reader]); if (self.isAjaxUpload && canLoad) { fm.add(file); } if (self.showPreview && canLoad) { $container.addClass("file-thumb-loading"); self._initCapStatus("processing"); self._previewDefault(file); self._initFileActions(); } setTimeout(function () { if (canLoad) { self._updateFileDetails(numFiles); } readFile(i + 1); self._raise("fileloaded", [file, previewId, id, i]); }, 10); return; } isImage = fnImage(file.type, caption); $h.setHtml($status, msgLoading.replace("{index}", i + 1).replace("{files}", numFiles)); $container.addClass("file-thumb-loading"); self._initCapStatus("processing"); reader.onerror = function (evt) { self._errorHandler(evt, caption); }; reader.onload = function (theFile) { var hex, fileInfo, fileData, byte, bytes = [], contents, mime, processPreview = function (fType, ext) { if ($h.isEmpty(fType)) { // look for ascii text content contents = $h.arrayBuffer2String(reader.result); fType = $h.isSvg(contents) ? "image/svg+xml" : $h.getMimeType(hex, contents, file.type); } fileInfo = { name: caption, type: fType || "" }; if (ext && typeof File !== "undefined") { try { var fName = (fileInfo.filename = caption + "." + ext); fileProcessed = new File([file], fName, { type: fileInfo.type }); initFileData(fileProcessed); } catch (err) {} } isImage = fnImage(fType, ""); if (isImage) { var newReader = new FileReader(); newReader.onerror = function (theFileNew) { self._errorHandler(theFileNew, caption); }; newReader.onload = function (theFileNew) { if (self.isAjaxUpload && !self._raise("filebeforeload", [file, i, reader])) { fileReaderAborted = true; self._resetCaption(); reader.abort(); $status.html(""); $container.removeClass("file-thumb-loading"); self._initCapStatus("valid"); self.enable(); return; } self._previewFile(i, file, theFileNew, previewData, fileInfo); self._initFileActions(); processFileLoaded(); }; newReader.readAsDataURL(file); return; } if (self.isAjaxUpload && !self._raise("filebeforeload", [file, i, reader])) { fileReaderAborted = true; self._resetCaption(); reader.abort(); $status.html(""); $container.removeClass("file-thumb-loading"); self._initCapStatus("valid"); self.enable(); return; } self._previewFile(i, file, theFile, previewData, fileInfo); self._initFileActions(); processFileLoaded(); }; mime = file.type; fileInfo = { name: caption, type: mime }; $.each(settings, function (k, f) { if (k !== "object" && k !== "other" && typeof f === "function" && f(mime, caption)) { knownTypes++; } }); if (typeof FileTypeParser !== "undefined") { fileData = new Uint8Array(theFile.target.result); new FileTypeParser().parse(fileData).then(function (result) { processPreview((result && result.mime) || mime, (result && result.ext) || ""); }); } else { if (knownTypes === 0) { // auto detect mime types from content if no known file types detected fileData = new Uint8Array(theFile.target.result); for (j = 0; j < fileData.length; j++) { byte = fileData[j].toString(16); bytes.push(byte); } hex = bytes.join("").toLowerCase().substring(0, 8); mime = $h.getMimeType(hex, "", ""); } processPreview(mime); } }; reader.onprogress = function (data) { if (data.lengthComputable) { var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact); msg = msgProgress.setTokens({ index: i + 1, files: numFiles, percent: progress, name: caption, }); setTimeout(function () { if (!fileReaderAborted) { $h.setHtml($status, msg); } }, self.processDelay); } }; reader.readAsArrayBuffer(file); }; readFile(0); self._updateFileDetails(numFiles); }, lock: function (selectMode) { var self = this, $container = self.$container; self._resetErrors(); self.disable(); if (!selectMode && self.showCancel) { $container.find(".fileinput-cancel").show(); } if (!selectMode && self.showPause) { $container.find(".fileinput-pause").show(); } self._initCapStatus("processing"); self._raise("filelock", [self.fileManager.stack, self._getExtraData()]); return self.$element; }, unlock: function (reset) { var self = this, $container = self.$container; if (reset === undefined) { reset = true; } self.enable(); $container.removeClass("is-locked"); if (self.showCancel) { $container.find(".fileinput-cancel").hide(); } if (self.showPause) { $container.find(".fileinput-pause").hide(); } if (reset) { self._resetFileStack(); } self._initCapStatus(); self._raise("fileunlock", [self.fileManager.stack, self._getExtraData()]); return self.$element; }, resume: function () { var self = this, fm = self.fileManager, flag = false, rm = self.resumableManager; fm.bpsLog = []; fm.bps = 0; if (!self.enableResumableUpload) { return self.$element; } if (self.paused) { self._toggleResumableProgress(self.progressPauseTemplate, self.msgUploadResume); } else { flag = true; } self.paused = false; if (flag) { self._toggleResumableProgress(self.progressInfoTemplate, self.msgUploadBegin); } setTimeout(function () { rm.upload(); }, self.processDelay); return self.$element; }, paste: function (e) { var self = this, dt = e.clipboardData || e.originalEvent.clipboardData; self._addFilesFromSystem(e, dt, "filePaste"); return self.$element; }, pause: function () { var self = this, rm = self.resumableManager, xhr = self.ajaxRequests, len = xhr.length, i, pct = rm.getProgress(), actions = self.fileActionSettings, tm = self.taskManager, pool = tm.getPool(rm.id); if (!self.enableResumableUpload) { return self.$element; } else { if (pool) { pool.cancel(); } } self._raise("fileuploadpaused", [self.fileManager, rm]); if (len > 0) { for (i = 0; i < len; i += 1) { self.paused = true; xhr[i].abort(); } } if (self.showPreview) { self._getThumbs().each(function () { var $thumb = $(this), t = self._getLayoutTemplate("stats"), stats, $indicator = $thumb.find(".file-upload-indicator"); $thumb.removeClass("file-uploading"); if ($indicator.attr("title") === actions.indicatorLoadingTitle) { self._setThumbStatus($thumb, "Paused"); stats = t.setTokens({ pendingTime: self.msgPaused, uploadSpeed: "" }); self.paused = true; self._setProgress(pct, $thumb.find(".file-thumb-progress"), pct + "%", stats); } if (!self._getThumbFile($thumb)) { $thumb.find(".kv-file-remove").removeClass("disabled").removeAttr("disabled"); } }); } self._setProgress(101, self.$progress, self.msgPaused); return self.$element; }, cancel: function () { var self = this, xhr = self.ajaxRequests, rm = self.resumableManager, tm = self.taskManager, pool = rm ? tm.getPool(rm.id) : undefined, len = xhr.length, i; if (self.enableResumableUpload && pool) { pool.cancel().done(function () { self._setProgressCancelled(); }); rm.reset(); self._raise("fileuploadcancelled", [self.fileManager, rm]); } else { if (self.ajaxPool) { self.ajaxPool.cancel(); } self._raise("fileuploadcancelled", [self.fileManager]); } self._initAjax(); if (len > 0) { for (i = 0; i < len; i += 1) { self.cancelling = true; xhr[i].abort(); } } self._getThumbs().each(function () { var $thumb = $(this), $prog = $thumb.find(".file-thumb-progress"); $thumb.removeClass("file-uploading"); self._setProgress(0, $prog); $prog.hide(); if (!self._getThumbFile($thumb)) { $thumb.find(".kv-file-upload").removeClass("disabled").removeAttr("disabled"); $thumb.find(".kv-file-remove").removeClass("disabled").removeAttr("disabled"); } self.unlock(); }); setTimeout(function () { self._setProgressCancelled(); }, self.processDelay); return self.$element; }, clear: function () { var self = this, cap; if (!self._raise("fileclear")) { return; } self.clearInput = true; self.$btnUpload.removeAttr("disabled"); self ._getThumbs() .find("video,audio,img") .each(function () { $h.cleanMemory($(this)); }); self._clearFileInput(); self._resetUpload(); self.clearFileStack(); self.isDuplicateError = false; self.isPersistentError = false; self._resetErrors(true); if (self._hasInitialPreview()) { self._showFileIcon(); self._resetPreview(); self._initPreviewActions(); self.$container.removeClass("file-input-new"); } else { self._getThumbs().each(function () { self._clearObjects($(this)); }); if (self.isAjaxUpload) { self.previewCache.data = {}; } self.$preview.html(""); cap = !self.overwriteInitial && self.initialCaption.length > 0 ? self.initialCaption : ""; self.$caption.attr("title", "").val(cap); $h.addCss(self.$container, "file-input-new"); self._validateDefaultPreview(); } if (self.$container.find($h.FRAMES).length === 0) { if (!self._initCaption()) { self.$captionContainer.removeClass("icon-visible"); } } self._hideFileIcon(); if (self.focusCaptionOnClear) { self.$captionContainer.focus(); } self._setFileDropZoneTitle(); self._raise("filecleared"); return self.$element; }, reset: function () { var self = this; if (!self._raise("filereset")) { return; } self.lastProgress = 0; self._resetPreview(); self.$container.find(".fileinput-filename").text(""); $h.addCss(self.$container, "file-input-new"); if (self.getFrames().length) { self.$container.removeClass("file-input-new"); } self.clearFileStack(); self._setFileDropZoneTitle(); return self.$element; }, disable: function () { var self = this, $container = self.$container; self.isDisabled = true; self._raise("filedisabled"); self.$element.attr("disabled", "disabled"); $container.addClass("is-locked"); $h.addCss($container.find(".btn-file"), "disabled"); $container.find(".kv-fileinput-caption").addClass("file-caption-disabled"); $container.find(".fileinput-remove, .fileinput-upload, .file-preview-frame button").attr("disabled", true); self._initDragDrop(); return self.$element; }, enable: function () { var self = this, $container = self.$container; self.isDisabled = false; self._raise("fileenabled"); self.$element.removeAttr("disabled"); $container.removeClass("is-locked"); $container.find(".kv-fileinput-caption").removeClass("file-caption-disabled"); $container.find(".fileinput-remove, .fileinput-upload, .file-preview-frame button").removeAttr("disabled"); $container.find(".btn-file").removeClass("disabled"); self._initDragDrop(); return self.$element; }, upload: function () { var self = this, fm = self.fileManager, totLen = fm.count(), i, outData, tm = self.taskManager, hasExtraData = !$.isEmptyObject(self._getExtraData()); fm.bpsLog = []; fm.bps = 0; if (!self.isAjaxUpload || self.isDisabled || !self._isFileSelectionValid(totLen)) { return; } self.lastProgress = 0; self._resetUpload(); if (totLen === 0 && !hasExtraData) { self._showFileError(self.msgUploadEmpty); return; } self.cancelling = false; self.uploadInitiated = true; self._showProgress(); self.lock(); if (totLen === 0 && hasExtraData) { self._setProgress(2); self._uploadExtraOnly(); return; } if (self.enableResumableUpload) { return self.resume(); } if (self.uploadAsync || self.enableResumableUpload) { outData = self._getOutData(null); if (!self._checkBatchPreupload(outData)) { return; } self.fileBatchCompleted = false; self.uploadCache = []; $.each(self.getFileStack(), function (id) { var previewId = self._getThumbId(id); self.uploadCache.push({ id: previewId, content: null, config: null, tags: null, append: true }); }); self.$preview.find(".file-preview-initial").removeClass($h.SORT_CSS); self._initSortable(); } self._setProgress(2); self.hasInitData = false; if (self.uploadAsync) { i = 0; var pool = (self.ajaxPool = tm.addPool($h.uniqId())); $.each(self.getFileStack(), function (id) { pool.addTask(id + i, function (deferrer) { self._uploadSingle(i, id, true, deferrer); }); i++; }); pool .run(self.maxAjaxThreads) .done(function () { self._log("Async upload batch completed successfully."); self._raise("filebatchuploadsuccess", [fm.stack, self._getExtraData()]); }) .fail(function () { self._log("Async upload batch completed with errors."); self._raise("filebatchuploaderror", [fm.stack, self._getExtraData()]); }); return; } self._uploadBatch(); return self.$element; }, destroy: function () { var self = this, $form = self.$form, $cont = self.$container, $el = self.$element, ns = self.namespace; $(document).off(ns); $(window).off(ns); if ($form && $form.length) { $form.off(ns); } if (self.isAjaxUpload) { self._clearFileInput(); } self._cleanup(); self._initPreviewCache(); $el.insertBefore($cont).off(ns).removeData(); $cont.off().remove(); return $el; }, refresh: function (options) { var self = this, $el = self.$element; if (typeof options !== "object" || $h.isEmpty(options)) { options = self.options; } else { options = $.extend(true, {}, self.options, options); } self._init(options, true); self._listen(); return $el; }, zoom: function (frameId) { var self = this, $frame = self._getFrame(frameId); self._showModal($frame); }, getExif: function (frameId) { var self = this, $frame = self._getFrame(frameId); return ($frame && $frame.data("exif")) || null; }, getFrames: function (cssFilter) { var self = this, $frames; cssFilter = cssFilter || ""; $frames = self.$preview.find($h.FRAMES + cssFilter); if (self.reversePreviewOrder) { $frames = $($frames.get().reverse()); } return $frames; }, getPreview: function () { var self = this; return { content: self.initialPreview, config: self.initialPreviewConfig, tags: self.initialPreviewThumbTags, }; }, }; $.fn.fileinput = function (option) { if (!$h.hasFileAPISupport() && !$h.isIE(9)) { return; } var args = Array.apply(null, arguments), retvals = []; args.shift(); this.each(function () { var options = {}, optObj = {}; if (typeof option === "object") { options = $.extend(true, {}, $.fn.fileinput.defaults, option); optObj = option; } var self = $(this), data = self.data("fileinput"), theme = options.theme || self.data("theme") || $.fn.fileinput.defaults.theme, l = {}, t = {}, lang = options.language || self.data("language") || $.fn.fileinput.defaults.language || "en", opt; if (!data) { if (theme) { t = $.fn.fileinputThemes[theme] || {}; } if (lang !== "en" && !$h.isEmpty($.fn.fileinputLocales[lang])) { l = $.fn.fileinputLocales[lang] || {}; } opt = $.extend(true, {}, $.fn.fileinput.defaults, t, $.fn.fileinputLocales.en, l, optObj, self.data()); data = new FileInput(this, opt); self.data("fileinput", data); } if (typeof option === "string") { retvals.push(data[option].apply(data, args)); } }); switch (retvals.length) { case 0: return this; case 1: return retvals[0]; default: return retvals; } }; var IFRAME_ATTRIBS = 'class="kv-preview-data file-preview-pdf" src="{renderer}?file={data}" {style}', defBtnCss1 = "btn btn-sm btn-kv " + $h.defaultButtonCss(), defBtnCss2 = "btn " + $h.defaultButtonCss(); $.fn.fileinput.defaults = { language: "zh", bytesToKB: 1024, showCaption: true, showBrowse: true, showPreview: true, showRemove: true, showUpload: true, showUploadStats: true, showCancel: null, showPause: null, showClose: true, showUploadedThumbs: true, showConsoleLogs: false, browseOnZoneClick: false, autoReplace: false, showDescriptionClose: true, autoOrientImage: function () { // applicable for JPEG images only and non ios safari var ua = window.navigator.userAgent, webkit = !!ua.match(/WebKit/i), iOS = !!ua.match(/iP(od|ad|hone)/i), iOSSafari = iOS && webkit && !ua.match(/CriOS/i); return !iOSSafari; }, autoOrientImageInitial: true, showExifErrorLog: false, required: false, rtl: false, hideThumbnailContent: false, encodeUrl: true, focusCaptionOnBrowse: true, focusCaptionOnClear: true, generateFileId: null, previewClass: "", captionClass: "", frameClass: "krajee-default", mainClass: "", inputGroupClass: "", mainTemplate: null, fileSizeGetter: null, initialCaption: "", initialPreview: [], initialPreviewDelimiter: "*$$*", initialPreviewAsData: false, initialPreviewFileType: "image", initialPreviewConfig: [], initialPreviewThumbTags: [], previewThumbTags: {}, initialPreviewShowDelete: true, initialPreviewDownloadUrl: "", removeFromPreviewOnError: false, deleteUrl: "", deleteExtraData: {}, overwriteInitial: true, sanitizeZoomCache: function (content) { var $container = $h.createDiv(); $h.setHtml($container, content); $container.find("input,textarea,select,datalist,form,.file-thumbnail-footer").remove(); return $container.html(); }, previewZoomButtonIcons: { prev: '', next: '', toggleheader: '', fullscreen: '', borderless: '', close: '' }, previewZoomButtonClasses: { prev: "btn btn-default btn-outline-secondary btn-navigate", next: "btn btn-default btn-outline-secondary btn-navigate", rotate: defBtnCss1, toggleheader: defBtnCss1, fullscreen: defBtnCss1, borderless: defBtnCss1, close: defBtnCss1, }, previewTemplates: {}, previewContentTemplates: {}, preferIconicPreview: false, preferIconicZoomPreview: false, alwaysPreviewFileExtensions: [], rotatableFileExtensions: ["jpg", "jpeg", "png", "gif"], allowedFileTypes: null, allowedFileExtensions: null, allowedPreviewTypes: undefined, allowedPreviewMimeTypes: null, allowedPreviewExtensions: null, disabledPreviewTypes: undefined, disabledPreviewExtensions: ["msi", "exe", "com", "zip", "rar", "app", "vb", "scr"], disabledPreviewMimeTypes: null, defaultPreviewContent: null, customLayoutTags: {}, customPreviewTags: {}, previewFileIcon: '', previewFileIconClass: "file-other-icon", previewFileIconSettings: {}, previewFileExtSettings: {}, buttonLabelClass: "hidden-xs", browseIcon: ' ', browseClass: "btn btn-primary", removeIcon: '', removeClass: defBtnCss2, cancelIcon: '', cancelClass: defBtnCss2, pauseIcon: '', pauseClass: defBtnCss2, uploadIcon: '', uploadClass: defBtnCss2, uploadUrl: null, uploadUrlThumb: null, uploadAsync: true, uploadParamNames: { chunkCount: "chunkCount", chunkIndex: "chunkIndex", chunkSize: "chunkSize", chunkSizeStart: "chunkSizeStart", chunksUploaded: "chunksUploaded", fileBlob: "fileBlob", fileId: "fileId", fileName: "fileName", fileRelativePath: "fileRelativePath", fileSize: "fileSize", retryCount: "retryCount", }, maxAjaxThreads: 5, fadeDelay: 800, processDelay: 100, bitrateUpdateDelay: 500, queueDelay: 10, // must be lesser than process delay progressDelay: 0, // must be lesser than process delay enableResumableUpload: false, resumableUploadOptions: { fallback: null, testUrl: null, // used for checking status of chunks/ files previously / partially uploaded chunkSize: 2048, // in KB maxThreads: 4, maxRetries: 3, showErrorLog: true, retainErrorHistory: false, // when set to true, display complete error history always unless user explicitly resets upload skipErrorsAndProceed: false, // when set to true, files with errors will be skipped and upload will continue with other files }, uploadExtraData: {}, zoomModalHeight: 485, // 5px more than the default preview content heights set for text, html, pdf etc. minImageWidth: null, minImageHeight: null, maxImageWidth: null, maxImageHeight: null, resizeImage: false, resizePreference: "width", resizeQuality: 0.92, resizeDefaultImageType: "image/jpeg", resizeIfSizeMoreThan: 0, // in KB minFileSize: -1, maxFileSize: 0, maxMultipleFileSize: 0, maxFilePreviewSize: 25600, // 25 MB minFileCount: 0, maxFileCount: 0, maxTotalFileCount: 0, validateInitialCount: false, msgValidationErrorClass: "text-danger", msgValidationErrorIcon: ' ', msgErrorClass: "file-error-message", progressThumbClass: "progress-bar progress-bar-striped active progress-bar-animated", progressClass: "progress-bar bg-success progress-bar-success progress-bar-striped active progress-bar-animated", progressInfoClass: "progress-bar bg-info progress-bar-info progress-bar-striped active progress-bar-animated", progressCompleteClass: "progress-bar bg-success progress-bar-success", progressPauseClass: "progress-bar bg-primary progress-bar-primary progress-bar-striped active progress-bar-animated", progressErrorClass: "progress-bar bg-danger progress-bar-danger", progressUploadThreshold: 99, previewFileType: "image", elCaptionContainer: null, elCaptionText: null, elPreviewContainer: null, elPreviewImage: null, elPreviewStatus: null, elErrorContainer: null, errorCloseButton: undefined, slugCallback: null, dropZoneEnabled: true, dropZoneTitleClass: "file-drop-zone-title", fileActionSettings: {}, otherActionButtons: "", textEncoding: "UTF-8", preProcessUpload: null, ajaxSettings: { headers: { "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content }}, ajaxDeleteSettings: {}, showAjaxErrorDetails: true, mergeAjaxCallbacks: false, mergeAjaxDeleteCallbacks: false, retryErrorUploads: true, reversePreviewOrder: false, usePdfRenderer: function () { var isIE11 = !!window.MSInputMethodContext && !!document.documentMode; return !!navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/i) || isIE11; }, pdfRendererUrl: "", pdfRendererTemplate: "", tabIndexConfig: { browse: 500, remove: 500, upload: 500, cancel: null, pause: null, modal: -1, }, }; // noinspection HtmlUnknownAttribute $.fn.fileinputLocales.en = { sizeUnits: ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], bitRateUnits: ["B/s", "KB/s", "MB/s", "GB/s", "TB/s", "PB/s", "EB/s", "ZB/s", "YB/s"], fileSingle: "file", filePlural: "files", browseLabel: "Browse …", removeLabel: "Remove", removeTitle: "Clear all unprocessed files", cancelLabel: "Cancel", cancelTitle: "Abort ongoing upload", pauseLabel: "Pause", pauseTitle: "Pause ongoing upload", uploadLabel: "Upload", uploadTitle: "Upload selected files", msgNo: "No", msgNoFilesSelected: "No files selected", msgCancelled: "Cancelled", msgPaused: "Paused", msgPlaceholder: "Select {files} ...", msgZoomModalHeading: "Detailed Preview", msgFileRequired: "You must select a file to upload.", msgSizeTooSmall: 'File "{name}" ({size}) is too small and must be larger than {minSize}.', msgSizeTooLarge: 'File "{name}" ({size}) exceeds maximum allowed upload size of {maxSize}.', msgMultipleSizeTooLarge: 'Files "{name}" ({size}) exceeds maximum allowed upload size of {maxSize}.', msgFilesTooLess: "You must select at least {n} {files} to upload.", msgFilesTooMany: "Number of files selected for upload ({n}) exceeds maximum allowed limit of {m}.", msgTotalFilesTooMany: "You can upload a maximum of {m} files ({n} files detected).", msgFileNotFound: 'File "{name}" not found!', msgFileSecured: 'Security restrictions prevent reading the file "{name}".', msgFileNotReadable: 'File "{name}" is not readable.', msgFilePreviewAborted: 'File preview aborted for "{name}".', msgFilePreviewError: 'An error occurred while reading the file "{name}".', msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".', msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.', msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.', msgFileTypes: { image: "image", html: "HTML", text: "text", video: "video", audio: "audio", flash: "flash", pdf: "PDF", object: "object", }, msgUploadAborted: "The file upload was aborted", msgUploadThreshold: "Processing …", msgUploadBegin: "Initializing …", msgUploadEnd: "Done", msgUploadResume: "Resuming upload …", msgUploadEmpty: "No valid data available for upload.", msgUploadError: "Upload Error", msgDeleteError: "Delete Error", msgProgressError: "Error", msgValidationError: "Validation Error", msgLoading: "Loading file {index} of {files} …", msgProgress: "Loading file {index} of {files} - {name} - {percent}% completed.", msgSelected: "{n} {files} selected", msgProcessing: "Processing ...", msgFoldersNotAllowed: "Drag & drop files only! {n} folder(s) dropped were skipped.", msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px (detected {dimension} px).', msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px (detected {dimension} px).', msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px (detected {dimension} px).', msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px (detected {dimension} px).', msgImageResizeError: "Could not get the image dimensions to resize.", msgImageResizeException: "Error while resizing the image.
    {errors}
    ", msgAjaxError: "Something went wrong with the {operation} operation. Please try again later!", msgAjaxProgressError: "{operation} failed", msgDuplicateFile: 'File "{name}" of same size "{size}" has already been selected earlier. Skipping duplicate selection.', msgResumableUploadRetriesExceeded: "Upload aborted beyond {max} retries for file {file}! Error Details:
    {error}
    ", msgPendingTime: "{time} remaining", msgCalculatingTime: "calculating time remaining", ajaxOperations: { deleteThumb: "file delete", uploadThumb: "file upload", uploadBatch: "batch file upload", uploadExtra: "form data upload", }, dropZoneTitle: "Drag & drop files here …", dropZoneClickTitle: "
    (or click to select {files})", previewZoomButtonTitles: { prev: "View previous file", next: "View next file", rotate: "Rotate 90 deg. clockwise", toggleheader: "Toggle header", fullscreen: "Toggle full screen", borderless: "Toggle borderless mode", close: "Close detailed preview", }, }; $.fn.fileinputLocales.zh = { sizeUnits: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], bitRateUnits: ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s'], fileSingle: '文件', filePlural: '个文件', browseLabel: '选择 …', removeLabel: '移除', removeTitle: '清除选中文件', cancelLabel: '取消', cancelTitle: '取消进行中的上传', pauseLabel: '暂停', pauseTitle: '暂停上传', uploadLabel: '上传', uploadTitle: '上传选中文件', msgNo: '没有', msgNoFilesSelected: '未选择文件', msgPaused: '已暂停', msgCancelled: '取消', msgPlaceholder: '选择 {files} ...', msgZoomModalHeading: '详细预览', msgFileRequired: '必须选择一个文件上传.', msgSizeTooSmall: '文件 "{name}" ({size}) 必须大于限定大小 {minSize}.', msgSizeTooLarge: '文件 "{name}" ({size}) 超过了允许大小 {maxSize}.', msgMultipleSizeTooLarge: '文件 "{name}" ({size}) 超过了允许大小 {maxSize}.', msgFilesTooLess: '你必须选择最少 {n} {files} 来上传. ', msgFilesTooMany: '选择的上传文件个数 ({n}) 超出最大文件的限制个数 {m}.', msgTotalFilesTooMany: '你最多可以上传 {m} 个文件 (当前有{n} 个文件).', msgFileNotFound: '文件 "{name}" 未找到!', msgFileSecured: '安全限制,为了防止读取文件 "{name}".', msgFileNotReadable: '文件 "{name}" 不可读.', msgFilePreviewAborted: '取消 "{name}" 的预览.', msgFilePreviewError: '读取 "{name}" 时出现了一个错误.', msgInvalidFileName: '文件名 "{name}" 包含非法字符.', msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.', msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.', msgFileTypes: { 'image': 'image', 'html': 'HTML', 'text': 'text', 'video': 'video', 'audio': 'audio', 'flash': 'flash', 'pdf': 'PDF', 'object': 'object' }, msgUploadAborted: '该文件上传被中止', msgUploadThreshold: '处理中 …', msgUploadBegin: '正在初始化 …', msgUploadEnd: '完成', msgUploadResume: '继续上传 …', msgUploadEmpty: '无效的文件上传.', msgUploadError: '上传出错', msgDeleteError: '删除出错', msgProgressError: '上传出错', msgValidationError: '验证错误', msgLoading: '加载第 {index} 文件 共 {files} …', msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.', msgSelected: '{n} {files} 选中', msgProcessing: '处理中 ...', msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.', msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.', msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.', msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.', msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.', msgImageResizeError: '无法获取的图像尺寸调整。', msgImageResizeException: '调整图像大小时发生错误。
    {errors}
    ', msgAjaxError: '{operation} 发生错误. 请重试!', msgAjaxProgressError: '{operation} 失败', msgDuplicateFile: '文件 "{name}",大小 "{size}" 已经被选中.忽略相同的文件.', msgResumableUploadRetriesExceeded: '文件 {file} 上传失败超过 {max} 次重试 ! 错误详情:
    {error}
    ', msgPendingTime: '{time} 剩余', msgCalculatingTime: '计算剩余时间', ajaxOperations: { deleteThumb: '删除文件', uploadThumb: '上传文件', uploadBatch: '批量上传', uploadExtra: '表单数据上传' }, dropZoneTitle: '拖拽文件到这里 …
    支持多文件同时上传', dropZoneClickTitle: '
    (或点击{files}按钮选择文件)', fileActionSettings: { removeTitle: '删除文件', uploadTitle: '上传文件', downloadTitle: '下载文件', uploadRetryTitle: '重试', rotateTitle: '顺时针旋转90度', zoomTitle: '查看详情', dragTitle: '移动 / 重置', indicatorNewTitle: '没有上传', indicatorSuccessTitle: '上传', indicatorErrorTitle: '上传错误', indicatorPausedTitle: '上传已暂停', indicatorLoadingTitle: '上传 …' }, previewZoomButtonTitles: { prev: '预览上一个文件', next: '预览下一个文件', rotate: '顺时针旋转90度', toggleheader: '缩放', fullscreen: '全屏', borderless: '无边界模式', close: '关闭当前预览' } }; $.fn.fileinput.Constructor = FileInput; /** * Convert automatically file inputs with class 'file' into a bootstrap fileinput control. */ $(document).ready(function () { var $input = $("input.file[type=file]"); if ($input.length) { $input.fileinput(); } }); }); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.css ================================================ /*! * Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select) * * Copyright 2012-2020 SnapAppointments, LLC * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) */ @-webkit-keyframes bs-notify-fadeOut { 0% { opacity: 0.9; } 100% { opacity: 0; } } @-o-keyframes bs-notify-fadeOut { 0% { opacity: 0.9; } 100% { opacity: 0; } } @keyframes bs-notify-fadeOut { 0% { opacity: 0.9; } 100% { opacity: 0; } } select.bs-select-hidden, .bootstrap-select > select.bs-select-hidden, select.selectpicker { display: none !important; } .bootstrap-select { width: 220px \0; /*IE9 and below*/ vertical-align: middle; } .bootstrap-select > .dropdown-toggle { position: relative; width: 100%; text-align: right; white-space: nowrap; display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; display: inline-flex; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; } .bootstrap-select > .dropdown-toggle:after { margin-top: -1px; } .bootstrap-select > .dropdown-toggle.bs-placeholder, .bootstrap-select > .dropdown-toggle.bs-placeholder:hover, .bootstrap-select > .dropdown-toggle.bs-placeholder:focus, .bootstrap-select > .dropdown-toggle.bs-placeholder:active { color: #999; } .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:hover, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:hover, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:hover, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:hover, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:hover, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:hover, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:focus, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:focus, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:focus, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:focus, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:focus, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:focus, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-primary:active, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-secondary:active, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-success:active, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-danger:active, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-info:active, .bootstrap-select > .dropdown-toggle.bs-placeholder.btn-dark:active { color: rgba(255, 255, 255, 0.5); } .bootstrap-select > select { position: absolute !important; bottom: 0; left: 50%; display: block !important; width: 0.5px !important; height: 100% !important; padding: 0 !important; opacity: 0 !important; border: none; z-index: 0 !important; } .bootstrap-select > select.mobile-device { top: 0; left: 0; display: block !important; width: 100% !important; z-index: 2 !important; } .has-error .bootstrap-select .dropdown-toggle, .error .bootstrap-select .dropdown-toggle, .bootstrap-select.is-invalid .dropdown-toggle, .was-validated .bootstrap-select select:invalid + .dropdown-toggle { border-color: #b94a48; } .bootstrap-select.is-valid .dropdown-toggle, .was-validated .bootstrap-select select:valid + .dropdown-toggle { border-color: #28a745; } .bootstrap-select.fit-width { width: auto !important; } .bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn) { width: 220px; } .bootstrap-select > select.mobile-device:focus + .dropdown-toggle, .bootstrap-select .dropdown-toggle:focus { outline: thin dotted #333333 !important; outline: 5px auto -webkit-focus-ring-color !important; outline-offset: -2px; } .bootstrap-select.form-control { margin-bottom: 0; padding: 0; border: none; height: auto; } :not(.input-group) > .bootstrap-select.form-control:not([class*="col-"]) { width: 100%; } .bootstrap-select.form-control.input-group-btn { float: none; z-index: auto; } .form-inline .bootstrap-select, .form-inline .bootstrap-select.form-control:not([class*="col-"]) { width: auto; } .bootstrap-select:not(.input-group-btn), .bootstrap-select[class*="col-"] { float: none; display: inline-block; margin-left: 0; } .bootstrap-select.dropdown-menu-right, .bootstrap-select[class*="col-"].dropdown-menu-right, .row .bootstrap-select[class*="col-"].dropdown-menu-right { float: right; } .form-inline .bootstrap-select, .form-horizontal .bootstrap-select, .form-group .bootstrap-select { margin-bottom: 0; } .form-group-lg .bootstrap-select.form-control, .form-group-sm .bootstrap-select.form-control { padding: 0; } .form-group-lg .bootstrap-select.form-control .dropdown-toggle, .form-group-sm .bootstrap-select.form-control .dropdown-toggle { height: 100%; font-size: inherit; line-height: inherit; border-radius: inherit; } .bootstrap-select.form-control-sm .dropdown-toggle, .bootstrap-select.form-control-lg .dropdown-toggle { font-size: inherit; line-height: inherit; border-radius: inherit; } .bootstrap-select.form-control-sm .dropdown-toggle { padding: 0.25rem 0.5rem; } .bootstrap-select.form-control-lg .dropdown-toggle { padding: 0.5rem 1rem; } .form-inline .bootstrap-select .form-control { width: 100%; } .bootstrap-select.disabled, .bootstrap-select > .disabled { cursor: not-allowed; } .bootstrap-select.disabled:focus, .bootstrap-select > .disabled:focus { outline: none !important; } .bootstrap-select.bs-container { position: absolute; top: 0; left: 0; height: 0 !important; padding: 0 !important; } .bootstrap-select.bs-container .dropdown-menu { z-index: 1060; } .bootstrap-select .dropdown-toggle .filter-option { position: static; top: 0; left: 0; float: left; height: 100%; width: 100%; text-align: left; overflow: hidden; -webkit-box-flex: 0; -webkit-flex: 0 1 auto; -ms-flex: 0 1 auto; flex: 0 1 auto; } .bs3.bootstrap-select .dropdown-toggle .filter-option { padding-right: inherit; } .input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option { position: absolute; padding-top: inherit; padding-bottom: inherit; padding-left: inherit; float: none; } .input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner { padding-right: inherit; } .bootstrap-select .dropdown-toggle .filter-option-inner-inner { overflow: hidden; } .bootstrap-select .dropdown-toggle .filter-expand { width: 0 !important; float: left; opacity: 0 !important; overflow: hidden; } .bootstrap-select .dropdown-toggle .caret { position: absolute; top: 50%; right: 12px; margin-top: -2px; vertical-align: middle; } .input-group .bootstrap-select.form-control .dropdown-toggle { border-radius: inherit; } .bootstrap-select[class*="col-"] .dropdown-toggle { width: 100%; } .bootstrap-select .dropdown-menu { min-width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .bootstrap-select .dropdown-menu > .inner:focus { outline: none !important; } .bootstrap-select .dropdown-menu.inner { position: static; float: none; border: 0; padding: 0; margin: 0; border-radius: 0; -webkit-box-shadow: none; box-shadow: none; } .bootstrap-select .dropdown-menu li { position: relative; } .bootstrap-select .dropdown-menu li.active small { color: rgba(255, 255, 255, 0.5) !important; } .bootstrap-select .dropdown-menu li.disabled a { cursor: not-allowed; } .bootstrap-select .dropdown-menu li a { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .bootstrap-select .dropdown-menu li a.opt { position: relative; padding-left: 2.25em; } .bootstrap-select .dropdown-menu li a span.check-mark { display: none; } .bootstrap-select .dropdown-menu li a span.text { display: inline-block; } .bootstrap-select .dropdown-menu li small { padding-left: 0.5em; } .bootstrap-select .dropdown-menu .notify { position: absolute; bottom: 5px; width: 96%; margin: 0 2%; min-height: 26px; padding: 3px 5px; background: #f5f5f5; border: 1px solid #e3e3e3; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); pointer-events: none; opacity: 0.9; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .bootstrap-select .dropdown-menu .notify.fadeOut { -webkit-animation: 300ms linear 750ms forwards bs-notify-fadeOut; -o-animation: 300ms linear 750ms forwards bs-notify-fadeOut; animation: 300ms linear 750ms forwards bs-notify-fadeOut; } .bootstrap-select .no-results { padding: 3px; background: #f5f5f5; margin: 0 5px; white-space: nowrap; } .bootstrap-select.fit-width .dropdown-toggle .filter-option { position: static; display: inline; padding: 0; } .bootstrap-select.fit-width .dropdown-toggle .filter-option-inner, .bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner { display: inline; } .bootstrap-select.fit-width .dropdown-toggle .bs-caret:before { content: '\00a0'; } .bootstrap-select.fit-width .dropdown-toggle .caret { position: static; top: auto; margin-top: -1px; } .bootstrap-select.show-tick .dropdown-menu .selected span.check-mark { position: absolute; display: inline-block; right: 15px; top: 5px; } .bootstrap-select.show-tick .dropdown-menu li a span.text { margin-right: 34px; } .bootstrap-select .bs-ok-default:after { content: ''; display: block; width: 0.5em; height: 1em; border-style: solid; border-width: 0 0.26em 0.26em 0; -webkit-transform-style: preserve-3d; transform-style: preserve-3d; -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); -o-transform: rotate(45deg); transform: rotate(45deg); } .bootstrap-select.show-menu-arrow.open > .dropdown-toggle, .bootstrap-select.show-menu-arrow.show > .dropdown-toggle { z-index: 1061; } .bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before { content: ''; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 7px solid rgba(204, 204, 204, 0.2); position: absolute; bottom: -4px; left: 9px; display: none; } .bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after { content: ''; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 6px solid white; position: absolute; bottom: -4px; left: 10px; display: none; } .bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before { bottom: auto; top: -4px; border-top: 7px solid rgba(204, 204, 204, 0.2); border-bottom: 0; } .bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after { bottom: auto; top: -4px; border-top: 6px solid white; border-bottom: 0; } .bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before { right: 12px; left: auto; } .bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after { right: 13px; left: auto; } .bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:before, .bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:before, .bootstrap-select.show-menu-arrow.open > .dropdown-toggle .filter-option:after, .bootstrap-select.show-menu-arrow.show > .dropdown-toggle .filter-option:after { display: block; } .bs-searchbox, .bs-actionsbox, .bs-donebutton { padding: 4px 8px; } .bs-actionsbox { width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .bs-actionsbox .btn-group button { width: 50%; } .bs-donebutton { float: left; width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .bs-donebutton .btn-group button { width: 100%; } .bs-searchbox + .bs-actionsbox { padding: 0 8px 4px; } .bs-searchbox .form-control { margin-bottom: 0; width: 100%; float: none; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-select/bootstrap-select.js ================================================ /*! * Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select) * * Copyright 2012-2020 SnapAppointments, LLC * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) */ (function (root, factory) { if (root === undefined && window !== undefined) root = window; if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module unless amdModuleId is set define(["jquery"], function (a0) { return (factory(a0)); }); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(require("jquery")); } else { factory(root["jQuery"]); } }(this, function (jQuery) { (function ($) { 'use strict'; var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']; var uriAttrs = [ 'background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href' ]; var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; var DefaultWhitelist = { // Global attributes allowed on any supplied element below. '*': ['class', 'dir', 'id', 'lang', 'role', 'tabindex', 'style', ARIA_ATTRIBUTE_PATTERN], a: ['target', 'href', 'title', 'rel'], area: [], b: [], br: [], col: [], code: [], div: [], em: [], hr: [], h1: [], h2: [], h3: [], h4: [], h5: [], h6: [], i: [], img: ['src', 'alt', 'title', 'width', 'height'], li: [], ol: [], p: [], pre: [], s: [], small: [], span: [], sub: [], sup: [], strong: [], u: [], ul: [] } /** * A pattern that recognizes a commonly useful subset of URLs that are safe. * * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts */ var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi; /** * A pattern that matches safe data URLs. Only matches image, video and audio types. * * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts */ var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i; function allowedAttribute (attr, allowedAttributeList) { var attrName = attr.nodeName.toLowerCase() if ($.inArray(attrName, allowedAttributeList) !== -1) { if ($.inArray(attrName, uriAttrs) !== -1) { return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)) } return true } var regExp = $(allowedAttributeList).filter(function (index, value) { return value instanceof RegExp }) // Check if a regular expression validates the attribute. for (var i = 0, l = regExp.length; i < l; i++) { if (attrName.match(regExp[i])) { return true } } return false } function sanitizeHtml (unsafeElements, whiteList, sanitizeFn) { if (sanitizeFn && typeof sanitizeFn === 'function') { return sanitizeFn(unsafeElements); } var whitelistKeys = Object.keys(whiteList); for (var i = 0, len = unsafeElements.length; i < len; i++) { var elements = unsafeElements[i].querySelectorAll('*'); for (var j = 0, len2 = elements.length; j < len2; j++) { var el = elements[j]; var elName = el.nodeName.toLowerCase(); if (whitelistKeys.indexOf(elName) === -1) { el.parentNode.removeChild(el); continue; } var attributeList = [].slice.call(el.attributes); var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []); for (var k = 0, len3 = attributeList.length; k < len3; k++) { var attr = attributeList[k]; if (!allowedAttribute(attr, whitelistedAttributes)) { el.removeAttribute(attr.nodeName); } } } } } // Polyfill for browsers with no classList support // Remove in v2 if (!('classList' in document.createElement('_'))) { (function (view) { if (!('Element' in view)) return; var classListProp = 'classList', protoProp = 'prototype', elemCtrProto = view.Element[protoProp], objCtr = Object, classListGetter = function () { var $elem = $(this); return { add: function (classes) { classes = Array.prototype.slice.call(arguments).join(' '); return $elem.addClass(classes); }, remove: function (classes) { classes = Array.prototype.slice.call(arguments).join(' '); return $elem.removeClass(classes); }, toggle: function (classes, force) { return $elem.toggleClass(classes, force); }, contains: function (classes) { return $elem.hasClass(classes); } } }; if (objCtr.defineProperty) { var classListPropDesc = { get: classListGetter, enumerable: true, configurable: true }; try { objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } catch (ex) { // IE 8 doesn't support enumerable:true // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36 // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected if (ex.number === undefined || ex.number === -0x7FF5EC54) { classListPropDesc.enumerable = false; objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } } } else if (objCtr[protoProp].__defineGetter__) { elemCtrProto.__defineGetter__(classListProp, classListGetter); } }(window)); } var testElement = document.createElement('_'); testElement.classList.add('c1', 'c2'); if (!testElement.classList.contains('c2')) { var _add = DOMTokenList.prototype.add, _remove = DOMTokenList.prototype.remove; DOMTokenList.prototype.add = function () { Array.prototype.forEach.call(arguments, _add.bind(this)); } DOMTokenList.prototype.remove = function () { Array.prototype.forEach.call(arguments, _remove.bind(this)); } } testElement.classList.toggle('c3', false); // Polyfill for IE 10 and Firefox <24, where classList.toggle does not // support the second argument. if (testElement.classList.contains('c3')) { var _toggle = DOMTokenList.prototype.toggle; DOMTokenList.prototype.toggle = function (token, force) { if (1 in arguments && !this.contains(token) === !force) { return force; } else { return _toggle.call(this, token); } }; } testElement = null; // shallow array comparison function isEqual (array1, array2) { return array1.length === array2.length && array1.every(function (element, index) { return element === array2[index]; }); }; // if (!String.prototype.startsWith) { (function () { 'use strict'; // needed to support `apply`/`call` with `undefined`/`null` var defineProperty = (function () { // IE 8 only supports `Object.defineProperty` on DOM elements try { var object = {}; var $defineProperty = Object.defineProperty; var result = $defineProperty(object, object, object) && $defineProperty; } catch (error) { } return result; }()); var toString = {}.toString; var startsWith = function (search) { if (this == null) { throw new TypeError(); } var string = String(this); if (search && toString.call(search) == '[object RegExp]') { throw new TypeError(); } var stringLength = string.length; var searchString = String(search); var searchLength = searchString.length; var position = arguments.length > 1 ? arguments[1] : undefined; // `ToInteger` var pos = position ? Number(position) : 0; if (pos != pos) { // better `isNaN` pos = 0; } var start = Math.min(Math.max(pos, 0), stringLength); // Avoid the `indexOf` call if no match is possible if (searchLength + start > stringLength) { return false; } var index = -1; while (++index < searchLength) { if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) { return false; } } return true; }; if (defineProperty) { defineProperty(String.prototype, 'startsWith', { 'value': startsWith, 'configurable': true, 'writable': true }); } else { String.prototype.startsWith = startsWith; } }()); } if (!Object.keys) { Object.keys = function ( o, // object k, // key r // result array ) { // initialize object and result r = []; // iterate over object keys for (k in o) { // fill result array with non-prototypical keys r.hasOwnProperty.call(o, k) && r.push(k); } // return result return r; }; } if (HTMLSelectElement && !HTMLSelectElement.prototype.hasOwnProperty('selectedOptions')) { Object.defineProperty(HTMLSelectElement.prototype, 'selectedOptions', { get: function () { return this.querySelectorAll(':checked'); } }); } function getSelectedOptions (select, ignoreDisabled) { var selectedOptions = select.selectedOptions, options = [], opt; if (ignoreDisabled) { for (var i = 0, len = selectedOptions.length; i < len; i++) { opt = selectedOptions[i]; if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) { options.push(opt); } } return options; } return selectedOptions; } // much faster than $.val() function getSelectValues (select, selectedOptions) { var value = [], options = selectedOptions || select.selectedOptions, opt; for (var i = 0, len = options.length; i < len; i++) { opt = options[i]; if (!(opt.disabled || opt.parentNode.tagName === 'OPTGROUP' && opt.parentNode.disabled)) { value.push(opt.value); } } if (!select.multiple) { return !value.length ? null : value[0]; } return value; } // set data-selected on select element if the value has been programmatically selected // prior to initialization of bootstrap-select // * consider removing or replacing an alternative method * var valHooks = { useDefault: false, _set: $.valHooks.select.set }; $.valHooks.select.set = function (elem, value) { if (value && !valHooks.useDefault) $(elem).data('selected', true); return valHooks._set.apply(this, arguments); }; var changedArguments = null; var EventIsSupported = (function () { try { new Event('change'); return true; } catch (e) { return false; } })(); $.fn.triggerNative = function (eventName) { var el = this[0], event; if (el.dispatchEvent) { // for modern browsers & IE9+ if (EventIsSupported) { // For modern browsers event = new Event(eventName, { bubbles: true }); } else { // For IE since it doesn't support Event constructor event = document.createEvent('Event'); event.initEvent(eventName, true, false); } el.dispatchEvent(event); } else if (el.fireEvent) { // for IE8 event = document.createEventObject(); event.eventType = eventName; el.fireEvent('on' + eventName, event); } else { // fall back to jQuery.trigger this.trigger(eventName); } }; // function stringSearch (li, searchString, method, normalize) { var stringTypes = [ 'display', 'subtext', 'tokens' ], searchSuccess = false; for (var i = 0; i < stringTypes.length; i++) { var stringType = stringTypes[i], string = li[stringType]; if (string) { string = string.toString(); // Strip HTML tags. This isn't perfect, but it's much faster than any other method if (stringType === 'display') { string = string.replace(/<[^>]+>/g, ''); } if (normalize) string = normalizeToBase(string); string = string.toUpperCase(); if (method === 'contains') { searchSuccess = string.indexOf(searchString) >= 0; } else { searchSuccess = string.startsWith(searchString); } if (searchSuccess) break; } } return searchSuccess; } function toInteger (value) { return parseInt(value, 10) || 0; } // Borrowed from Lodash (_.deburr) /** Used to map Latin Unicode letters to basic Latin letters. */ var deburredLetters = { // Latin-1 Supplement block. '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', '\xc7': 'C', '\xe7': 'c', '\xd0': 'D', '\xf0': 'd', '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', '\xd1': 'N', '\xf1': 'n', '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', '\xc6': 'Ae', '\xe6': 'ae', '\xde': 'Th', '\xfe': 'th', '\xdf': 'ss', // Latin Extended-A block. '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', '\u0134': 'J', '\u0135': 'j', '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', '\u0163': 't', '\u0165': 't', '\u0167': 't', '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', '\u0174': 'W', '\u0175': 'w', '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', '\u0132': 'IJ', '\u0133': 'ij', '\u0152': 'Oe', '\u0153': 'oe', '\u0149': "'n", '\u017f': 's' }; /** Used to match Latin Unicode letters (excluding mathematical operators). */ var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; /** Used to compose unicode character classes. */ var rsComboMarksRange = '\\u0300-\\u036f', reComboHalfMarksRange = '\\ufe20-\\ufe2f', rsComboSymbolsRange = '\\u20d0-\\u20ff', rsComboMarksExtendedRange = '\\u1ab0-\\u1aff', rsComboMarksSupplementRange = '\\u1dc0-\\u1dff', rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange; /** Used to compose unicode capture groups. */ var rsCombo = '[' + rsComboRange + ']'; /** * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). */ var reComboMark = RegExp(rsCombo, 'g'); function deburrLetter (key) { return deburredLetters[key]; }; function normalizeToBase (string) { string = string.toString(); return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); } // List of HTML entities for escaping. var escapeMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; // Functions for escaping and unescaping strings to/from HTML interpolation. var createEscaper = function (map) { var escaper = function (match) { return map[match]; }; // Regexes for identifying a key that needs to be escaped. var source = '(?:' + Object.keys(map).join('|') + ')'; var testRegexp = RegExp(source); var replaceRegexp = RegExp(source, 'g'); return function (string) { string = string == null ? '' : '' + string; return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; }; }; var htmlEscape = createEscaper(escapeMap); /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var keyCodeMap = { 32: ' ', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 65: 'A', 66: 'B', 67: 'C', 68: 'D', 69: 'E', 70: 'F', 71: 'G', 72: 'H', 73: 'I', 74: 'J', 75: 'K', 76: 'L', 77: 'M', 78: 'N', 79: 'O', 80: 'P', 81: 'Q', 82: 'R', 83: 'S', 84: 'T', 85: 'U', 86: 'V', 87: 'W', 88: 'X', 89: 'Y', 90: 'Z', 96: '0', 97: '1', 98: '2', 99: '3', 100: '4', 101: '5', 102: '6', 103: '7', 104: '8', 105: '9' }; var keyCodes = { ESCAPE: 27, // KeyboardEvent.which value for Escape (Esc) key ENTER: 13, // KeyboardEvent.which value for Enter key SPACE: 32, // KeyboardEvent.which value for space key TAB: 9, // KeyboardEvent.which value for tab key ARROW_UP: 38, // KeyboardEvent.which value for up arrow key ARROW_DOWN: 40 // KeyboardEvent.which value for down arrow key } var version = { success: false, major: '3' }; try { version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.'); version.major = version.full[0]; version.success = true; } catch (err) { // do nothing } var selectId = 0; var EVENT_KEY = '.bs.select'; var classNames = { DISABLED: 'disabled', DIVIDER: 'divider', SHOW: 'open', DROPUP: 'dropup', MENU: 'dropdown-menu', MENURIGHT: 'dropdown-menu-right', MENULEFT: 'dropdown-menu-left', // to-do: replace with more advanced template/customization options BUTTONCLASS: 'btn-default', POPOVERHEADER: 'popover-title', ICONBASE: 'glyphicon', TICKICON: 'glyphicon-ok' } var Selector = { MENU: '.' + classNames.MENU } var elementTemplates = { div: document.createElement('div'), span: document.createElement('span'), i: document.createElement('i'), subtext: document.createElement('small'), a: document.createElement('a'), li: document.createElement('li'), whitespace: document.createTextNode('\u00A0'), fragment: document.createDocumentFragment() } elementTemplates.noResults = elementTemplates.li.cloneNode(false); elementTemplates.noResults.className = 'no-results'; elementTemplates.a.setAttribute('role', 'option'); elementTemplates.a.className = 'dropdown-item'; elementTemplates.subtext.className = 'text-muted'; elementTemplates.text = elementTemplates.span.cloneNode(false); elementTemplates.text.className = 'text'; elementTemplates.checkMark = elementTemplates.span.cloneNode(false); var REGEXP_ARROW = new RegExp(keyCodes.ARROW_UP + '|' + keyCodes.ARROW_DOWN); var REGEXP_TAB_OR_ESCAPE = new RegExp('^' + keyCodes.TAB + '$|' + keyCodes.ESCAPE); var generateOption = { li: function (content, classes, optgroup) { var li = elementTemplates.li.cloneNode(false); if (content) { if (content.nodeType === 1 || content.nodeType === 11) { li.appendChild(content); } else { li.innerHTML = content; } } if (typeof classes !== 'undefined' && classes !== '') li.className = classes; if (typeof optgroup !== 'undefined' && optgroup !== null) li.classList.add('optgroup-' + optgroup); return li; }, a: function (text, classes, inline) { var a = elementTemplates.a.cloneNode(true); if (text) { if (text.nodeType === 11) { a.appendChild(text); } else { a.insertAdjacentHTML('beforeend', text); } } if (typeof classes !== 'undefined' && classes !== '') a.classList.add.apply(a.classList, classes.split(/\s+/)); if (inline) a.setAttribute('style', inline); return a; }, text: function (options, useFragment) { var textElement = elementTemplates.text.cloneNode(false), subtextElement, iconElement; if (options.content) { textElement.innerHTML = options.content; } else { textElement.textContent = options.text; if (options.icon) { var whitespace = elementTemplates.whitespace.cloneNode(false); // need to use for icons in the button to prevent a breaking change // note: switch to span in next major release iconElement = (useFragment === true ? elementTemplates.i : elementTemplates.span).cloneNode(false); iconElement.className = this.options.iconBase + ' ' + options.icon; elementTemplates.fragment.appendChild(iconElement); elementTemplates.fragment.appendChild(whitespace); } if (options.subtext) { subtextElement = elementTemplates.subtext.cloneNode(false); subtextElement.textContent = options.subtext; textElement.appendChild(subtextElement); } } if (useFragment === true) { while (textElement.childNodes.length > 0) { elementTemplates.fragment.appendChild(textElement.childNodes[0]); } } else { elementTemplates.fragment.appendChild(textElement); } return elementTemplates.fragment; }, label: function (options) { var textElement = elementTemplates.text.cloneNode(false), subtextElement, iconElement; textElement.innerHTML = options.display; if (options.icon) { var whitespace = elementTemplates.whitespace.cloneNode(false); iconElement = elementTemplates.span.cloneNode(false); iconElement.className = this.options.iconBase + ' ' + options.icon; elementTemplates.fragment.appendChild(iconElement); elementTemplates.fragment.appendChild(whitespace); } if (options.subtext) { subtextElement = elementTemplates.subtext.cloneNode(false); subtextElement.textContent = options.subtext; textElement.appendChild(subtextElement); } elementTemplates.fragment.appendChild(textElement); return elementTemplates.fragment; } } function showNoResults (searchMatch, searchValue) { if (!searchMatch.length) { elementTemplates.noResults.innerHTML = this.options.noneResultsText.replace('{0}', '"' + htmlEscape(searchValue) + '"'); this.$menuInner[0].firstChild.appendChild(elementTemplates.noResults); } } var Selectpicker = function (element, options) { var that = this; // bootstrap-select has been initialized - revert valHooks.select.set back to its original function if (!valHooks.useDefault) { $.valHooks.select.set = valHooks._set; valHooks.useDefault = true; } this.$element = $(element); this.$newElement = null; this.$button = null; this.$menu = null; this.options = options; this.selectpicker = { main: {}, search: {}, current: {}, // current changes if a search is in progress view: {}, isSearching: false, keydown: { keyHistory: '', resetKeyHistory: { start: function () { return setTimeout(function () { that.selectpicker.keydown.keyHistory = ''; }, 800); } } } }; this.sizeInfo = {}; // If we have no title yet, try to pull it from the html title attribute (jQuery doesnt' pick it up as it's not a // data-attribute) if (this.options.title === null) { this.options.title = this.$element.attr('title'); } // Format window padding var winPad = this.options.windowPadding; if (typeof winPad === 'number') { this.options.windowPadding = [winPad, winPad, winPad, winPad]; } // Expose public methods this.val = Selectpicker.prototype.val; this.render = Selectpicker.prototype.render; this.refresh = Selectpicker.prototype.refresh; this.setStyle = Selectpicker.prototype.setStyle; this.selectAll = Selectpicker.prototype.selectAll; this.deselectAll = Selectpicker.prototype.deselectAll; this.destroy = Selectpicker.prototype.destroy; this.remove = Selectpicker.prototype.remove; this.show = Selectpicker.prototype.show; this.hide = Selectpicker.prototype.hide; this.init(); }; Selectpicker.VERSION = '1.13.18'; // part of this is duplicated in i18n/defaults-en_US.js. Make sure to update both. Selectpicker.DEFAULTS = { noneSelectedText: 'Nothing selected', noneResultsText: 'No results matched {0}', countSelectedText: function (numSelected, numTotal) { return (numSelected == 1) ? '{0} item selected' : '{0} items selected'; }, maxOptionsText: function (numAll, numGroup) { return [ (numAll == 1) ? 'Limit reached ({n} item max)' : 'Limit reached ({n} items max)', (numGroup == 1) ? 'Group limit reached ({n} item max)' : 'Group limit reached ({n} items max)' ]; }, selectAllText: 'Select All', deselectAllText: 'Deselect All', doneButton: false, doneButtonText: 'Close', multipleSeparator: ', ', styleBase: 'btn', style: classNames.BUTTONCLASS, size: 'auto', title: null, selectedTextFormat: 'values', width: false, container: false, hideDisabled: false, showSubtext: false, showIcon: true, showContent: true, dropupAuto: true, header: false, liveSearch: false, liveSearchPlaceholder: null, liveSearchNormalize: false, liveSearchStyle: 'contains', actionsBox: false, iconBase: classNames.ICONBASE, tickIcon: classNames.TICKICON, showTick: false, template: { caret: '' }, maxOptions: false, mobile: false, selectOnTab: false, dropdownAlignRight: false, windowPadding: 0, virtualScroll: 600, display: false, sanitize: true, sanitizeFn: null, whiteList: DefaultWhitelist }; Selectpicker.prototype = { constructor: Selectpicker, init: function () { var that = this, id = this.$element.attr('id'), element = this.$element[0], form = element.form; selectId++; this.selectId = 'bs-select-' + selectId; element.classList.add('bs-select-hidden'); this.multiple = this.$element.prop('multiple'); this.autofocus = this.$element.prop('autofocus'); if (element.classList.contains('show-tick')) { this.options.showTick = true; } this.$newElement = this.createDropdown(); this.buildData(); this.$element .after(this.$newElement) .prependTo(this.$newElement); // ensure select is associated with form element if it got unlinked after moving it inside newElement if (form && element.form === null) { if (!form.id) form.id = 'form-' + this.selectId; element.setAttribute('form', form.id); } this.$button = this.$newElement.children('button'); this.$menu = this.$newElement.children(Selector.MENU); this.$menuInner = this.$menu.children('.inner'); this.$searchbox = this.$menu.find('input'); element.classList.remove('bs-select-hidden'); if (this.options.dropdownAlignRight === true) this.$menu[0].classList.add(classNames.MENURIGHT); if (typeof id !== 'undefined') { this.$button.attr('data-id', id); } this.checkDisabled(); this.clickListener(); if (this.options.liveSearch) { this.liveSearchListener(); this.focusedParent = this.$searchbox[0]; } else { this.focusedParent = this.$menuInner[0]; } this.setStyle(); this.render(); this.setWidth(); if (this.options.container) { this.selectPosition(); } else { this.$element.on('hide' + EVENT_KEY, function () { if (that.isVirtual()) { // empty menu on close var menuInner = that.$menuInner[0], emptyMenu = menuInner.firstChild.cloneNode(false); // replace the existing UL with an empty one - this is faster than $.empty() or innerHTML = '' menuInner.replaceChild(emptyMenu, menuInner.firstChild); menuInner.scrollTop = 0; } }); } this.$menu.data('this', this); this.$newElement.data('this', this); if (this.options.mobile) this.mobile(); this.$newElement.on({ 'hide.bs.dropdown': function (e) { that.$element.trigger('hide' + EVENT_KEY, e); }, 'hidden.bs.dropdown': function (e) { that.$element.trigger('hidden' + EVENT_KEY, e); }, 'show.bs.dropdown': function (e) { that.$element.trigger('show' + EVENT_KEY, e); }, 'shown.bs.dropdown': function (e) { that.$element.trigger('shown' + EVENT_KEY, e); } }); if (element.hasAttribute('required')) { this.$element.on('invalid' + EVENT_KEY, function () { that.$button[0].classList.add('bs-invalid'); that.$element .on('shown' + EVENT_KEY + '.invalid', function () { that.$element .val(that.$element.val()) // set the value to hide the validation message in Chrome when menu is opened .off('shown' + EVENT_KEY + '.invalid'); }) .on('rendered' + EVENT_KEY, function () { // if select is no longer invalid, remove the bs-invalid class if (this.validity.valid) that.$button[0].classList.remove('bs-invalid'); that.$element.off('rendered' + EVENT_KEY); }); that.$button.on('blur' + EVENT_KEY, function () { that.$element.trigger('focus').trigger('blur'); that.$button.off('blur' + EVENT_KEY); }); }); } setTimeout(function () { that.buildList(); that.$element.trigger('loaded' + EVENT_KEY); }); }, createDropdown: function () { // Options // If we are multiple or showTick option is set, then add the show-tick class var showTick = (this.multiple || this.options.showTick) ? ' show-tick' : '', multiselectable = this.multiple ? ' aria-multiselectable="true"' : '', inputGroup = '', autofocus = this.autofocus ? ' autofocus' : ''; if (version.major < 4 && this.$element.parent().hasClass('input-group')) { inputGroup = ' input-group-btn'; } // Elements var drop, header = '', searchbox = '', actionsbox = '', donebutton = ''; if (this.options.header) { header = '
    ' + '' + this.options.header + '
    '; } if (this.options.liveSearch) { searchbox = ''; } if (this.multiple && this.options.actionsBox) { actionsbox = '
    ' + '
    ' + '' + '' + '
    ' + '
    '; } if (this.multiple && this.options.doneButton) { donebutton = '
    ' + '
    ' + '' + '
    ' + '
    '; } drop = ''; return $(drop); }, setPositionData: function () { this.selectpicker.view.canHighlight = []; this.selectpicker.view.size = 0; this.selectpicker.view.firstHighlightIndex = false; for (var i = 0; i < this.selectpicker.current.data.length; i++) { var li = this.selectpicker.current.data[i], canHighlight = true; if (li.type === 'divider') { canHighlight = false; li.height = this.sizeInfo.dividerHeight; } else if (li.type === 'optgroup-label') { canHighlight = false; li.height = this.sizeInfo.dropdownHeaderHeight; } else { li.height = this.sizeInfo.liHeight; } if (li.disabled) canHighlight = false; this.selectpicker.view.canHighlight.push(canHighlight); if (canHighlight) { this.selectpicker.view.size++; li.posinset = this.selectpicker.view.size; if (this.selectpicker.view.firstHighlightIndex === false) this.selectpicker.view.firstHighlightIndex = i; } li.position = (i === 0 ? 0 : this.selectpicker.current.data[i - 1].position) + li.height; } }, isVirtual: function () { return (this.options.virtualScroll !== false) && (this.selectpicker.main.elements.length >= this.options.virtualScroll) || this.options.virtualScroll === true; }, createView: function (isSearching, setSize, refresh) { var that = this, scrollTop = 0, active = [], selected, prevActive; this.selectpicker.isSearching = isSearching; this.selectpicker.current = isSearching ? this.selectpicker.search : this.selectpicker.main; this.setPositionData(); if (setSize) { if (refresh) { scrollTop = this.$menuInner[0].scrollTop; } else if (!that.multiple) { var element = that.$element[0], selectedIndex = (element.options[element.selectedIndex] || {}).liIndex; if (typeof selectedIndex === 'number' && that.options.size !== false) { var selectedData = that.selectpicker.main.data[selectedIndex], position = selectedData && selectedData.position; if (position) { scrollTop = position - ((that.sizeInfo.menuInnerHeight + that.sizeInfo.liHeight) / 2); } } } } scroll(scrollTop, true); this.$menuInner.off('scroll.createView').on('scroll.createView', function (e, updateValue) { if (!that.noScroll) scroll(this.scrollTop, updateValue); that.noScroll = false; }); function scroll (scrollTop, init) { var size = that.selectpicker.current.elements.length, chunks = [], chunkSize, chunkCount, firstChunk, lastChunk, currentChunk, prevPositions, positionIsDifferent, previousElements, menuIsDifferent = true, isVirtual = that.isVirtual(); that.selectpicker.view.scrollTop = scrollTop; chunkSize = Math.ceil(that.sizeInfo.menuInnerHeight / that.sizeInfo.liHeight * 1.5); // number of options in a chunk chunkCount = Math.round(size / chunkSize) || 1; // number of chunks for (var i = 0; i < chunkCount; i++) { var endOfChunk = (i + 1) * chunkSize; if (i === chunkCount - 1) { endOfChunk = size; } chunks[i] = [ (i) * chunkSize + (!i ? 0 : 1), endOfChunk ]; if (!size) break; if (currentChunk === undefined && scrollTop - 1 <= that.selectpicker.current.data[endOfChunk - 1].position - that.sizeInfo.menuInnerHeight) { currentChunk = i; } } if (currentChunk === undefined) currentChunk = 0; prevPositions = [that.selectpicker.view.position0, that.selectpicker.view.position1]; // always display previous, current, and next chunks firstChunk = Math.max(0, currentChunk - 1); lastChunk = Math.min(chunkCount - 1, currentChunk + 1); that.selectpicker.view.position0 = isVirtual === false ? 0 : (Math.max(0, chunks[firstChunk][0]) || 0); that.selectpicker.view.position1 = isVirtual === false ? size : (Math.min(size, chunks[lastChunk][1]) || 0); positionIsDifferent = prevPositions[0] !== that.selectpicker.view.position0 || prevPositions[1] !== that.selectpicker.view.position1; if (that.activeIndex !== undefined) { prevActive = that.selectpicker.main.elements[that.prevActiveIndex]; active = that.selectpicker.main.elements[that.activeIndex]; selected = that.selectpicker.main.elements[that.selectedIndex]; if (init) { if (that.activeIndex !== that.selectedIndex) { that.defocusItem(active); } that.activeIndex = undefined; } if (that.activeIndex && that.activeIndex !== that.selectedIndex) { that.defocusItem(selected); } } if (that.prevActiveIndex !== undefined && that.prevActiveIndex !== that.activeIndex && that.prevActiveIndex !== that.selectedIndex) { that.defocusItem(prevActive); } if (init || positionIsDifferent) { previousElements = that.selectpicker.view.visibleElements ? that.selectpicker.view.visibleElements.slice() : []; if (isVirtual === false) { that.selectpicker.view.visibleElements = that.selectpicker.current.elements; } else { that.selectpicker.view.visibleElements = that.selectpicker.current.elements.slice(that.selectpicker.view.position0, that.selectpicker.view.position1); } that.setOptionStatus(); // if searching, check to make sure the list has actually been updated before updating DOM // this prevents unnecessary repaints if (isSearching || (isVirtual === false && init)) menuIsDifferent = !isEqual(previousElements, that.selectpicker.view.visibleElements); // if virtual scroll is disabled and not searching, // menu should never need to be updated more than once if ((init || isVirtual === true) && menuIsDifferent) { var menuInner = that.$menuInner[0], menuFragment = document.createDocumentFragment(), emptyMenu = menuInner.firstChild.cloneNode(false), marginTop, marginBottom, elements = that.selectpicker.view.visibleElements, toSanitize = []; // replace the existing UL with an empty one - this is faster than $.empty() menuInner.replaceChild(emptyMenu, menuInner.firstChild); for (var i = 0, visibleElementsLen = elements.length; i < visibleElementsLen; i++) { var element = elements[i], elText, elementData; if (that.options.sanitize) { elText = element.lastChild; if (elText) { elementData = that.selectpicker.current.data[i + that.selectpicker.view.position0]; if (elementData && elementData.content && !elementData.sanitized) { toSanitize.push(elText); elementData.sanitized = true; } } } menuFragment.appendChild(element); } if (that.options.sanitize && toSanitize.length) { sanitizeHtml(toSanitize, that.options.whiteList, that.options.sanitizeFn); } if (isVirtual === true) { marginTop = (that.selectpicker.view.position0 === 0 ? 0 : that.selectpicker.current.data[that.selectpicker.view.position0 - 1].position); marginBottom = (that.selectpicker.view.position1 > size - 1 ? 0 : that.selectpicker.current.data[size - 1].position - that.selectpicker.current.data[that.selectpicker.view.position1 - 1].position); menuInner.firstChild.style.marginTop = marginTop + 'px'; menuInner.firstChild.style.marginBottom = marginBottom + 'px'; } else { menuInner.firstChild.style.marginTop = 0; menuInner.firstChild.style.marginBottom = 0; } menuInner.firstChild.appendChild(menuFragment); // if an option is encountered that is wider than the current menu width, update the menu width accordingly // switch to ResizeObserver with increased browser support if (isVirtual === true && that.sizeInfo.hasScrollBar) { var menuInnerInnerWidth = menuInner.firstChild.offsetWidth; if (init && menuInnerInnerWidth < that.sizeInfo.menuInnerInnerWidth && that.sizeInfo.totalMenuWidth > that.sizeInfo.selectWidth) { menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; } else if (menuInnerInnerWidth > that.sizeInfo.menuInnerInnerWidth) { // set to 0 to get actual width of menu that.$menu[0].style.minWidth = 0; var actualMenuWidth = menuInner.firstChild.offsetWidth; if (actualMenuWidth > that.sizeInfo.menuInnerInnerWidth) { that.sizeInfo.menuInnerInnerWidth = actualMenuWidth; menuInner.firstChild.style.minWidth = that.sizeInfo.menuInnerInnerWidth + 'px'; } // reset to default CSS styling that.$menu[0].style.minWidth = ''; } } } } that.prevActiveIndex = that.activeIndex; if (!that.options.liveSearch) { that.$menuInner.trigger('focus'); } else if (isSearching && init) { var index = 0, newActive; if (!that.selectpicker.view.canHighlight[index]) { index = 1 + that.selectpicker.view.canHighlight.slice(1).indexOf(true); } newActive = that.selectpicker.view.visibleElements[index]; that.defocusItem(that.selectpicker.view.currentActive); that.activeIndex = (that.selectpicker.current.data[index] || {}).index; that.focusItem(newActive); } } $(window) .off('resize' + EVENT_KEY + '.' + this.selectId + '.createView') .on('resize' + EVENT_KEY + '.' + this.selectId + '.createView', function () { var isActive = that.$newElement.hasClass(classNames.SHOW); if (isActive) scroll(that.$menuInner[0].scrollTop); }); }, focusItem: function (li, liData, noStyle) { if (li) { liData = liData || this.selectpicker.main.data[this.activeIndex]; var a = li.firstChild; if (a) { a.setAttribute('aria-setsize', this.selectpicker.view.size); a.setAttribute('aria-posinset', liData.posinset); if (noStyle !== true) { this.focusedParent.setAttribute('aria-activedescendant', a.id); li.classList.add('active'); a.classList.add('active'); } } } }, defocusItem: function (li) { if (li) { li.classList.remove('active'); if (li.firstChild) li.firstChild.classList.remove('active'); } }, setPlaceholder: function () { var that = this, updateIndex = false; if (this.options.title && !this.multiple) { if (!this.selectpicker.view.titleOption) this.selectpicker.view.titleOption = document.createElement('option'); // this option doesn't create a new
  • element, but does add a new option at the start, // so startIndex should increase to prevent having to check every option for the bs-title-option class updateIndex = true; var element = this.$element[0], selectTitleOption = false, titleNotAppended = !this.selectpicker.view.titleOption.parentNode, selectedIndex = element.selectedIndex, selectedOption = element.options[selectedIndex], navigation = window.performance && window.performance.getEntriesByType('navigation'), // Safari doesn't support getEntriesByType('navigation') - fall back to performance.navigation isNotBackForward = (navigation && navigation.length) ? navigation[0].type !== 'back_forward' : window.performance.navigation.type !== 2; if (titleNotAppended) { // Use native JS to prepend option (faster) this.selectpicker.view.titleOption.className = 'bs-title-option'; this.selectpicker.view.titleOption.value = ''; // Check if selected or data-selected attribute is already set on an option. If not, select the titleOption option. // the selected item may have been changed by user or programmatically before the bootstrap select plugin runs, // if so, the select will have the data-selected attribute selectTitleOption = !selectedOption || (selectedIndex === 0 && selectedOption.defaultSelected === false && this.$element.data('selected') === undefined); } if (titleNotAppended || this.selectpicker.view.titleOption.index !== 0) { element.insertBefore(this.selectpicker.view.titleOption, element.firstChild); } // Set selected *after* appending to select, // otherwise the option doesn't get selected in IE // set using selectedIndex, as setting the selected attr to true here doesn't work in IE11 if (selectTitleOption && isNotBackForward) { element.selectedIndex = 0; } else if (document.readyState !== 'complete') { // if navigation type is back_forward, there's a chance the select will have its value set by BFCache // wait for that value to be set, then run render again window.addEventListener('pageshow', function () { if (that.selectpicker.view.displayedValue !== element.value) that.render(); }); } } return updateIndex; }, buildData: function () { var optionSelector = ':not([hidden]):not([data-hidden="true"])', mainData = [], optID = 0, startIndex = this.setPlaceholder() ? 1 : 0; // append the titleOption if necessary and skip the first option in the loop if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; var selectOptions = this.$element[0].querySelectorAll('select > *' + optionSelector); function addDivider (config) { var previousData = mainData[mainData.length - 1]; // ensure optgroup doesn't create back-to-back dividers if ( previousData && previousData.type === 'divider' && (previousData.optID || config.optID) ) { return; } config = config || {}; config.type = 'divider'; mainData.push(config); } function addOption (option, config) { config = config || {}; config.divider = option.getAttribute('data-divider') === 'true'; if (config.divider) { addDivider({ optID: config.optID }); } else { var liIndex = mainData.length, cssText = option.style.cssText, inlineStyle = cssText ? htmlEscape(cssText) : '', optionClass = (option.className || '') + (config.optgroupClass || ''); if (config.optID) optionClass = 'opt ' + optionClass; config.optionClass = optionClass.trim(); config.inlineStyle = inlineStyle; config.text = option.textContent; config.content = option.getAttribute('data-content'); config.tokens = option.getAttribute('data-tokens'); config.subtext = option.getAttribute('data-subtext'); config.icon = option.getAttribute('data-icon'); option.liIndex = liIndex; config.display = config.content || config.text; config.type = 'option'; config.index = liIndex; config.option = option; config.selected = !!option.selected; config.disabled = config.disabled || !!option.disabled; mainData.push(config); } } function addOptgroup (index, selectOptions) { var optgroup = selectOptions[index], // skip placeholder option previous = index - 1 < startIndex ? false : selectOptions[index - 1], next = selectOptions[index + 1], options = optgroup.querySelectorAll('option' + optionSelector); if (!options.length) return; var config = { display: htmlEscape(optgroup.label), subtext: optgroup.getAttribute('data-subtext'), icon: optgroup.getAttribute('data-icon'), type: 'optgroup-label', optgroupClass: ' ' + (optgroup.className || '') }, headerIndex, lastIndex; optID++; if (previous) { addDivider({ optID: optID }); } config.optID = optID; mainData.push(config); for (var j = 0, len = options.length; j < len; j++) { var option = options[j]; if (j === 0) { headerIndex = mainData.length - 1; lastIndex = headerIndex + len; } addOption(option, { headerIndex: headerIndex, lastIndex: lastIndex, optID: config.optID, optgroupClass: config.optgroupClass, disabled: optgroup.disabled }); } if (next) { addDivider({ optID: optID }); } } for (var len = selectOptions.length, i = startIndex; i < len; i++) { var item = selectOptions[i]; if (item.tagName !== 'OPTGROUP') { addOption(item, {}); } else { addOptgroup(i, selectOptions); } } this.selectpicker.main.data = this.selectpicker.current.data = mainData; }, buildList: function () { var that = this, selectData = this.selectpicker.main.data, mainElements = [], widestOptionLength = 0; if ((that.options.showTick || that.multiple) && !elementTemplates.checkMark.parentNode) { elementTemplates.checkMark.className = this.options.iconBase + ' ' + that.options.tickIcon + ' check-mark'; elementTemplates.a.appendChild(elementTemplates.checkMark); } function buildElement (item) { var liElement, combinedLength = 0; switch (item.type) { case 'divider': liElement = generateOption.li( false, classNames.DIVIDER, (item.optID ? item.optID + 'div' : undefined) ); break; case 'option': liElement = generateOption.li( generateOption.a( generateOption.text.call(that, item), item.optionClass, item.inlineStyle ), '', item.optID ); if (liElement.firstChild) { liElement.firstChild.id = that.selectId + '-' + item.index; } break; case 'optgroup-label': liElement = generateOption.li( generateOption.label.call(that, item), 'dropdown-header' + item.optgroupClass, item.optID ); break; } item.element = liElement; mainElements.push(liElement); // count the number of characters in the option - not perfect, but should work in most cases if (item.display) combinedLength += item.display.length; if (item.subtext) combinedLength += item.subtext.length; // if there is an icon, ensure this option's width is checked if (item.icon) combinedLength += 1; if (combinedLength > widestOptionLength) { widestOptionLength = combinedLength; // guess which option is the widest // use this when calculating menu width // not perfect, but it's fast, and the width will be updating accordingly when scrolling that.selectpicker.view.widestOption = mainElements[mainElements.length - 1]; } } for (var len = selectData.length, i = 0; i < len; i++) { var item = selectData[i]; buildElement(item); } this.selectpicker.main.elements = this.selectpicker.current.elements = mainElements; }, findLis: function () { return this.$menuInner.find('.inner > li'); }, render: function () { var that = this, element = this.$element[0], // ensure titleOption is appended and selected (if necessary) before getting selectedOptions placeholderSelected = this.setPlaceholder() && element.selectedIndex === 0, selectedOptions = getSelectedOptions(element, this.options.hideDisabled), selectedCount = selectedOptions.length, button = this.$button[0], buttonInner = button.querySelector('.filter-option-inner-inner'), multipleSeparator = document.createTextNode(this.options.multipleSeparator), titleFragment = elementTemplates.fragment.cloneNode(false), showCount, countMax, hasContent = false; button.classList.toggle('bs-placeholder', that.multiple ? !selectedCount : !getSelectValues(element, selectedOptions)); if (!that.multiple && selectedOptions.length === 1) { that.selectpicker.view.displayedValue = getSelectValues(element, selectedOptions); } if (this.options.selectedTextFormat === 'static') { titleFragment = generateOption.text.call(this, { text: this.options.title }, true); } else { showCount = this.multiple && this.options.selectedTextFormat.indexOf('count') !== -1 && selectedCount > 1; // determine if the number of selected options will be shown (showCount === true) if (showCount) { countMax = this.options.selectedTextFormat.split('>'); showCount = (countMax.length > 1 && selectedCount > countMax[1]) || (countMax.length === 1 && selectedCount >= 2); } // only loop through all selected options if the count won't be shown if (showCount === false) { if (!placeholderSelected) { for (var selectedIndex = 0; selectedIndex < selectedCount; selectedIndex++) { if (selectedIndex < 50) { var option = selectedOptions[selectedIndex], thisData = this.selectpicker.main.data[option.liIndex], titleOptions = {}; if (this.multiple && selectedIndex > 0) { titleFragment.appendChild(multipleSeparator.cloneNode(false)); } if (option.title) { titleOptions.text = option.title; } else if (thisData) { if (thisData.content && that.options.showContent) { titleOptions.content = thisData.content.toString(); hasContent = true; } else { if (that.options.showIcon) { titleOptions.icon = thisData.icon; } if (that.options.showSubtext && !that.multiple && thisData.subtext) titleOptions.subtext = ' ' + thisData.subtext; titleOptions.text = option.textContent.trim(); } } titleFragment.appendChild(generateOption.text.call(this, titleOptions, true)); } else { break; } } // add ellipsis if (selectedCount > 49) { titleFragment.appendChild(document.createTextNode('...')); } } } else { var optionSelector = ':not([hidden]):not([data-hidden="true"]):not([data-divider="true"])'; if (this.options.hideDisabled) optionSelector += ':not(:disabled)'; // If this is a multiselect, and selectedTextFormat is count, then show 1 of 2 selected, etc. var totalCount = this.$element[0].querySelectorAll('select > option' + optionSelector + ', optgroup' + optionSelector + ' option' + optionSelector).length, tr8nText = (typeof this.options.countSelectedText === 'function') ? this.options.countSelectedText(selectedCount, totalCount) : this.options.countSelectedText; titleFragment = generateOption.text.call(this, { text: tr8nText.replace('{0}', selectedCount.toString()).replace('{1}', totalCount.toString()) }, true); } } if (this.options.title == undefined) { // use .attr to ensure undefined is returned if title attribute is not set this.options.title = this.$element.attr('title'); } // If the select doesn't have a title, then use the default, or if nothing is set at all, use noneSelectedText if (!titleFragment.childNodes.length) { titleFragment = generateOption.text.call(this, { text: typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText }, true); } // strip all HTML tags and trim the result, then unescape any escaped tags button.title = titleFragment.textContent.replace(/<[^>]*>?/g, '').trim(); if (this.options.sanitize && hasContent) { sanitizeHtml([titleFragment], that.options.whiteList, that.options.sanitizeFn); } buttonInner.innerHTML = ''; buttonInner.appendChild(titleFragment); if (version.major < 4 && this.$newElement[0].classList.contains('bs3-has-addon')) { var filterExpand = button.querySelector('.filter-expand'), clone = buttonInner.cloneNode(true); clone.className = 'filter-expand'; if (filterExpand) { button.replaceChild(clone, filterExpand); } else { button.appendChild(clone); } } this.$element.trigger('rendered' + EVENT_KEY); }, /** * @param [style] * @param [status] */ setStyle: function (newStyle, status) { var button = this.$button[0], newElement = this.$newElement[0], style = this.options.style.trim(), buttonClass; if (this.$element.attr('class')) { this.$newElement.addClass(this.$element.attr('class').replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi, '')); } if (version.major < 4) { newElement.classList.add('bs3'); if (newElement.parentNode.classList && newElement.parentNode.classList.contains('input-group') && (newElement.previousElementSibling || newElement.nextElementSibling) && (newElement.previousElementSibling || newElement.nextElementSibling).classList.contains('input-group-addon') ) { newElement.classList.add('bs3-has-addon'); } } if (newStyle) { buttonClass = newStyle.trim(); } else { buttonClass = style; } if (status == 'add') { if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); } else if (status == 'remove') { if (buttonClass) button.classList.remove.apply(button.classList, buttonClass.split(' ')); } else { if (style) button.classList.remove.apply(button.classList, style.split(' ')); if (buttonClass) button.classList.add.apply(button.classList, buttonClass.split(' ')); } }, liHeight: function (refresh) { if (!refresh && (this.options.size === false || Object.keys(this.sizeInfo).length)) return; var newElement = elementTemplates.div.cloneNode(false), menu = elementTemplates.div.cloneNode(false), menuInner = elementTemplates.div.cloneNode(false), menuInnerInner = document.createElement('ul'), divider = elementTemplates.li.cloneNode(false), dropdownHeader = elementTemplates.li.cloneNode(false), li, a = elementTemplates.a.cloneNode(false), text = elementTemplates.span.cloneNode(false), header = this.options.header && this.$menu.find('.' + classNames.POPOVERHEADER).length > 0 ? this.$menu.find('.' + classNames.POPOVERHEADER)[0].cloneNode(true) : null, search = this.options.liveSearch ? elementTemplates.div.cloneNode(false) : null, actions = this.options.actionsBox && this.multiple && this.$menu.find('.bs-actionsbox').length > 0 ? this.$menu.find('.bs-actionsbox')[0].cloneNode(true) : null, doneButton = this.options.doneButton && this.multiple && this.$menu.find('.bs-donebutton').length > 0 ? this.$menu.find('.bs-donebutton')[0].cloneNode(true) : null, firstOption = this.$element.find('option')[0]; this.sizeInfo.selectWidth = this.$newElement[0].offsetWidth; text.className = 'text'; a.className = 'dropdown-item ' + (firstOption ? firstOption.className : ''); newElement.className = this.$menu[0].parentNode.className + ' ' + classNames.SHOW; newElement.style.width = 0; // ensure button width doesn't affect natural width of menu when calculating if (this.options.width === 'auto') menu.style.minWidth = 0; menu.className = classNames.MENU + ' ' + classNames.SHOW; menuInner.className = 'inner ' + classNames.SHOW; menuInnerInner.className = classNames.MENU + ' inner ' + (version.major === '4' ? classNames.SHOW : ''); divider.className = classNames.DIVIDER; dropdownHeader.className = 'dropdown-header'; text.appendChild(document.createTextNode('\u200b')); if (this.selectpicker.current.data.length) { for (var i = 0; i < this.selectpicker.current.data.length; i++) { var data = this.selectpicker.current.data[i]; if (data.type === 'option') { li = data.element; break; } } } else { li = elementTemplates.li.cloneNode(false); a.appendChild(text); li.appendChild(a); } dropdownHeader.appendChild(text.cloneNode(true)); if (this.selectpicker.view.widestOption) { menuInnerInner.appendChild(this.selectpicker.view.widestOption.cloneNode(true)); } menuInnerInner.appendChild(li); menuInnerInner.appendChild(divider); menuInnerInner.appendChild(dropdownHeader); if (header) menu.appendChild(header); if (search) { var input = document.createElement('input'); search.className = 'bs-searchbox'; input.className = 'form-control'; search.appendChild(input); menu.appendChild(search); } if (actions) menu.appendChild(actions); menuInner.appendChild(menuInnerInner); menu.appendChild(menuInner); if (doneButton) menu.appendChild(doneButton); newElement.appendChild(menu); document.body.appendChild(newElement); var liHeight = li.offsetHeight, dropdownHeaderHeight = dropdownHeader ? dropdownHeader.offsetHeight : 0, headerHeight = header ? header.offsetHeight : 0, searchHeight = search ? search.offsetHeight : 0, actionsHeight = actions ? actions.offsetHeight : 0, doneButtonHeight = doneButton ? doneButton.offsetHeight : 0, dividerHeight = $(divider).outerHeight(true), // fall back to jQuery if getComputedStyle is not supported menuStyle = window.getComputedStyle ? window.getComputedStyle(menu) : false, menuWidth = menu.offsetWidth, $menu = menuStyle ? null : $(menu), menuPadding = { vert: toInteger(menuStyle ? menuStyle.paddingTop : $menu.css('paddingTop')) + toInteger(menuStyle ? menuStyle.paddingBottom : $menu.css('paddingBottom')) + toInteger(menuStyle ? menuStyle.borderTopWidth : $menu.css('borderTopWidth')) + toInteger(menuStyle ? menuStyle.borderBottomWidth : $menu.css('borderBottomWidth')), horiz: toInteger(menuStyle ? menuStyle.paddingLeft : $menu.css('paddingLeft')) + toInteger(menuStyle ? menuStyle.paddingRight : $menu.css('paddingRight')) + toInteger(menuStyle ? menuStyle.borderLeftWidth : $menu.css('borderLeftWidth')) + toInteger(menuStyle ? menuStyle.borderRightWidth : $menu.css('borderRightWidth')) }, menuExtras = { vert: menuPadding.vert + toInteger(menuStyle ? menuStyle.marginTop : $menu.css('marginTop')) + toInteger(menuStyle ? menuStyle.marginBottom : $menu.css('marginBottom')) + 2, horiz: menuPadding.horiz + toInteger(menuStyle ? menuStyle.marginLeft : $menu.css('marginLeft')) + toInteger(menuStyle ? menuStyle.marginRight : $menu.css('marginRight')) + 2 }, scrollBarWidth; menuInner.style.overflowY = 'scroll'; scrollBarWidth = menu.offsetWidth - menuWidth; document.body.removeChild(newElement); this.sizeInfo.liHeight = liHeight; this.sizeInfo.dropdownHeaderHeight = dropdownHeaderHeight; this.sizeInfo.headerHeight = headerHeight; this.sizeInfo.searchHeight = searchHeight; this.sizeInfo.actionsHeight = actionsHeight; this.sizeInfo.doneButtonHeight = doneButtonHeight; this.sizeInfo.dividerHeight = dividerHeight; this.sizeInfo.menuPadding = menuPadding; this.sizeInfo.menuExtras = menuExtras; this.sizeInfo.menuWidth = menuWidth; this.sizeInfo.menuInnerInnerWidth = menuWidth - menuPadding.horiz; this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth; this.sizeInfo.scrollBarWidth = scrollBarWidth; this.sizeInfo.selectHeight = this.$newElement[0].offsetHeight; this.setPositionData(); }, getSelectPosition: function () { var that = this, $window = $(window), pos = that.$newElement.offset(), $container = $(that.options.container), containerPos; if (that.options.container && $container.length && !$container.is('body')) { containerPos = $container.offset(); containerPos.top += parseInt($container.css('borderTopWidth')); containerPos.left += parseInt($container.css('borderLeftWidth')); } else { containerPos = { top: 0, left: 0 }; } var winPad = that.options.windowPadding; this.sizeInfo.selectOffsetTop = pos.top - containerPos.top - $window.scrollTop(); this.sizeInfo.selectOffsetBot = $window.height() - this.sizeInfo.selectOffsetTop - this.sizeInfo.selectHeight - containerPos.top - winPad[2]; this.sizeInfo.selectOffsetLeft = pos.left - containerPos.left - $window.scrollLeft(); this.sizeInfo.selectOffsetRight = $window.width() - this.sizeInfo.selectOffsetLeft - this.sizeInfo.selectWidth - containerPos.left - winPad[1]; this.sizeInfo.selectOffsetTop -= winPad[0]; this.sizeInfo.selectOffsetLeft -= winPad[3]; }, setMenuSize: function (isAuto) { this.getSelectPosition(); var selectWidth = this.sizeInfo.selectWidth, liHeight = this.sizeInfo.liHeight, headerHeight = this.sizeInfo.headerHeight, searchHeight = this.sizeInfo.searchHeight, actionsHeight = this.sizeInfo.actionsHeight, doneButtonHeight = this.sizeInfo.doneButtonHeight, divHeight = this.sizeInfo.dividerHeight, menuPadding = this.sizeInfo.menuPadding, menuInnerHeight, menuHeight, divLength = 0, minHeight, _minHeight, maxHeight, menuInnerMinHeight, estimate, isDropup; if (this.options.dropupAuto) { // Get the estimated height of the menu without scrollbars. // This is useful for smaller menus, where there might be plenty of room // below the button without setting dropup, but we can't know // the exact height of the menu until createView is called later estimate = liHeight * this.selectpicker.current.elements.length + menuPadding.vert; isDropup = this.sizeInfo.selectOffsetTop - this.sizeInfo.selectOffsetBot > this.sizeInfo.menuExtras.vert && estimate + this.sizeInfo.menuExtras.vert + 50 > this.sizeInfo.selectOffsetBot; // ensure dropup doesn't change while searching (so menu doesn't bounce back and forth) if (this.selectpicker.isSearching === true) { isDropup = this.selectpicker.dropup; } this.$newElement.toggleClass(classNames.DROPUP, isDropup); this.selectpicker.dropup = isDropup; } if (this.options.size === 'auto') { _minHeight = this.selectpicker.current.elements.length > 3 ? this.sizeInfo.liHeight * 3 + this.sizeInfo.menuExtras.vert - 2 : 0; menuHeight = this.sizeInfo.selectOffsetBot - this.sizeInfo.menuExtras.vert; minHeight = _minHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; menuInnerMinHeight = Math.max(_minHeight - menuPadding.vert, 0); if (this.$newElement.hasClass(classNames.DROPUP)) { menuHeight = this.sizeInfo.selectOffsetTop - this.sizeInfo.menuExtras.vert; } maxHeight = menuHeight; menuInnerHeight = menuHeight - headerHeight - searchHeight - actionsHeight - doneButtonHeight - menuPadding.vert; } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { for (var i = 0; i < this.options.size; i++) { if (this.selectpicker.current.data[i].type === 'divider') divLength++; } menuHeight = liHeight * this.options.size + divLength * divHeight + menuPadding.vert; menuInnerHeight = menuHeight - menuPadding.vert; maxHeight = menuHeight + headerHeight + searchHeight + actionsHeight + doneButtonHeight; minHeight = menuInnerMinHeight = ''; } this.$menu.css({ 'max-height': maxHeight + 'px', 'overflow': 'hidden', 'min-height': minHeight + 'px' }); this.$menuInner.css({ 'max-height': menuInnerHeight + 'px', 'overflow-y': 'auto', 'min-height': menuInnerMinHeight + 'px' }); // ensure menuInnerHeight is always a positive number to prevent issues calculating chunkSize in createView this.sizeInfo.menuInnerHeight = Math.max(menuInnerHeight, 1); if (this.selectpicker.current.data.length && this.selectpicker.current.data[this.selectpicker.current.data.length - 1].position > this.sizeInfo.menuInnerHeight) { this.sizeInfo.hasScrollBar = true; this.sizeInfo.totalMenuWidth = this.sizeInfo.menuWidth + this.sizeInfo.scrollBarWidth; } if (this.options.dropdownAlignRight === 'auto') { this.$menu.toggleClass(classNames.MENURIGHT, this.sizeInfo.selectOffsetLeft > this.sizeInfo.selectOffsetRight && this.sizeInfo.selectOffsetRight < (this.sizeInfo.totalMenuWidth - selectWidth)); } if (this.dropdown && this.dropdown._popper) this.dropdown._popper.update(); }, setSize: function (refresh) { this.liHeight(refresh); if (this.options.header) this.$menu.css('padding-top', 0); if (this.options.size !== false) { var that = this, $window = $(window); this.setMenuSize(); if (this.options.liveSearch) { this.$searchbox .off('input.setMenuSize propertychange.setMenuSize') .on('input.setMenuSize propertychange.setMenuSize', function () { return that.setMenuSize(); }); } if (this.options.size === 'auto') { $window .off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize') .on('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize', function () { return that.setMenuSize(); }); } else if (this.options.size && this.options.size != 'auto' && this.selectpicker.current.elements.length > this.options.size) { $window.off('resize' + EVENT_KEY + '.' + this.selectId + '.setMenuSize' + ' scroll' + EVENT_KEY + '.' + this.selectId + '.setMenuSize'); } } this.createView(false, true, refresh); }, setWidth: function () { var that = this; if (this.options.width === 'auto') { requestAnimationFrame(function () { that.$menu.css('min-width', '0'); that.$element.on('loaded' + EVENT_KEY, function () { that.liHeight(); that.setMenuSize(); // Get correct width if element is hidden var $selectClone = that.$newElement.clone().appendTo('body'), btnWidth = $selectClone.css('width', 'auto').children('button').outerWidth(); $selectClone.remove(); // Set width to whatever's larger, button title or longest option that.sizeInfo.selectWidth = Math.max(that.sizeInfo.totalMenuWidth, btnWidth); that.$newElement.css('width', that.sizeInfo.selectWidth + 'px'); }); }); } else if (this.options.width === 'fit') { // Remove inline min-width so width can be changed from 'auto' this.$menu.css('min-width', ''); this.$newElement.css('width', '').addClass('fit-width'); } else if (this.options.width) { // Remove inline min-width so width can be changed from 'auto' this.$menu.css('min-width', ''); this.$newElement.css('width', this.options.width); } else { // Remove inline min-width/width so width can be changed this.$menu.css('min-width', ''); this.$newElement.css('width', ''); } // Remove fit-width class if width is changed programmatically if (this.$newElement.hasClass('fit-width') && this.options.width !== 'fit') { this.$newElement[0].classList.remove('fit-width'); } }, selectPosition: function () { this.$bsContainer = $('
    '); var that = this, $container = $(this.options.container), pos, containerPos, actualHeight, getPlacement = function ($element) { var containerPosition = {}, // fall back to dropdown's default display setting if display is not manually set display = that.options.display || ( // Bootstrap 3 doesn't have $.fn.dropdown.Constructor.Default $.fn.dropdown.Constructor.Default ? $.fn.dropdown.Constructor.Default.display : false ); that.$bsContainer.addClass($element.attr('class').replace(/form-control|fit-width/gi, '')).toggleClass(classNames.DROPUP, $element.hasClass(classNames.DROPUP)); pos = $element.offset(); if (!$container.is('body')) { containerPos = $container.offset(); containerPos.top += parseInt($container.css('borderTopWidth')) - $container.scrollTop(); containerPos.left += parseInt($container.css('borderLeftWidth')) - $container.scrollLeft(); } else { containerPos = { top: 0, left: 0 }; } actualHeight = $element.hasClass(classNames.DROPUP) ? 0 : $element[0].offsetHeight; // Bootstrap 4+ uses Popper for menu positioning if (version.major < 4 || display === 'static') { containerPosition.top = pos.top - containerPos.top + actualHeight; containerPosition.left = pos.left - containerPos.left; } containerPosition.width = $element[0].offsetWidth; that.$bsContainer.css(containerPosition); }; this.$button.on('click.bs.dropdown.data-api', function () { if (that.isDisabled()) { return; } getPlacement(that.$newElement); that.$bsContainer .appendTo(that.options.container) .toggleClass(classNames.SHOW, !that.$button.hasClass(classNames.SHOW)) .append(that.$menu); }); $(window) .off('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId) .on('resize' + EVENT_KEY + '.' + this.selectId + ' scroll' + EVENT_KEY + '.' + this.selectId, function () { var isActive = that.$newElement.hasClass(classNames.SHOW); if (isActive) getPlacement(that.$newElement); }); this.$element.on('hide' + EVENT_KEY, function () { that.$menu.data('height', that.$menu.height()); that.$bsContainer.detach(); }); }, setOptionStatus: function (selectedOnly) { var that = this; that.noScroll = false; if (that.selectpicker.view.visibleElements && that.selectpicker.view.visibleElements.length) { for (var i = 0; i < that.selectpicker.view.visibleElements.length; i++) { var liData = that.selectpicker.current.data[i + that.selectpicker.view.position0], option = liData.option; if (option) { if (selectedOnly !== true) { that.setDisabled( liData.index, liData.disabled ); } that.setSelected( liData.index, option.selected ); } } } }, /** * @param {number} index - the index of the option that is being changed * @param {boolean} selected - true if the option is being selected, false if being deselected */ setSelected: function (index, selected) { var li = this.selectpicker.main.elements[index], liData = this.selectpicker.main.data[index], activeIndexIsSet = this.activeIndex !== undefined, thisIsActive = this.activeIndex === index, prevActive, a, // if current option is already active // OR // if the current option is being selected, it's NOT multiple, and // activeIndex is undefined: // - when the menu is first being opened, OR // - after a search has been performed, OR // - when retainActive is false when selecting a new option (i.e. index of the newly selected option is not the same as the current activeIndex) keepActive = thisIsActive || (selected && !this.multiple && !activeIndexIsSet); liData.selected = selected; a = li.firstChild; if (selected) { this.selectedIndex = index; } li.classList.toggle('selected', selected); if (keepActive) { this.focusItem(li, liData); this.selectpicker.view.currentActive = li; this.activeIndex = index; } else { this.defocusItem(li); } if (a) { a.classList.toggle('selected', selected); if (selected) { a.setAttribute('aria-selected', true); } else { if (this.multiple) { a.setAttribute('aria-selected', false); } else { a.removeAttribute('aria-selected'); } } } if (!keepActive && !activeIndexIsSet && selected && this.prevActiveIndex !== undefined) { prevActive = this.selectpicker.main.elements[this.prevActiveIndex]; this.defocusItem(prevActive); } }, /** * @param {number} index - the index of the option that is being disabled * @param {boolean} disabled - true if the option is being disabled, false if being enabled */ setDisabled: function (index, disabled) { var li = this.selectpicker.main.elements[index], a; this.selectpicker.main.data[index].disabled = disabled; a = li.firstChild; li.classList.toggle(classNames.DISABLED, disabled); if (a) { if (version.major === '4') a.classList.toggle(classNames.DISABLED, disabled); if (disabled) { a.setAttribute('aria-disabled', disabled); a.setAttribute('tabindex', -1); } else { a.removeAttribute('aria-disabled'); a.setAttribute('tabindex', 0); } } }, isDisabled: function () { return this.$element[0].disabled; }, checkDisabled: function () { if (this.isDisabled()) { this.$newElement[0].classList.add(classNames.DISABLED); this.$button.addClass(classNames.DISABLED).attr('aria-disabled', true); } else { if (this.$button[0].classList.contains(classNames.DISABLED)) { this.$newElement[0].classList.remove(classNames.DISABLED); this.$button.removeClass(classNames.DISABLED).attr('aria-disabled', false); } } }, clickListener: function () { var that = this, $document = $(document); $document.data('spaceSelect', false); this.$button.on('keyup', function (e) { if (/(32)/.test(e.keyCode.toString(10)) && $document.data('spaceSelect')) { e.preventDefault(); $document.data('spaceSelect', false); } }); this.$newElement.on('show.bs.dropdown', function () { if (version.major > 3 && !that.dropdown) { that.dropdown = that.$button.data('bs.dropdown'); that.dropdown._menu = that.$menu[0]; } }); this.$button.on('click.bs.dropdown.data-api', function () { if (!that.$newElement.hasClass(classNames.SHOW)) { that.setSize(); } }); function setFocus () { if (that.options.liveSearch) { that.$searchbox.trigger('focus'); } else { that.$menuInner.trigger('focus'); } } function checkPopperExists () { if (that.dropdown && that.dropdown._popper && that.dropdown._popper.state.isCreated) { setFocus(); } else { requestAnimationFrame(checkPopperExists); } } this.$element.on('shown' + EVENT_KEY, function () { if (that.$menuInner[0].scrollTop !== that.selectpicker.view.scrollTop) { that.$menuInner[0].scrollTop = that.selectpicker.view.scrollTop; } if (version.major > 3) { requestAnimationFrame(checkPopperExists); } else { setFocus(); } }); // ensure posinset and setsize are correct before selecting an option via a click this.$menuInner.on('mouseenter', 'li a', function (e) { var hoverLi = this.parentElement, position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, index = Array.prototype.indexOf.call(hoverLi.parentElement.children, hoverLi), hoverData = that.selectpicker.current.data[index + position0]; that.focusItem(hoverLi, hoverData, true); }); this.$menuInner.on('click', 'li a', function (e, retainActive) { var $this = $(this), element = that.$element[0], position0 = that.isVirtual() ? that.selectpicker.view.position0 : 0, clickedData = that.selectpicker.current.data[$this.parent().index() + position0], clickedIndex = clickedData.index, prevValue = getSelectValues(element), prevIndex = element.selectedIndex, prevOption = element.options[prevIndex], triggerChange = true; // Don't close on multi choice menu if (that.multiple && that.options.maxOptions !== 1) { e.stopPropagation(); } e.preventDefault(); // Don't run if the select is disabled if (!that.isDisabled() && !$this.parent().hasClass(classNames.DISABLED)) { var option = clickedData.option, $option = $(option), state = option.selected, $optgroup = $option.parent('optgroup'), $optgroupOptions = $optgroup.find('option'), maxOptions = that.options.maxOptions, maxOptionsGrp = $optgroup.data('maxOptions') || false; if (clickedIndex === that.activeIndex) retainActive = true; if (!retainActive) { that.prevActiveIndex = that.activeIndex; that.activeIndex = undefined; } if (!that.multiple) { // Deselect all others if not multi select box if (prevOption) prevOption.selected = false; option.selected = true; that.setSelected(clickedIndex, true); } else { // Toggle the one we have chosen if we are multi select. option.selected = !state; that.setSelected(clickedIndex, !state); that.focusedParent.focus(); if (maxOptions !== false || maxOptionsGrp !== false) { var maxReached = maxOptions < getSelectedOptions(element).length, maxReachedGrp = maxOptionsGrp < $optgroup.find('option:selected').length; if ((maxOptions && maxReached) || (maxOptionsGrp && maxReachedGrp)) { if (maxOptions && maxOptions == 1) { element.selectedIndex = -1; option.selected = true; that.setOptionStatus(true); } else if (maxOptionsGrp && maxOptionsGrp == 1) { for (var i = 0; i < $optgroupOptions.length; i++) { var _option = $optgroupOptions[i]; _option.selected = false; that.setSelected(_option.liIndex, false); } option.selected = true; that.setSelected(clickedIndex, true); } else { var maxOptionsText = typeof that.options.maxOptionsText === 'string' ? [that.options.maxOptionsText, that.options.maxOptionsText] : that.options.maxOptionsText, maxOptionsArr = typeof maxOptionsText === 'function' ? maxOptionsText(maxOptions, maxOptionsGrp) : maxOptionsText, maxTxt = maxOptionsArr[0].replace('{n}', maxOptions), maxTxtGrp = maxOptionsArr[1].replace('{n}', maxOptionsGrp), $notify = $('
    '); // If {var} is set in array, replace it /** @deprecated */ if (maxOptionsArr[2]) { maxTxt = maxTxt.replace('{var}', maxOptionsArr[2][maxOptions > 1 ? 0 : 1]); maxTxtGrp = maxTxtGrp.replace('{var}', maxOptionsArr[2][maxOptionsGrp > 1 ? 0 : 1]); } option.selected = false; that.$menu.append($notify); if (maxOptions && maxReached) { $notify.append($('
    ' + maxTxt + '
    ')); triggerChange = false; that.$element.trigger('maxReached' + EVENT_KEY); } if (maxOptionsGrp && maxReachedGrp) { $notify.append($('
    ' + maxTxtGrp + '
    ')); triggerChange = false; that.$element.trigger('maxReachedGrp' + EVENT_KEY); } setTimeout(function () { that.setSelected(clickedIndex, false); }, 10); $notify[0].classList.add('fadeOut'); setTimeout(function () { $notify.remove(); }, 1050); } } } } if (!that.multiple || (that.multiple && that.options.maxOptions === 1)) { that.$button.trigger('focus'); } else if (that.options.liveSearch) { that.$searchbox.trigger('focus'); } // Trigger select 'change' if (triggerChange) { if (that.multiple || prevIndex !== element.selectedIndex) { // $option.prop('selected') is current option state (selected/unselected). prevValue is the value of the select prior to being changed. changedArguments = [option.index, $option.prop('selected'), prevValue]; that.$element .triggerNative('change'); } } } }); this.$menu.on('click', 'li.' + classNames.DISABLED + ' a, .' + classNames.POPOVERHEADER + ', .' + classNames.POPOVERHEADER + ' :not(.close)', function (e) { if (e.currentTarget == this) { e.preventDefault(); e.stopPropagation(); if (that.options.liveSearch && !$(e.target).hasClass('close')) { that.$searchbox.trigger('focus'); } else { that.$button.trigger('focus'); } } }); this.$menuInner.on('click', '.divider, .dropdown-header', function (e) { e.preventDefault(); e.stopPropagation(); if (that.options.liveSearch) { that.$searchbox.trigger('focus'); } else { that.$button.trigger('focus'); } }); this.$menu.on('click', '.' + classNames.POPOVERHEADER + ' .close', function () { that.$button.trigger('click'); }); this.$searchbox.on('click', function (e) { e.stopPropagation(); }); this.$menu.on('click', '.actions-btn', function (e) { if (that.options.liveSearch) { that.$searchbox.trigger('focus'); } else { that.$button.trigger('focus'); } e.preventDefault(); e.stopPropagation(); if ($(this).hasClass('bs-select-all')) { that.selectAll(); } else { that.deselectAll(); } }); this.$button .on('focus' + EVENT_KEY, function (e) { var tabindex = that.$element[0].getAttribute('tabindex'); // only change when button is actually focused if (tabindex !== undefined && e.originalEvent && e.originalEvent.isTrusted) { // apply select element's tabindex to ensure correct order is followed when tabbing to the next element this.setAttribute('tabindex', tabindex); // set element's tabindex to -1 to allow for reverse tabbing that.$element[0].setAttribute('tabindex', -1); that.selectpicker.view.tabindex = tabindex; } }) .on('blur' + EVENT_KEY, function (e) { // revert everything to original tabindex if (that.selectpicker.view.tabindex !== undefined && e.originalEvent && e.originalEvent.isTrusted) { that.$element[0].setAttribute('tabindex', that.selectpicker.view.tabindex); this.setAttribute('tabindex', -1); that.selectpicker.view.tabindex = undefined; } }); this.$element .on('change' + EVENT_KEY, function () { that.render(); that.$element.trigger('changed' + EVENT_KEY, changedArguments); changedArguments = null; }) .on('focus' + EVENT_KEY, function () { if (!that.options.mobile) that.$button[0].focus(); }); }, liveSearchListener: function () { var that = this; this.$button.on('click.bs.dropdown.data-api', function () { if (!!that.$searchbox.val()) { that.$searchbox.val(''); that.selectpicker.search.previousValue = undefined; } }); this.$searchbox.on('click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api', function (e) { e.stopPropagation(); }); this.$searchbox.on('input propertychange', function () { var searchValue = that.$searchbox[0].value; that.selectpicker.search.elements = []; that.selectpicker.search.data = []; if (searchValue) { var i, searchMatch = [], q = searchValue.toUpperCase(), cache = {}, cacheArr = [], searchStyle = that._searchStyle(), normalizeSearch = that.options.liveSearchNormalize; if (normalizeSearch) q = normalizeToBase(q); for (var i = 0; i < that.selectpicker.main.data.length; i++) { var li = that.selectpicker.main.data[i]; if (!cache[i]) { cache[i] = stringSearch(li, q, searchStyle, normalizeSearch); } if (cache[i] && li.headerIndex !== undefined && cacheArr.indexOf(li.headerIndex) === -1) { if (li.headerIndex > 0) { cache[li.headerIndex - 1] = true; cacheArr.push(li.headerIndex - 1); } cache[li.headerIndex] = true; cacheArr.push(li.headerIndex); cache[li.lastIndex + 1] = true; } if (cache[i] && li.type !== 'optgroup-label') cacheArr.push(i); } for (var i = 0, cacheLen = cacheArr.length; i < cacheLen; i++) { var index = cacheArr[i], prevIndex = cacheArr[i - 1], li = that.selectpicker.main.data[index], liPrev = that.selectpicker.main.data[prevIndex]; if (li.type !== 'divider' || (li.type === 'divider' && liPrev && liPrev.type !== 'divider' && cacheLen - 1 !== i)) { that.selectpicker.search.data.push(li); searchMatch.push(that.selectpicker.main.elements[index]); } } that.activeIndex = undefined; that.noScroll = true; that.$menuInner.scrollTop(0); that.selectpicker.search.elements = searchMatch; that.createView(true); showNoResults.call(that, searchMatch, searchValue); } else if (that.selectpicker.search.previousValue) { // for IE11 (#2402) that.$menuInner.scrollTop(0); that.createView(false); } that.selectpicker.search.previousValue = searchValue; }); }, _searchStyle: function () { return this.options.liveSearchStyle || 'contains'; }, val: function (value) { var element = this.$element[0]; if (typeof value !== 'undefined') { var prevValue = getSelectValues(element); changedArguments = [null, null, prevValue]; this.$element .val(value) .trigger('changed' + EVENT_KEY, changedArguments); if (this.$newElement.hasClass(classNames.SHOW)) { if (this.multiple) { this.setOptionStatus(true); } else { var liSelectedIndex = (element.options[element.selectedIndex] || {}).liIndex; if (typeof liSelectedIndex === 'number') { this.setSelected(this.selectedIndex, false); this.setSelected(liSelectedIndex, true); } } } this.render(); changedArguments = null; return this.$element; } else { return this.$element.val(); } }, changeAll: function (status) { if (!this.multiple) return; if (typeof status === 'undefined') status = true; var element = this.$element[0], previousSelected = 0, currentSelected = 0, prevValue = getSelectValues(element); element.classList.add('bs-select-hidden'); for (var i = 0, data = this.selectpicker.current.data, len = data.length; i < len; i++) { var liData = data[i], option = liData.option; if (option && !liData.disabled && liData.type !== 'divider') { if (liData.selected) previousSelected++; option.selected = status; if (status === true) currentSelected++; } } element.classList.remove('bs-select-hidden'); if (previousSelected === currentSelected) return; this.setOptionStatus(); changedArguments = [null, null, prevValue]; this.$element .triggerNative('change'); }, selectAll: function () { return this.changeAll(true); }, deselectAll: function () { return this.changeAll(false); }, toggle: function (e) { e = e || window.event; if (e) e.stopPropagation(); this.$button.trigger('click.bs.dropdown.data-api'); }, keydown: function (e) { var $this = $(this), isToggle = $this.hasClass('dropdown-toggle'), $parent = isToggle ? $this.closest('.dropdown') : $this.closest(Selector.MENU), that = $parent.data('this'), $items = that.findLis(), index, isActive, liActive, activeLi, offset, updateScroll = false, downOnTab = e.which === keyCodes.TAB && !isToggle && !that.options.selectOnTab, isArrowKey = REGEXP_ARROW.test(e.which) || downOnTab, scrollTop = that.$menuInner[0].scrollTop, isVirtual = that.isVirtual(), position0 = isVirtual === true ? that.selectpicker.view.position0 : 0; // do nothing if a function key is pressed if (e.which >= 112 && e.which <= 123) return; isActive = that.$newElement.hasClass(classNames.SHOW); if ( !isActive && ( isArrowKey || (e.which >= 48 && e.which <= 57) || (e.which >= 96 && e.which <= 105) || (e.which >= 65 && e.which <= 90) ) ) { that.$button.trigger('click.bs.dropdown.data-api'); if (that.options.liveSearch) { that.$searchbox.trigger('focus'); return; } } if (e.which === keyCodes.ESCAPE && isActive) { e.preventDefault(); that.$button.trigger('click.bs.dropdown.data-api').trigger('focus'); } if (isArrowKey) { // if up or down if (!$items.length) return; liActive = that.selectpicker.main.elements[that.activeIndex]; index = liActive ? Array.prototype.indexOf.call(liActive.parentElement.children, liActive) : -1; if (index !== -1) { that.defocusItem(liActive); } if (e.which === keyCodes.ARROW_UP) { // up if (index !== -1) index--; if (index + position0 < 0) index += $items.length; if (!that.selectpicker.view.canHighlight[index + position0]) { index = that.selectpicker.view.canHighlight.slice(0, index + position0).lastIndexOf(true) - position0; if (index === -1) index = $items.length - 1; } } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down index++; if (index + position0 >= that.selectpicker.view.canHighlight.length) index = that.selectpicker.view.firstHighlightIndex; if (!that.selectpicker.view.canHighlight[index + position0]) { index = index + 1 + that.selectpicker.view.canHighlight.slice(index + position0 + 1).indexOf(true); } } e.preventDefault(); var liActiveIndex = position0 + index; if (e.which === keyCodes.ARROW_UP) { // up // scroll to bottom and highlight last option if (position0 === 0 && index === $items.length - 1) { that.$menuInner[0].scrollTop = that.$menuInner[0].scrollHeight; liActiveIndex = that.selectpicker.current.elements.length - 1; } else { activeLi = that.selectpicker.current.data[liActiveIndex]; offset = activeLi.position - activeLi.height; updateScroll = offset < scrollTop; } } else if (e.which === keyCodes.ARROW_DOWN || downOnTab) { // down // scroll to top and highlight first option if (index === that.selectpicker.view.firstHighlightIndex) { that.$menuInner[0].scrollTop = 0; liActiveIndex = that.selectpicker.view.firstHighlightIndex; } else { activeLi = that.selectpicker.current.data[liActiveIndex]; offset = activeLi.position - that.sizeInfo.menuInnerHeight; updateScroll = offset > scrollTop; } } liActive = that.selectpicker.current.elements[liActiveIndex]; that.activeIndex = that.selectpicker.current.data[liActiveIndex].index; that.focusItem(liActive); that.selectpicker.view.currentActive = liActive; if (updateScroll) that.$menuInner[0].scrollTop = offset; if (that.options.liveSearch) { that.$searchbox.trigger('focus'); } else { $this.trigger('focus'); } } else if ( (!$this.is('input') && !REGEXP_TAB_OR_ESCAPE.test(e.which)) || (e.which === keyCodes.SPACE && that.selectpicker.keydown.keyHistory) ) { var searchMatch, matches = [], keyHistory; e.preventDefault(); that.selectpicker.keydown.keyHistory += keyCodeMap[e.which]; if (that.selectpicker.keydown.resetKeyHistory.cancel) clearTimeout(that.selectpicker.keydown.resetKeyHistory.cancel); that.selectpicker.keydown.resetKeyHistory.cancel = that.selectpicker.keydown.resetKeyHistory.start(); keyHistory = that.selectpicker.keydown.keyHistory; // if all letters are the same, set keyHistory to just the first character when searching if (/^(.)\1+$/.test(keyHistory)) { keyHistory = keyHistory.charAt(0); } // find matches for (var i = 0; i < that.selectpicker.current.data.length; i++) { var li = that.selectpicker.current.data[i], hasMatch; hasMatch = stringSearch(li, keyHistory, 'startsWith', true); if (hasMatch && that.selectpicker.view.canHighlight[i]) { matches.push(li.index); } } if (matches.length) { var matchIndex = 0; $items.removeClass('active').find('a').removeClass('active'); // either only one key has been pressed or they are all the same key if (keyHistory.length === 1) { matchIndex = matches.indexOf(that.activeIndex); if (matchIndex === -1 || matchIndex === matches.length - 1) { matchIndex = 0; } else { matchIndex++; } } searchMatch = matches[matchIndex]; activeLi = that.selectpicker.main.data[searchMatch]; if (scrollTop - activeLi.position > 0) { offset = activeLi.position - activeLi.height; updateScroll = true; } else { offset = activeLi.position - that.sizeInfo.menuInnerHeight; // if the option is already visible at the current scroll position, just keep it the same updateScroll = activeLi.position > scrollTop + that.sizeInfo.menuInnerHeight; } liActive = that.selectpicker.main.elements[searchMatch]; that.activeIndex = matches[matchIndex]; that.focusItem(liActive); if (liActive) liActive.firstChild.focus(); if (updateScroll) that.$menuInner[0].scrollTop = offset; $this.trigger('focus'); } } // Select focused option if "Enter", "Spacebar" or "Tab" (when selectOnTab is true) are pressed inside the menu. if ( isActive && ( (e.which === keyCodes.SPACE && !that.selectpicker.keydown.keyHistory) || e.which === keyCodes.ENTER || (e.which === keyCodes.TAB && that.options.selectOnTab) ) ) { if (e.which !== keyCodes.SPACE) e.preventDefault(); if (!that.options.liveSearch || e.which !== keyCodes.SPACE) { that.$menuInner.find('.active a').trigger('click', true); // retain active class $this.trigger('focus'); if (!that.options.liveSearch) { // Prevent screen from scrolling if the user hits the spacebar e.preventDefault(); // Fixes spacebar selection of dropdown items in FF & IE $(document).data('spaceSelect', true); } } } }, mobile: function () { // ensure mobile is set to true if mobile function is called after init this.options.mobile = true; this.$element[0].classList.add('mobile-device'); }, refresh: function () { // update options if data attributes have been changed var config = $.extend({}, this.options, this.$element.data()); this.options = config; this.checkDisabled(); this.buildData(); this.setStyle(); this.render(); this.buildList(); this.setWidth(); this.setSize(true); this.$element.trigger('refreshed' + EVENT_KEY); }, hide: function () { this.$newElement.hide(); }, show: function () { this.$newElement.show(); }, remove: function () { this.$newElement.remove(); this.$element.remove(); }, destroy: function () { this.$newElement.before(this.$element).remove(); if (this.$bsContainer) { this.$bsContainer.remove(); } else { this.$menu.remove(); } if (this.selectpicker.view.titleOption && this.selectpicker.view.titleOption.parentNode) { this.selectpicker.view.titleOption.parentNode.removeChild(this.selectpicker.view.titleOption); } this.$element .off(EVENT_KEY) .removeData('selectpicker') .removeClass('bs-select-hidden selectpicker'); $(window).off(EVENT_KEY + '.' + this.selectId); } }; // SELECTPICKER PLUGIN DEFINITION // ============================== function Plugin (option) { // get the args of the outer function.. var args = arguments; // The arguments of the function are explicitly re-defined from the argument list, because the shift causes them // to get lost/corrupted in android 2.3 and IE9 #715 #775 var _option = option; [].shift.apply(args); // if the version was not set successfully if (!version.success) { // try to retreive it again try { version.full = ($.fn.dropdown.Constructor.VERSION || '').split(' ')[0].split('.'); } catch (err) { // fall back to use BootstrapVersion if set if (Selectpicker.BootstrapVersion) { version.full = Selectpicker.BootstrapVersion.split(' ')[0].split('.'); } else { version.full = [version.major, '0', '0']; console.warn( 'There was an issue retrieving Bootstrap\'s version. ' + 'Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. ' + 'If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.', err ); } } version.major = version.full[0]; version.success = true; } if (version.major === '4') { // some defaults need to be changed if using Bootstrap 4 // check to see if they have already been manually changed before forcing them to update var toUpdate = []; if (Selectpicker.DEFAULTS.style === classNames.BUTTONCLASS) toUpdate.push({ name: 'style', className: 'BUTTONCLASS' }); if (Selectpicker.DEFAULTS.iconBase === classNames.ICONBASE) toUpdate.push({ name: 'iconBase', className: 'ICONBASE' }); if (Selectpicker.DEFAULTS.tickIcon === classNames.TICKICON) toUpdate.push({ name: 'tickIcon', className: 'TICKICON' }); classNames.DIVIDER = 'dropdown-divider'; classNames.SHOW = 'show'; classNames.BUTTONCLASS = 'btn-light'; classNames.POPOVERHEADER = 'popover-header'; classNames.ICONBASE = ''; classNames.TICKICON = 'bs-ok-default'; for (var i = 0; i < toUpdate.length; i++) { var option = toUpdate[i]; Selectpicker.DEFAULTS[option.name] = classNames[option.className]; } } var value; var chain = this.each(function () { var $this = $(this); if ($this.is('select')) { var data = $this.data('selectpicker'), options = typeof _option == 'object' && _option; if (!data) { var dataAttributes = $this.data(); for (var dataAttr in dataAttributes) { if (Object.prototype.hasOwnProperty.call(dataAttributes, dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) { delete dataAttributes[dataAttr]; } } var config = $.extend({}, Selectpicker.DEFAULTS, $.fn.selectpicker.defaults || {}, dataAttributes, options); config.template = $.extend({}, Selectpicker.DEFAULTS.template, ($.fn.selectpicker.defaults ? $.fn.selectpicker.defaults.template : {}), dataAttributes.template, options.template); $this.data('selectpicker', (data = new Selectpicker(this, config))); } else if (options) { for (var i in options) { if (Object.prototype.hasOwnProperty.call(options, i)) { data.options[i] = options[i]; } } } if (typeof _option == 'string') { if (data[_option] instanceof Function) { value = data[_option].apply(data, args); } else { value = data.options[_option]; } } } }); if (typeof value !== 'undefined') { // noinspection JSUnusedAssignment return value; } else { return chain; } } var old = $.fn.selectpicker; $.fn.selectpicker = Plugin; $.fn.selectpicker.Constructor = Selectpicker; // SELECTPICKER NO CONFLICT // ======================== $.fn.selectpicker.noConflict = function () { $.fn.selectpicker = old; return this; }; // get Bootstrap's keydown event handler for either Bootstrap 4 or Bootstrap 3 function keydownHandler () { if ($.fn.dropdown) { // wait to define until function is called in case Bootstrap isn't loaded yet var bootstrapKeydown = $.fn.dropdown.Constructor._dataApiKeydownHandler || $.fn.dropdown.Constructor.prototype.keydown; return bootstrapKeydown.apply(this, arguments); } } $(document) .off('keydown.bs.dropdown.data-api') .on('keydown.bs.dropdown.data-api', ':not(.bootstrap-select) > [data-toggle="dropdown"]', keydownHandler) .on('keydown.bs.dropdown.data-api', ':not(.bootstrap-select) > .dropdown-menu', keydownHandler) .on('keydown' + EVENT_KEY, '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', Selectpicker.prototype.keydown) .on('focusin.modal', '.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input', function (e) { e.stopPropagation(); }); // SELECTPICKER DATA-API // ===================== $(window).on('load' + EVENT_KEY + '.data-api', function () { $('.selectpicker').each(function () { var $selectpicker = $(this); Plugin.call($selectpicker, $selectpicker.data()); }) }); })(jQuery); })); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/auto-refresh/bootstrap-table-auto-refresh.js ================================================ /** * @author: Alec Fenichel * @webSite: https://fenichelar.com * @update: zhixin wen */ var Utils = $.fn.bootstrapTable.utils Object.assign($.fn.bootstrapTable.defaults, { autoRefresh: false, showAutoRefresh: true, autoRefreshInterval: 60, autoRefreshSilent: true, autoRefreshStatus: true, autoRefreshFunction: null }) Utils.assignIcons($.fn.bootstrapTable.icons, 'autoRefresh', { glyphicon: 'glyphicon-time icon-time', fa: 'fa-clock', bi: 'bi-clock', icon: 'icon-clock', 'material-icons': 'access_time' }) Object.assign($.fn.bootstrapTable.locales, { formatAutoRefresh () { return 'Auto Refresh' } }) Object.assign($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales) $.BootstrapTable = class extends $.BootstrapTable { init (...args) { super.init(...args) if (this.options.autoRefresh && this.options.autoRefreshStatus) { this.setupRefreshInterval() } } initToolbar (...args) { if (this.options.autoRefresh) { this.buttons = Object.assign(this.buttons, { autoRefresh: { text: this.options.formatAutoRefresh(), icon: this.options.icons.autoRefresh, render: false, event: this.toggleAutoRefresh, attributes: { 'aria-label': this.options.formatAutoRefresh(), title: this.options.formatAutoRefresh() } } }) } super.initToolbar(...args) } toggleAutoRefresh () { if (this.options.autoRefresh) { if (this.options.autoRefreshStatus) { clearInterval(this.options.autoRefreshFunction) this.$toolbar.find('>.columns .auto-refresh') .removeClass(this.constants.classes.buttonActive) } else { this.setupRefreshInterval() this.$toolbar.find('>.columns .auto-refresh') .addClass(this.constants.classes.buttonActive) } this.options.autoRefreshStatus = !this.options.autoRefreshStatus } } destroy () { if (this.options.autoRefresh && this.options.autoRefreshStatus) { clearInterval(this.options.autoRefreshFunction) } super.destroy() } setupRefreshInterval () { this.options.autoRefreshFunction = setInterval(() => { if (!this.options.autoRefresh || !this.options.autoRefreshStatus) { return } this.refresh({ silent: this.options.autoRefreshSilent }) }, this.options.autoRefreshInterval * 1000) } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/columns/bootstrap-table-fixed-columns.js ================================================ /** * @author zhixin wen * @github: bootstrap-table/dist/extensions/fixed-columns/bootstrap-table-fixed-columns.min.js */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).jQuery)}(this,(function(t){"use strict";function e(t,e,n){return e=i(e),function(t,e){if(e&&("object"==typeof e||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}(t,o()?Reflect.construct(e,n||[],i(t).constructor):e.apply(t,n))}function n(t,e,n){return function(t,e){for(var n=0;n0&&t[0]<4?1:+(t[0]+t[1])),!e&&r&&(!(t=r.match(/Edge\/(\d+)/))||t[1]>=74)&&(t=r.match(/Chrome\/(\d+)/))&&(e=+t[1]),Z=e}function Rt(){if(et)return tt;et=1;var t=St(),e=O(),n=h().String;return tt=!!Object.getOwnPropertySymbols&&!e((function(){var e=Symbol("symbol detection");return!n(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&t&&t<41}))}function Bt(){if(rt)return nt;rt=1;var t=Rt();return nt=t&&!Symbol.sham&&"symbol"==typeof Symbol.iterator}function Ft(){if(ot)return it;ot=1;var t=jt(),e=Ot(),n=function(){if(U)return V;U=1;var t=bt();return V=t({}.isPrototypeOf)}(),r=Bt(),i=Object;return it=r?function(t){return"symbol"==typeof t}:function(r){var o=t("Symbol");return e(o)&&n(o.prototype,i(r))}}function Tt(){if(ft)return ut;ft=1;var t=String;return ut=function(e){try{return t(e)}catch(t){return"Object"}}}function kt(){if(at)return ct;at=1;var t=Ot(),e=Tt(),n=TypeError;return ct=function(r){if(t(r))return r;throw new n(e(r)+" is not a function")}}function Pt(){if(lt)return st;lt=1;var t=kt(),e=xt();return st=function(n,r){var i=n[r];return e(i)?void 0:t(i)}}function Et(){if(ht)return dt;ht=1;var t=S(),e=Ot(),n=Ct(),r=TypeError;return dt=function(i,o){var u,f;if("string"===o&&e(u=i.toString)&&!n(f=t(u,i)))return f;if(e(u=i.valueOf)&&!n(f=t(u,i)))return f;if("string"!==o&&e(u=i.toString)&&!n(f=t(u,i)))return f;throw new r("Can't convert object to primitive value")}}var At,Nt,Ht,It,Lt,Wt,Dt,Mt,_t,zt,Xt,Yt,qt,Gt,Vt,Ut,Kt,Qt,Zt,Jt,te,ee,ne,re,ie={exports:{}};function oe(){if(It)return Ht;It=1;var t=h(),e=Object.defineProperty;return Ht=function(n,r){try{e(t,n,{value:r,configurable:!0,writable:!0})}catch(e){t[n]=r}return r}}function ue(){if(Lt)return ie.exports;Lt=1;var t=Nt?At:(Nt=1,At=!1),e=h(),n=oe(),r="__core-js_shared__",i=ie.exports=e[r]||n(r,{});return(i.versions||(i.versions=[])).push({version:"3.39.0",mode:t?"pure":"global",copyright:"© 2014-2024 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE",source:"https://github.com/zloirock/core-js"}),ie.exports}function fe(){if(Dt)return Wt;Dt=1;var t=ue();return Wt=function(e,n){return t[e]||(t[e]=n||{})}}function ce(){if(_t)return Mt;_t=1;var t=wt(),e=Object;return Mt=function(n){return e(t(n))}}function ae(){if(Xt)return zt;Xt=1;var t=bt(),e=ce(),n=t({}.hasOwnProperty);return zt=Object.hasOwn||function(t,r){return n(e(t),r)}}function se(){if(qt)return Yt;qt=1;var t=bt(),e=0,n=Math.random(),r=t(1..toString);return Yt=function(t){return"Symbol("+(void 0===t?"":t)+")_"+r(++e+n,36)}}function le(){if(Vt)return Gt;Vt=1;var t=h(),e=fe(),n=ae(),r=se(),i=Rt(),o=Bt(),u=t.Symbol,f=e("wks"),c=o?u.for||u:u&&u.withoutSetter||r;return Gt=function(t){return n(f,t)||(f[t]=i&&n(u,t)?u[t]:c("Symbol."+t)),f[t]}}function de(){if(Kt)return Ut;Kt=1;var t=S(),e=Ct(),n=Ft(),r=Pt(),i=Et(),o=le(),u=TypeError,f=o("toPrimitive");return Ut=function(o,c){if(!e(o)||n(o))return o;var a,s=r(o,f);if(s){if(void 0===c&&(c="default"),a=t(s,o,c),!e(a)||n(a))return a;throw new u("Can't convert object to primitive value")}return void 0===c&&(c="number"),i(o,c)}}function he(){if(Zt)return Qt;Zt=1;var t=de(),e=Ft();return Qt=function(n){var r=t(n,"string");return e(r)?r:r+""}}function pe(){if(te)return Jt;te=1;var t=h(),e=Ct(),n=t.document,r=e(n)&&e(n.createElement);return Jt=function(t){return r?n.createElement(t):{}}}function ve(){if(ne)return ee;ne=1;var t=C(),e=O(),n=pe();return ee=!t&&!e((function(){return 7!==Object.defineProperty(n("div"),"a",{get:function(){return 7}}).a}))}function ye(){if(re)return $;re=1;var t=C(),e=S(),n=vt(),r=yt(),i=$t(),o=he(),u=ae(),f=ve(),c=Object.getOwnPropertyDescriptor;return $.f=t?c:function(t,a){if(t=i(t),a=o(a),f)try{return c(t,a)}catch(t){}if(u(t,a))return r(!e(n.f,t,a),t[a])},$}var be,ge,me,xe,we,$e,Oe,Ce={};function je(){if(ge)return be;ge=1;var t=C(),e=O();return be=t&&e((function(){return 42!==Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))}function Se(){if(xe)return me;xe=1;var t=Ct(),e=String,n=TypeError;return me=function(r){if(t(r))return r;throw new n(e(r)+" is not an object")}}function Re(){if(we)return Ce;we=1;var t=C(),e=ve(),n=je(),r=Se(),i=he(),o=TypeError,u=Object.defineProperty,f=Object.getOwnPropertyDescriptor,c="enumerable",a="configurable",s="writable";return Ce.f=t?n?function(t,e,n){if(r(t),e=i(e),r(n),"function"==typeof t&&"prototype"===e&&"value"in n&&s in n&&!n[s]){var o=f(t,e);o&&o[s]&&(t[e]=n.value,n={configurable:a in n?n[a]:o[a],enumerable:c in n?n[c]:o[c],writable:!1})}return u(t,e,n)}:u:function(t,n,f){if(r(t),n=i(n),r(f),e)try{return u(t,n,f)}catch(t){}if("get"in f||"set"in f)throw new o("Accessors not supported");return"value"in f&&(t[n]=f.value),t},Ce}function Be(){if(Oe)return $e;Oe=1;var t=C(),e=Re(),n=yt();return $e=t?function(t,r,i){return e.f(t,r,n(1,i))}:function(t,e,n){return t[e]=n,t}}var Fe,Te,ke,Pe,Ee,Ae,Ne,He,Ie,Le,We,De,Me,_e,ze,Xe={exports:{}};function Ye(){if(Pe)return ke;Pe=1;var t=bt(),e=Ot(),n=ue(),r=t(Function.toString);return e(n.inspectSource)||(n.inspectSource=function(t){return r(t)}),ke=n.inspectSource}function qe(){if(He)return Ne;He=1;var t=fe(),e=se(),n=t("keys");return Ne=function(t){return n[t]||(n[t]=e(t))}}function Ge(){return Le?Ie:(Le=1,Ie={})}function Ve(){if(De)return We;De=1;var t,e,n,r=function(){if(Ae)return Ee;Ae=1;var t=h(),e=Ot(),n=t.WeakMap;return Ee=e(n)&&/native code/.test(String(n))}(),i=h(),o=Ct(),u=Be(),f=ae(),c=ue(),a=qe(),s=Ge(),l="Object already initialized",d=i.TypeError,p=i.WeakMap;if(r||c.state){var v=c.state||(c.state=new p);v.get=v.get,v.has=v.has,v.set=v.set,t=function(t,e){if(v.has(t))throw new d(l);return e.facade=t,v.set(t,e),e},e=function(t){return v.get(t)||{}},n=function(t){return v.has(t)}}else{var y=a("state");s[y]=!0,t=function(t,e){if(f(t,y))throw new d(l);return e.facade=t,u(t,y,e),e},e=function(t){return f(t,y)?t[y]:{}},n=function(t){return f(t,y)}}return We={set:t,get:e,has:n,enforce:function(r){return n(r)?e(r):t(r,{})},getterFor:function(t){return function(n){var r;if(!o(n)||(r=e(n)).type!==t)throw new d("Incompatible receiver, "+t+" required");return r}}}}function Ue(){if(Me)return Xe.exports;Me=1;var t=bt(),e=O(),n=Ot(),r=ae(),i=C(),o=function(){if(Te)return Fe;Te=1;var t=C(),e=ae(),n=Function.prototype,r=t&&Object.getOwnPropertyDescriptor,i=e(n,"name"),o=i&&"something"===function(){}.name,u=i&&(!t||t&&r(n,"name").configurable);return Fe={EXISTS:i,PROPER:o,CONFIGURABLE:u}}().CONFIGURABLE,u=Ye(),f=Ve(),c=f.enforce,a=f.get,s=String,l=Object.defineProperty,d=t("".slice),h=t("".replace),p=t([].join),v=i&&!e((function(){return 8!==l((function(){}),"length",{value:8}).length})),y=String(String).split("String"),b=Xe.exports=function(t,e,n){"Symbol("===d(s(e),0,7)&&(e="["+h(s(e),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),n&&n.getter&&(e="get "+e),n&&n.setter&&(e="set "+e),(!r(t,"name")||o&&t.name!==e)&&(i?l(t,"name",{value:e,configurable:!0}):t.name=e),v&&n&&r(n,"arity")&&t.length!==n.arity&&l(t,"length",{value:n.arity});try{n&&r(n,"constructor")&&n.constructor?i&&l(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var u=c(t);return r(u,"source")||(u.source=p(y,"string"==typeof e?e:"")),t};return Function.prototype.toString=b((function(){return n(this)&&a(this).source||u(this)}),"toString"),Xe.exports}function Ke(){if(ze)return _e;ze=1;var t=Ot(),e=Re(),n=Ue(),r=oe();return _e=function(i,o,u,f){f||(f={});var c=f.enumerable,a=void 0!==f.name?f.name:o;if(t(u)&&n(u,a,f),f.global)c?i[o]=u:r(o,u);else{try{f.unsafe?i[o]&&(c=!0):delete i[o]}catch(t){}c?i[o]=u:e.f(i,o,{value:u,enumerable:!1,configurable:!f.nonConfigurable,writable:!f.nonWritable})}return i}}var Qe,Ze,Je,tn,en,nn,rn,on,un,fn,cn,an,sn,ln,dn,hn,pn,vn={};function yn(){if(tn)return Je;tn=1;var t=function(){if(Ze)return Qe;Ze=1;var t=Math.ceil,e=Math.floor;return Qe=Math.trunc||function(n){var r=+n;return(r>0?e:t)(r)}}();return Je=function(e){var n=+e;return n!=n||0===n?0:t(n)}}function bn(){if(nn)return en;nn=1;var t=yn(),e=Math.max,n=Math.min;return en=function(r,i){var o=t(r);return o<0?e(o+i,0):n(o,i)}}function gn(){if(on)return rn;on=1;var t=yn(),e=Math.min;return rn=function(n){var r=t(n);return r>0?e(r,9007199254740991):0}}function mn(){if(fn)return un;fn=1;var t=gn();return un=function(e){return t(e.length)}}function xn(){if(an)return cn;an=1;var t=$t(),e=bn(),n=mn(),r=function(r){return function(i,o,u){var f=t(i),c=n(f);if(0===c)return!r&&-1;var a,s=e(u,c);if(r&&o!=o){for(;c>s;)if((a=f[s++])!=a)return!0}else for(;c>s;s++)if((r||s in f)&&f[s]===o)return r||s||0;return!r&&-1}};return cn={includes:r(!0),indexOf:r(!1)}}function wn(){if(ln)return sn;ln=1;var t=bt(),e=ae(),n=$t(),r=xn().indexOf,i=Ge(),o=t([].push);return sn=function(t,u){var f,c=n(t),a=0,s=[];for(f in c)!e(i,f)&&e(c,f)&&o(s,f);for(;u.length>a;)e(c,f=u[a++])&&(~r(s,f)||o(s,f));return s}}function $n(){return hn?dn:(hn=1,dn=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"])}var On,Cn,jn,Sn,Rn,Bn,Fn,Tn,kn,Pn,En,An,Nn,Hn,In,Ln,Wn,Dn,Mn,_n,zn,Xn,Yn,qn,Gn,Vn,Un,Kn={};function Qn(){return On||(On=1,Kn.f=Object.getOwnPropertySymbols),Kn}function Zn(){if(jn)return Cn;jn=1;var t=jt(),e=bt(),n=function(){if(pn)return vn;pn=1;var t=wn(),e=$n().concat("length","prototype");return vn.f=Object.getOwnPropertyNames||function(n){return t(n,e)},vn}(),r=Qn(),i=Se(),o=e([].concat);return Cn=t("Reflect","ownKeys")||function(t){var e=n.f(i(t)),u=r.f;return u?o(e,u(t)):e}}function Jn(){if(Rn)return Sn;Rn=1;var t=ae(),e=Zn(),n=ye(),r=Re();return Sn=function(i,o,u){for(var f=e(o),c=r.f,a=n.f,s=0;sC;C++)if((h||C in w)&&(m=O(g=w[C],C,x),e))if(f)S[C]=m;else if(m)switch(e){case 3:return!0;case 5:return g;case 6:return C;case 2:u(S,g)}else switch(e){case 4:return!1;case 7:u(S,g)}return l?-1:a||s?s:S}};return Vn={forEach:f(0),map:f(1),filter:f(2),some:f(3),every:f(4),find:f(5),findIndex:f(6),filterReject:f(7)}}var ar,sr,lr,dr,hr,pr,vr,yr,br,gr,mr={};function xr(){if(sr)return ar;sr=1;var t=wn(),e=$n();return ar=Object.keys||function(n){return t(n,e)}}function wr(){if(hr)return dr;hr=1;var t=jt();return dr=t("document","documentElement")}function $r(){if(vr)return pr;vr=1;var t,e=Se(),n=function(){if(lr)return mr;lr=1;var t=C(),e=je(),n=Re(),r=Se(),i=$t(),o=xr();return mr.f=t&&!e?Object.defineProperties:function(t,e){r(t);for(var u,f=i(e),c=o(e),a=c.length,s=0;a>s;)n.f(t,u=c[s++],f[u]);return t},mr}(),r=$n(),i=Ge(),o=wr(),u=pe(),f=qe(),c="prototype",a="script",s=f("IE_PROTO"),l=function(){},d=function(t){return"<"+a+">"+t+""},h=function(t){t.write(d("")),t.close();var e=t.parentWindow.Object;return t=null,e},p=function(){try{t=new ActiveXObject("htmlfile")}catch(t){}var e,n,i;p="undefined"!=typeof document?document.domain&&t?h(t):(n=u("iframe"),i="java"+a+":",n.style.display="none",o.appendChild(n),n.src=String(i),(e=n.contentWindow.document).open(),e.write(d("document.F=Object")),e.close(),e.F):h(t);for(var f=r.length;f--;)delete p[c][r[f]];return p()};return i[s]=!0,pr=Object.create||function(t,r){var i;return null!==t?(l[c]=e(t),i=new l,l[c]=null,i[s]=t):i=p(),void 0===r?i:n.f(i,r)}}function Or(){if(br)return yr;br=1;var t=le(),e=$r(),n=Re().f,r=t("unscopables"),i=Array.prototype;return void 0===i[r]&&n(i,r,{configurable:!0,value:e(null)}),yr=function(t){i[r][t]=!0}}!function(){if(gr)return d;gr=1;var t=tr(),e=cr().find,n=Or(),r="find",i=!0;r in[]&&Array(1)[r]((function(){i=!1})),t({target:"Array",proto:!0,forced:i},{find:function(t){return e(this,t,arguments.length>1?arguments[1]:void 0)}}),n(r)}();var Cr,jr,Sr,Rr={};function Br(){if(jr)return Cr;jr=1;var t=O();return Cr=function(e,n){var r=[][e];return!!r&&t((function(){r.call(null,n||function(){return 1},1)}))}}!function(){if(Sr)return Rr;Sr=1;var t=tr(),e=er(),n=xn().indexOf,r=Br(),i=e([].indexOf),o=!!i&&1/i([1],1,-0)<0;t({target:"Array",proto:!0,forced:o||!r("indexOf")},{indexOf:function(t){var e=arguments.length>1?arguments[1]:void 0;return o?i(this,t,e)||0:n(this,t,e)}})}();var Fr,Tr={};!function(){if(Fr)return Tr;Fr=1;var t=tr(),e=bt(),n=nr(),r=e([].reverse),i=[1,2];t({target:"Array",proto:!0,forced:String(i)===String(i.reverse())},{reverse:function(){return n(this)&&(this.length=this.length),r(this)}})}();var kr,Pr,Er,Ar={};!function(){if(Er)return Ar;Er=1;var t=tr(),e=function(){if(Pr)return kr;Pr=1;var t=C(),e=bt(),n=S(),r=O(),i=xr(),o=Qn(),u=vt(),f=ce(),c=mt(),a=Object.assign,s=Object.defineProperty,l=e([].concat);return kr=!a||r((function(){if(t&&1!==a({b:1},a(s({},"a",{enumerable:!0,get:function(){s(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},n={},r=Symbol("assign detection"),o="abcdefghijklmnopqrst";return e[r]=7,o.split("").forEach((function(t){n[t]=t})),7!==a({},e)[r]||i(a({},n)).join("")!==o}))?function(e,r){for(var a=f(e),s=arguments.length,d=1,h=o.f,p=u.f;s>d;)for(var v,y=c(arguments[d++]),b=h?l(i(y),h(y)):i(y),g=b.length,m=0;g>m;)v=b[m++],t&&!n(p,y,v)||(a[v]=y[v]);return a}:a,kr}();t({target:"Object",stat:!0,arity:2,forced:Object.assign!==e},{assign:e})}();var Nr,Hr,Ir,Lr={};!function(){if(Ir)return Lr;Ir=1;var t=rr(),e=Ke(),n=function(){if(Hr)return Nr;Hr=1;var t=rr(),e=ir();return Nr=t?{}.toString:function(){return"[object "+e(this)+"]"}}();t||e(Object.prototype,"toString",n,{unsafe:!0})}();var Wr,Dr,Mr,_r,zr,Xr,Yr,qr,Gr,Vr={};function Ur(){if(Dr)return Wr;Dr=1;var t=ir(),e=String;return Wr=function(n){if("Symbol"===t(n))throw new TypeError("Cannot convert a Symbol value to a string");return e(n)}}function Kr(){return _r?Mr:(_r=1,Mr="\t\n\v\f\r                 \u2028\u2029\ufeff")}function Qr(){if(qr)return Yr;qr=1;var t=h(),e=O(),n=bt(),r=Ur(),i=function(){if(Xr)return zr;Xr=1;var t=bt(),e=wt(),n=Ur(),r=Kr(),i=t("".replace),o=RegExp("^["+r+"]+"),u=RegExp("(^|[^"+r+"])["+r+"]+$"),f=function(t){return function(r){var f=n(e(r));return 1&t&&(f=i(f,o,"")),2&t&&(f=i(f,u,"$1")),f}};return zr={start:f(1),end:f(2),trim:f(3)}}().trim,o=Kr(),u=t.parseInt,f=t.Symbol,c=f&&f.iterator,a=/^[+-]?0x/i,s=n(a.exec),l=8!==u(o+"08")||22!==u(o+"0x16")||c&&!e((function(){u(Object(c))}));return Yr=l?function(t,e){var n=i(r(t));return u(n,e>>>0||(s(a,n)?16:10))}:u}!function(){if(Gr)return Vr;Gr=1;var t=tr(),e=Qr();t({global:!0,forced:parseInt!==e},{parseInt:e})}();var Zr=t.fn.bootstrapTable.utils;Object.assign(t.fn.bootstrapTable.defaults,{fixedColumns:!1,fixedNumber:0,fixedRightNumber:0}),t.BootstrapTable=function(r){function i(){return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,i),e(this,i,arguments)}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&u(t,e)}(i,r),n(i,[{key:"fixedColumnsSupported",value:function(){return this.options.fixedColumns&&!this.options.detailView&&!this.options.cardView}},{key:"initContainer",value:function(){f(i,"initContainer",this)([]),this.fixedColumnsSupported()&&(this.options.fixedNumber&&(this.$tableContainer.append('
    '),this.$fixedColumns=this.$tableContainer.find(".fixed-columns")),this.options.fixedRightNumber&&(this.$tableContainer.append('
    '),this.$fixedColumnsRight=this.$tableContainer.find(".fixed-columns-right")))}},{key:"initBody",value:function(){for(var t=arguments.length,e=new Array(t),n=0;ni.clientWidth?Zr.getScrollBarWidth():0,u=t.$tableContainer.outerHeight(!0)-o-1;return e.css({height:u}),r.css({height:u-n.height()}),r};this.needFixedColumns&&this.options.fixedNumber&&(this.$fixedBody=e(this.$fixedColumns,this.$fixedHeader)),this.needFixedColumns&&this.options.fixedRightNumber&&this.$fixedColumnsRight&&(this.$fixedBodyRight=e(this.$fixedColumnsRight,this.$fixedHeaderRight),this.$fixedBodyRight.scrollLeft(this.$fixedBodyRight.find("table").width()),this.$fixedBodyRight.css("overflow-y",this.options.height?"auto":"hidden"))}},{key:"getFixedColumnsWidth",value:function(t){var e=this.getVisibleFields(),n=0,r=this.options.fixedNumber,i=0;t&&(e=e.reverse(),r=this.options.fixedRightNumber,i=parseInt(this.$tableHeader.css("margin-right"),10));for(var o=0;o-1?"DOMMouseScroll":"mousewheel";this.needFixedColumns&&this.options.fixedNumber&&this.$fixedBody&&(this.$fixedBody.find("tr").hover((function(t){n(t,!0)}),(function(t){n(t,!1)})),this.$fixedBody[0].addEventListener(r,(function(t){!function(t,n){var r,i,o,u,f,c=(i=0,o=0,u=0,f=0,"detail"in(r=t)&&(o=r.detail),"wheelDelta"in r&&(o=-r.wheelDelta/120),"wheelDeltaY"in r&&(o=-r.wheelDeltaY/120),"wheelDeltaX"in r&&(i=-r.wheelDeltaX/120),"axis"in r&&r.axis===r.HORIZONTAL_AXIS&&(i=o,o=0),u=10*i,f=10*o,"deltaY"in r&&(f=r.deltaY),"deltaX"in r&&(u=r.deltaX),(u||f)&&r.deltaMode&&(1===r.deltaMode?(u*=40,f*=40):(u*=800,f*=800)),u&&!i&&(i=u<1?-1:1),f&&!o&&(o=f<1?-1:1),{spinX:i,spinY:o,pixelX:u,pixelY:f}),a=Math.ceil(c.pixelY),s=e.$tableBody.scrollTop()+a;(a<0&&s>0||a>0&&s */ var Utils = $.fn.bootstrapTable.utils var UtilsCookie = { cookieIds: { sortOrder: 'bs.table.sortOrder', sortName: 'bs.table.sortName', sortPriority: 'bs.table.sortPriority', pageNumber: 'bs.table.pageNumber', pageList: 'bs.table.pageList', hiddenColumns: 'bs.table.hiddenColumns', columns: 'bs.table.columns', cardView: 'bs.table.cardView', customView: 'bs.table.customView', searchText: 'bs.table.searchText', reorderColumns: 'bs.table.reorderColumns', filterControl: 'bs.table.filterControl', filterBy: 'bs.table.filterBy' }, getCurrentHeader (that) { return that.options.height ? that.$tableHeader : that.$header }, getCurrentSearchControls (that) { return that.options.height ? 'table select, table input' : 'select, input' }, isCookieSupportedByBrowser () { return navigator.cookieEnabled }, isCookieEnabled (that, cookieName) { if (cookieName === 'bs.table.columns') { return that.options.cookiesEnabled.includes('bs.table.hiddenColumns') } return that.options.cookiesEnabled.includes(cookieName) }, setCookie (that, cookieName, cookieValue) { if ( !that.options.cookie || !UtilsCookie.isCookieEnabled(that, cookieName) ) { return } return that._storage.setItem(`${that.options.cookieIdTable}.${cookieName}`, cookieValue) }, getCookie (that, cookieName) { if ( !cookieName || !UtilsCookie.isCookieEnabled(that, cookieName) ) { return null } return that._storage.getItem(`${that.options.cookieIdTable}.${cookieName}`) }, deleteCookie (that, cookieName) { return that._storage.removeItem(`${that.options.cookieIdTable}.${cookieName}`) }, calculateExpiration (cookieExpire) { const time = cookieExpire.replace(/[0-9]*/, '') // s,mi,h,d,m,y cookieExpire = cookieExpire.replace(/[A-Za-z]{1,2}/, '') // number switch (time.toLowerCase()) { case 's': cookieExpire = +cookieExpire break case 'mi': cookieExpire *= 60 break case 'h': cookieExpire = cookieExpire * 60 * 60 break case 'd': cookieExpire = cookieExpire * 24 * 60 * 60 break case 'm': cookieExpire = cookieExpire * 30 * 24 * 60 * 60 break case 'y': cookieExpire = cookieExpire * 365 * 24 * 60 * 60 break default: cookieExpire = undefined break } if (!cookieExpire) { return '' } const d = new Date() d.setTime(d.getTime() + cookieExpire * 1000) return d.toGMTString() }, initCookieFilters (that) { setTimeout(() => { const parsedCookieFilters = JSON.parse( UtilsCookie.getCookie(that, UtilsCookie.cookieIds.filterControl)) if (!that._filterControlValuesLoaded && parsedCookieFilters) { const cachedFilters = {} const header = UtilsCookie.getCurrentHeader(that) const searchControls = UtilsCookie.getCurrentSearchControls(that) const applyCookieFilters = (element, filteredCookies) => { filteredCookies.forEach(cookie => { const value = element.value.toString() const text = cookie.text if ( text === '' || element.type === 'radio' && value !== text ) { return } if ( element.tagName === 'INPUT' && element.type === 'radio' && value === text ) { element.checked = true cachedFilters[cookie.field] = text } else if (element.tagName === 'INPUT') { element.value = text cachedFilters[cookie.field] = text } else if ( element.tagName === 'SELECT' && that.options.filterControlContainer ) { element.value = text cachedFilters[cookie.field] = text } else if (text !== '' && element.tagName === 'SELECT') { cachedFilters[cookie.field] = text for (const currentElement of element) { if (currentElement.value === text) { currentElement.selected = true return } } const option = document.createElement('option') option.value = text option.text = text element.add(option, element[1]) element.selectedIndex = 1 } }) } let filterContainer = header if (that.options.filterControlContainer) { filterContainer = $(`${that.options.filterControlContainer}`) } filterContainer.find(searchControls).each(function () { const field = $(this).closest('[data-field]').data('field') const filteredCookies = parsedCookieFilters.filter(cookie => cookie.field === field) applyCookieFilters(this, filteredCookies) }) that.initColumnSearch(cachedFilters) that._filterControlValuesLoaded = true that.initServer() } }, 250) } } Object.assign($.fn.bootstrapTable.defaults, { cookie: false, cookieExpire: '2h', cookiePath: null, cookieDomain: null, cookieSecure: null, cookieSameSite: 'Lax', cookieIdTable: '', cookiesEnabled: [ 'bs.table.sortOrder', 'bs.table.sortName', 'bs.table.sortPriority', 'bs.table.pageNumber', 'bs.table.pageList', 'bs.table.hiddenColumns', 'bs.table.searchText', 'bs.table.filterControl', 'bs.table.filterBy', 'bs.table.reorderColumns', 'bs.table.cardView', 'bs.table.customView' ], cookieStorage: 'cookieStorage', // localStorage, sessionStorage, customStorage cookieCustomStorageGet: null, cookieCustomStorageSet: null, cookieCustomStorageDelete: null, // internal variable _filterControls: [], _filterControlValuesLoaded: false, _storage: { setItem: undefined, getItem: undefined, removeItem: undefined } }) $.fn.bootstrapTable.methods.push('getCookies') $.fn.bootstrapTable.methods.push('deleteCookie') Object.assign($.fn.bootstrapTable.utils, { setCookie: UtilsCookie.setCookie, getCookie: UtilsCookie.getCookie }) $.BootstrapTable = class extends $.BootstrapTable { init () { if (this.options.cookie) { if ( this.options.cookieStorage === 'cookieStorage' && !UtilsCookie.isCookieSupportedByBrowser() ) { throw new Error('Cookies are not enabled in this browser.') } this.configureStorage() // FilterBy logic const filterByCookieValue = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.filterBy) if (typeof filterByCookieValue === 'boolean' && !filterByCookieValue) { throw new Error('The cookie value of filterBy must be a json!') } let filterByCookie = {} try { filterByCookie = JSON.parse(filterByCookieValue) } catch (e) { console.error(e) throw new Error('Could not parse the json of the filterBy cookie!') } this.filterColumns = filterByCookie ? filterByCookie : {} // FilterControl logic this._filterControls = [] this._filterControlValuesLoaded = false this.options.cookiesEnabled = typeof this.options.cookiesEnabled === 'string' ? this.options.cookiesEnabled.replace('[', '').replace(']', '') .replace(/'/g, '').replace(/ /g, '').split(',') : this.options.cookiesEnabled if (this.options.filterControl) { this.$el.on('column-search.bs.table', (e, field, text) => { let isNewField = true for (let i = 0; i < this._filterControls.length; i++) { if (this._filterControls[i].field === field) { this._filterControls[i].text = text isNewField = false break } } if (isNewField) { this._filterControls.push({ field, text }) } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.filterControl, JSON.stringify(this._filterControls)) }).on('created-controls.bs.table', UtilsCookie.initCookieFilters(this)) } } super.init() } initServer (...args) { if ( this.options.cookie && this.options.filterControl && !this._filterControlValuesLoaded ) { const cookie = JSON.parse(UtilsCookie.getCookie(this, UtilsCookie.cookieIds.filterControl)) if (cookie) { return } } super.initServer(...args) } initTable (...args) { super.initTable(...args) this.initCookie() } onSort (...args) { super.onSort(...args) if (!this.options.cookie) { return } if (this.options.sortName === undefined || this.options.sortOrder === undefined) { UtilsCookie.deleteCookie(this, UtilsCookie.cookieIds.sortName) UtilsCookie.deleteCookie(this, UtilsCookie.cookieIds.sortOrder) } else { this.options.sortPriority = null UtilsCookie.deleteCookie(this, UtilsCookie.cookieIds.sortPriority) UtilsCookie.setCookie(this, UtilsCookie.cookieIds.sortOrder, this.options.sortOrder) UtilsCookie.setCookie(this, UtilsCookie.cookieIds.sortName, this.options.sortName) } } onMultipleSort (...args) { super.onMultipleSort(...args) if (!this.options.cookie) { return } if (this.options.sortPriority === undefined) { UtilsCookie.deleteCookie(this, UtilsCookie.cookieIds.sortPriority) } else { this.options.sortName = undefined this.options.sortOrder = undefined UtilsCookie.deleteCookie(this, UtilsCookie.cookieIds.sortName) UtilsCookie.deleteCookie(this, UtilsCookie.cookieIds.sortOrder) UtilsCookie.setCookie(this, UtilsCookie.cookieIds.sortPriority, JSON.stringify(this.options.sortPriority)) } } onPageNumber (...args) { super.onPageNumber(...args) if (!this.options.cookie) { return } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber) } onPageListChange (...args) { super.onPageListChange(...args) if (!this.options.cookie) { return } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageList, this.options.pageSize === this.options.formatAllRows() ? 'all' : this.options.pageSize) UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber) } onPagePre (...args) { super.onPagePre(...args) if (!this.options.cookie) { return } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber) } onPageNext (...args) { super.onPageNext(...args) if (!this.options.cookie) { return } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber) } _toggleColumn (...args) { super._toggleColumn(...args) if (!this.options.cookie) { return } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.hiddenColumns, JSON.stringify(this.getHiddenColumns().map(column => column.field))) UtilsCookie.setCookie(this, UtilsCookie.cookieIds.columns, JSON.stringify(this.columns.map(column => column.field))) } _toggleAllColumns (...args) { super._toggleAllColumns(...args) if (!this.options.cookie) { return } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.hiddenColumns, JSON.stringify(this.getHiddenColumns().map(column => column.field))) UtilsCookie.setCookie(this, UtilsCookie.cookieIds.columns, JSON.stringify(this.columns.map(column => column.field))) } toggleView () { super.toggleView() UtilsCookie.setCookie(this, UtilsCookie.cookieIds.cardView, this.options.cardView) } toggleCustomView () { super.toggleCustomView() UtilsCookie.setCookie(this, UtilsCookie.cookieIds.customView, this.customViewDefaultView) } selectPage (page) { super.selectPage(page) if (!this.options.cookie) { return } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, page) } onSearch (event) { super.onSearch(event, arguments.length > 1 ? arguments[1] : true) if (!this.options.cookie) { return } if (this.options.search) { UtilsCookie.setCookie(this, UtilsCookie.cookieIds.searchText, this.searchText) } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.pageNumber, this.options.pageNumber) } initHeader (...args) { if (this.options.reorderableColumns && this.options.cookie) { this.columnsSortOrder = JSON.parse(UtilsCookie.getCookie(this, UtilsCookie.cookieIds.reorderColumns)) } super.initHeader(...args) } persistReorderColumnsState (that) { UtilsCookie.setCookie(that, UtilsCookie.cookieIds.reorderColumns, JSON.stringify(that.columnsSortOrder)) } filterBy (...args) { super.filterBy(...args) if (!this.options.cookie) { return } UtilsCookie.setCookie(this, UtilsCookie.cookieIds.filterBy, JSON.stringify(this.filterColumns)) } initCookie () { if (!this.options.cookie) { return } if (this.options.cookieIdTable === '' || this.options.cookieExpire === '') { console.error('Configuration error. Please review the cookieIdTable and the cookieExpire property. If the properties are correct, then this browser does not support cookies.') this.options.cookie = false // Make sure that the cookie extension is disabled return } const sortOrderCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.sortOrder) const sortOrderNameCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.sortName) let sortPriorityCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.sortPriority) const pageNumberCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.pageNumber) const pageListCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.pageList) const searchTextCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.searchText) const cardViewCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.cardView) const customViewCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.customView) const hiddenColumnsCookieValue = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.hiddenColumns) const columnsCookieValue = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.columns) let hiddenColumnsCookie = {} let columnsCookie = {} try { hiddenColumnsCookie = JSON.parse(hiddenColumnsCookieValue) columnsCookie = JSON.parse(columnsCookieValue) } catch (e) { console.error(e) throw new Error('Could not parse the json of the columns cookie!') } try { sortPriorityCookie = JSON.parse(sortPriorityCookie) } catch (e) { console.error(e) throw new Error('Could not parse the json of the sortPriority cookie!', sortPriorityCookie) } if (!sortPriorityCookie) { // sortOrder this.options.sortOrder = sortOrderCookie ? sortOrderCookie : this.options.sortOrder // sortName this.options.sortName = sortOrderNameCookie ? sortOrderNameCookie : this.options.sortName } else { this.options.sortOrder = undefined this.options.sortName = undefined } // sortPriority this.options.sortPriority = sortPriorityCookie ? sortPriorityCookie : this.options.sortPriority if (this.options.sortOrder || this.options.sortName) { // sortPriority this.options.sortPriority = null } // pageNumber this.options.pageNumber = pageNumberCookie ? +pageNumberCookie : this.options.pageNumber // pageSize this.options.pageSize = pageListCookie ? pageListCookie === 'all' ? this.options.formatAllRows() : +pageListCookie : this.options.pageSize // searchText if (UtilsCookie.isCookieEnabled(this, UtilsCookie.cookieIds.searchText) && this.options.searchText === '') { this.options.searchText = searchTextCookie ? searchTextCookie : '' } // cardView if (cardViewCookie !== null) { this.options.cardView = cardViewCookie === 'true' ? cardViewCookie : false } this.customViewDefaultView = customViewCookie === 'true' if (hiddenColumnsCookie) { columnsCookie = columnsCookie || this.columns.map(column => column.field) for (const column of this.columns) { if ( !column.switchable || !columnsCookie.includes(column.field) ) { continue } column.visible = this.isSelectionColumn(column) || !hiddenColumnsCookie.includes(column.field) } } } getCookies () { const cookies = {} for (const [key, value] of Object.entries(UtilsCookie.cookieIds)) { cookies[key] = UtilsCookie.getCookie(this, value) if (['columns', 'hiddenColumns', 'sortPriority'].includes(key)) { cookies[key] = JSON.parse(cookies[key]) } } return cookies } deleteCookie (cookieName) { if (!cookieName || !this.options.cookie) { return } UtilsCookie.deleteCookie(this, UtilsCookie.cookieIds[cookieName]) } configureStorage () { this._storage = {} switch (this.options.cookieStorage) { case 'cookieStorage': this._storage.setItem = (cookieName, cookieValue) => { document.cookie = [ cookieName, '=', encodeURIComponent(cookieValue), `; expires=${UtilsCookie.calculateExpiration(this.options.cookieExpire)}`, this.options.cookiePath ? `; path=${this.options.cookiePath}` : '', this.options.cookieDomain ? `; domain=${this.options.cookieDomain}` : '', this.options.cookieSecure ? '; secure' : '', `;SameSite=${this.options.cookieSameSite}` ].join('') } this._storage.getItem = cookieName => { const value = `; ${document.cookie}` const parts = value.split(`; ${cookieName}=`) return parts.length === 2 ? decodeURIComponent(parts.pop().split(';').shift()) : null } this._storage.removeItem = cookieName => { document.cookie = [ encodeURIComponent(cookieName), '=', '; expires=Thu, 01 Jan 1970 00:00:00 GMT', this.options.cookiePath ? `; path=${this.options.cookiePath}` : '', this.options.cookieDomain ? `; domain=${this.options.cookieDomain}` : '', `;SameSite=${this.options.cookieSameSite}` ].join('') } break case 'localStorage': this._storage.setItem = (cookieName, cookieValue) => { localStorage.setItem(cookieName, cookieValue) } this._storage.getItem = cookieName => localStorage.getItem(cookieName) this._storage.removeItem = cookieName => { localStorage.removeItem(cookieName) } break case 'sessionStorage': this._storage.setItem = (cookieName, cookieValue) => { sessionStorage.setItem(cookieName, cookieValue) } this._storage.getItem = cookieName => sessionStorage.getItem(cookieName) this._storage.removeItem = cookieName => { sessionStorage.removeItem(cookieName) } break case 'customStorage': if ( !this.options.cookieCustomStorageSet || !this.options.cookieCustomStorageGet || !this.options.cookieCustomStorageDelete ) { throw new Error('The following options must be set while using the customStorage: cookieCustomStorageSet, cookieCustomStorageGet and cookieCustomStorageDelete') } this._storage.setItem = (cookieName, cookieValue) => { Utils.calculateObjectValue(this.options, this.options.cookieCustomStorageSet, [cookieName, cookieValue], '') } this._storage.getItem = cookieName => Utils.calculateObjectValue(this.options, this.options.cookieCustomStorageGet, [cookieName], '') this._storage.removeItem = cookieName => { Utils.calculateObjectValue(this.options, this.options.cookieCustomStorageDelete, [cookieName], '') } break default: throw new Error('Storage method not supported.') } } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/custom-view/bootstrap-table-custom-view.js ================================================ /** * @author: Dustin Utecht * @github: https://github.com/UtechtDustin */ var Utils = $.fn.bootstrapTable.utils Object.assign($.fn.bootstrapTable.defaults, { customView: false, showCustomView: false, customViewDefaultView: false }) Utils.assignIcons($.fn.bootstrapTable.icons, 'customViewOn', { glyphicon: 'glyphicon-list', fa: 'fa-list', bi: 'bi-list', icon: 'list', 'material-icons': 'list' }) Utils.assignIcons($.fn.bootstrapTable.icons, 'customViewOff', { glyphicon: 'glyphicon glyphicon-eye-open', fa: 'fa-th', bi: 'bi-grid', icon: 'grid_on', 'material-icons': 'grid_on' }) Object.assign($.fn.bootstrapTable.defaults, { onCustomViewPostBody () { return false }, onCustomViewPreBody () { return false }, onToggleCustomView () { return false } }) Object.assign($.fn.bootstrapTable.locales, { formatToggleCustomViewOn () { return 'Show custom view' }, formatToggleCustomViewOff () { return 'Hide custom view' } }) Object.assign($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales) $.fn.bootstrapTable.methods.push('toggleCustomView') Object.assign($.fn.bootstrapTable.events, { 'custom-view-post-body.bs.table': 'onCustomViewPostBody', 'custom-view-pre-body.bs.table': 'onCustomViewPreBody', 'toggle-custom-view.bs.table': 'onToggleCustomView' }) $.BootstrapTable = class extends $.BootstrapTable { init () { this.customViewDefaultView = this.options.customViewDefaultView super.init() } initToolbar (...args) { if (this.options.customView && this.options.showCustomView) { this.buttons = Object.assign(this.buttons, { customView: { text: this.options.customViewDefaultView ? this.options.formatToggleCustomViewOff() : this.options.formatToggleCustomViewOn(), icon: this.options.customViewDefaultView ? this.options.icons.customViewOn : this.options.icons.customViewOff, event: this.toggleCustomView, attributes: { 'aria-label': this.options.customViewDefaultView ? this.options.formatToggleCustomViewOff() : this.options.formatToggleCustomViewOn(), title: this.options.customViewDefaultView ? this.options.formatToggleCustomViewOff() : this.options.formatToggleCustomViewOn() } } }) } super.initToolbar(...args) } initBody () { super.initBody() if (!this.options.customView) { return } const $table = this.$el const $customViewContainer = this.$container.find('.fixed-table-custom-view') $table.hide() $customViewContainer.hide() if (!this.options.customView || !this.customViewDefaultView) { $table.show() return } const data = this.getData().slice(this.pageFrom - 1, this.pageTo) const value = Utils.calculateObjectValue(this, this.options.customView, [data], '') this.trigger('custom-view-pre-body', data, value) if ($customViewContainer.length === 1) { $customViewContainer.show().html(value) } else { this.$tableBody.after(`
    ${value}
    `) } this.trigger('custom-view-post-body', data, value) } toggleCustomView () { this.customViewDefaultView = !this.customViewDefaultView const icon = this.options.showButtonIcons ? this.customViewDefaultView ? this.options.icons.customViewOn : this.options.icons.customViewOff : '' const text = this.options.showButtonText ? this.customViewDefaultView ? this.options.formatToggleCustomViewOff() : this.options.formatToggleCustomViewOn() : '' this.$toolbar.find('button[name="customView"]') .html(`${Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, icon)} ${text}`) .attr('aria-label', text) .attr('title', text) this.initBody() this.trigger('toggle-custom-view', this.customViewDefaultView) } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-editable.css ================================================ /*! X-editable - v1.5.3 * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * http://github.com/vitalets/x-editable * Copyright (c) 2019 Vitaliy Potapov; Licensed MIT */ .editableform { margin-bottom: 0; /* overwrites bootstrap margin */ } .editableform .control-group { margin-bottom: 0; /* overwrites bootstrap margin */ white-space: nowrap; /* prevent wrapping buttons on new line */ line-height: 20px; /* overwriting bootstrap line-height. See #133 */ } /* BS3 fix: stop css from breaking when the form is inside a popup and inside a form with the class .form-horizontal See: https://github.com/vitalets/x-editable/issues/682 */ .form-horizontal .editable-popup .editableform .form-group { margin-left:0; margin-right:0; } /* BS3 width:1005 for inputs breaks editable form in popup See: https://github.com/vitalets/x-editable/issues/393 */ .editableform .form-control { width: auto; } .editable-buttons { display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ vertical-align: top; margin-left: 7px; /* inline-block emulation for IE7*/ zoom: 1; *display: inline; } .editable-buttons.editable-buttons-bottom { display: block; margin-top: 7px; margin-left: 0; } .editable-input { vertical-align: top; display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */ width: auto; /* bootstrap-responsive has width: 100% that breakes layout */ white-space: normal; /* reset white-space decalred in parent*/ /* display-inline emulation for IE7*/ zoom: 1; *display: inline; } .editable-buttons .editable-cancel { margin-left: 7px; } /*for jquery-ui buttons need set height to look more pretty*/ .editable-buttons button.ui-button-icon-only { height: 24px; width: 30px; } .editableform-loading { background: url('loading.gif') center center no-repeat; height: 25px; width: auto; min-width: 25px; } .editable-inline .editableform-loading { background-position: left 5px; } .editable-error-block { max-width: 300px; margin: 5px 0 0 0; width: auto; white-space: normal; } /*add padding for jquery ui*/ .editable-error-block.ui-state-error { padding: 3px; } .editable-error { color: red; } /* ---- For specific types ---- */ .editableform .editable-date { padding: 0; margin: 0; float: left; } /* move datepicker icon to center of add-on button. See https://github.com/vitalets/x-editable/issues/183 */ .editable-inline .add-on .icon-th { margin-top: 3px; margin-left: 1px; } /* checklist vertical alignment */ .editable-checklist label input[type="checkbox"], .editable-checklist label span { vertical-align: middle; margin: 0; } .editable-checklist label { white-space: nowrap; } /* set exact width of textarea to fit buttons toolbar */ .editable-wysihtml5 { width: 566px; height: 250px; } /* clear button shown as link in date inputs */ .editable-clear { clear: both; font-size: 0.9em; text-decoration: none; text-align: right; } /* IOS-style clear button for text inputs */ .editable-clear-x { background: url('clear.png') center center no-repeat; display: block; width: 13px; height: 13px; position: absolute; opacity: 0.6; z-index: 100; top: 50%; right: 6px; margin-top: -6px; } .editable-clear-x:hover { opacity: 1; } .editable-pre-wrapped { white-space: pre-wrap; } .editable-container.editable-popup { max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ } .editable-container.popover { width: auto; /* without this rule popover does not stretch */ } .editable-container.editable-inline { display: inline-block; vertical-align: middle; width: auto; /* inline-block emulation for IE7*/ zoom: 1; *display: inline; } .editable-container.ui-widget { font-size: inherit; /* jqueryui widget font 1.1em too big, overwrite it */ z-index: 9990; /* should be less than select2 dropdown z-index to close dropdown first when click */ } .editable-click, a.editable-click, a.editable-click:hover { text-decoration: none; border-bottom: dashed 1px #0088cc; } .editable-click.editable-disabled, a.editable-click.editable-disabled, a.editable-click.editable-disabled:hover { color: #585858; cursor: default; border-bottom: none; } .editable-empty, .editable-empty:hover, .editable-empty:focus{ font-style: italic; color: #DD1144; /* border-bottom: none; */ text-decoration: none; } .editable-unsaved { font-weight: bold; } .editable-unsaved:after { /* content: '*'*/ } .editable-bg-transition { -webkit-transition: background-color 1400ms ease-out; -moz-transition: background-color 1400ms ease-out; -o-transition: background-color 1400ms ease-out; -ms-transition: background-color 1400ms ease-out; transition: background-color 1400ms ease-out; } /*see https://github.com/vitalets/x-editable/issues/139 */ .form-horizontal .editable { padding-top: 5px; display:inline-block; } /*! * Datepicker for Bootstrap * * Copyright 2012 Stefan Petre * Improvements by Andrew Rowls * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0 * */ .datepicker { padding: 4px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; direction: ltr; /*.dow { border-top: 1px solid #ddd !important; }*/ } .datepicker-inline { width: 220px; } .datepicker.datepicker-rtl { direction: rtl; } .datepicker.datepicker-rtl table tr td span { float: right; } .datepicker-dropdown { top: 0; left: 0; } .datepicker-dropdown:before { content: ''; display: inline-block; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 7px solid #ccc; border-bottom-color: rgba(0, 0, 0, 0.2); position: absolute; top: -7px; left: 6px; } .datepicker-dropdown:after { content: ''; display: inline-block; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 6px solid #ffffff; position: absolute; top: -6px; left: 7px; } .datepicker > div { display: none; } .datepicker.days div.datepicker-days { display: block; } .datepicker.months div.datepicker-months { display: block; } .datepicker.years div.datepicker-years { display: block; } .datepicker table { margin: 0; } .datepicker td, .datepicker th { text-align: center; width: 20px; height: 20px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; border: none; } .table-striped .datepicker table tr td, .table-striped .datepicker table tr th { background-color: transparent; } .datepicker table tr td.day:hover { background: #eeeeee; cursor: pointer; } .datepicker table tr td.old, .datepicker table tr td.new { color: #999999; } .datepicker table tr td.disabled, .datepicker table tr td.disabled:hover { background: none; color: #999999; cursor: default; } .datepicker table tr td.today, .datepicker table tr td.today:hover, .datepicker table tr td.today.disabled, .datepicker table tr td.today.disabled:hover { background-color: #fde19a; background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a); background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a); background-image: -o-linear-gradient(top, #fdd49a, #fdf59a); background-image: linear-gradient(top, #fdd49a, #fdf59a); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); border-color: #fdf59a #fdf59a #fbed50; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #000; } .datepicker table tr td.today:hover, .datepicker table tr td.today:hover:hover, .datepicker table tr td.today.disabled:hover, .datepicker table tr td.today.disabled:hover:hover, .datepicker table tr td.today:active, .datepicker table tr td.today:hover:active, .datepicker table tr td.today.disabled:active, .datepicker table tr td.today.disabled:hover:active, .datepicker table tr td.today.active, .datepicker table tr td.today:hover.active, .datepicker table tr td.today.disabled.active, .datepicker table tr td.today.disabled:hover.active, .datepicker table tr td.today.disabled, .datepicker table tr td.today:hover.disabled, .datepicker table tr td.today.disabled.disabled, .datepicker table tr td.today.disabled:hover.disabled, .datepicker table tr td.today[disabled], .datepicker table tr td.today:hover[disabled], .datepicker table tr td.today.disabled[disabled], .datepicker table tr td.today.disabled:hover[disabled] { background-color: #fdf59a; } .datepicker table tr td.today:active, .datepicker table tr td.today:hover:active, .datepicker table tr td.today.disabled:active, .datepicker table tr td.today.disabled:hover:active, .datepicker table tr td.today.active, .datepicker table tr td.today:hover.active, .datepicker table tr td.today.disabled.active, .datepicker table tr td.today.disabled:hover.active { background-color: #fbf069 \9; } .datepicker table tr td.today:hover:hover { color: #000; } .datepicker table tr td.today.active:hover { color: #fff; } .datepicker table tr td.range, .datepicker table tr td.range:hover, .datepicker table tr td.range.disabled, .datepicker table tr td.range.disabled:hover { background: #eeeeee; -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; } .datepicker table tr td.range.today, .datepicker table tr td.range.today:hover, .datepicker table tr td.range.today.disabled, .datepicker table tr td.range.today.disabled:hover { background-color: #f3d17a; background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a); background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a); background-image: -o-linear-gradient(top, #f3c17a, #f3e97a); background-image: linear-gradient(top, #f3c17a, #f3e97a); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); border-color: #f3e97a #f3e97a #edde34; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; } .datepicker table tr td.range.today:hover, .datepicker table tr td.range.today:hover:hover, .datepicker table tr td.range.today.disabled:hover, .datepicker table tr td.range.today.disabled:hover:hover, .datepicker table tr td.range.today:active, .datepicker table tr td.range.today:hover:active, .datepicker table tr td.range.today.disabled:active, .datepicker table tr td.range.today.disabled:hover:active, .datepicker table tr td.range.today.active, .datepicker table tr td.range.today:hover.active, .datepicker table tr td.range.today.disabled.active, .datepicker table tr td.range.today.disabled:hover.active, .datepicker table tr td.range.today.disabled, .datepicker table tr td.range.today:hover.disabled, .datepicker table tr td.range.today.disabled.disabled, .datepicker table tr td.range.today.disabled:hover.disabled, .datepicker table tr td.range.today[disabled], .datepicker table tr td.range.today:hover[disabled], .datepicker table tr td.range.today.disabled[disabled], .datepicker table tr td.range.today.disabled:hover[disabled] { background-color: #f3e97a; } .datepicker table tr td.range.today:active, .datepicker table tr td.range.today:hover:active, .datepicker table tr td.range.today.disabled:active, .datepicker table tr td.range.today.disabled:hover:active, .datepicker table tr td.range.today.active, .datepicker table tr td.range.today:hover.active, .datepicker table tr td.range.today.disabled.active, .datepicker table tr td.range.today.disabled:hover.active { background-color: #efe24b \9; } .datepicker table tr td.selected, .datepicker table tr td.selected:hover, .datepicker table tr td.selected.disabled, .datepicker table tr td.selected.disabled:hover { background-color: #9e9e9e; background-image: -moz-linear-gradient(top, #b3b3b3, #808080); background-image: -ms-linear-gradient(top, #b3b3b3, #808080); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); background-image: -webkit-linear-gradient(top, #b3b3b3, #808080); background-image: -o-linear-gradient(top, #b3b3b3, #808080); background-image: linear-gradient(top, #b3b3b3, #808080); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); border-color: #808080 #808080 #595959; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } .datepicker table tr td.selected:hover, .datepicker table tr td.selected:hover:hover, .datepicker table tr td.selected.disabled:hover, .datepicker table tr td.selected.disabled:hover:hover, .datepicker table tr td.selected:active, .datepicker table tr td.selected:hover:active, .datepicker table tr td.selected.disabled:active, .datepicker table tr td.selected.disabled:hover:active, .datepicker table tr td.selected.active, .datepicker table tr td.selected:hover.active, .datepicker table tr td.selected.disabled.active, .datepicker table tr td.selected.disabled:hover.active, .datepicker table tr td.selected.disabled, .datepicker table tr td.selected:hover.disabled, .datepicker table tr td.selected.disabled.disabled, .datepicker table tr td.selected.disabled:hover.disabled, .datepicker table tr td.selected[disabled], .datepicker table tr td.selected:hover[disabled], .datepicker table tr td.selected.disabled[disabled], .datepicker table tr td.selected.disabled:hover[disabled] { background-color: #808080; } .datepicker table tr td.selected:active, .datepicker table tr td.selected:hover:active, .datepicker table tr td.selected.disabled:active, .datepicker table tr td.selected.disabled:hover:active, .datepicker table tr td.selected.active, .datepicker table tr td.selected:hover.active, .datepicker table tr td.selected.disabled.active, .datepicker table tr td.selected.disabled:hover.active { background-color: #666666 \9; } .datepicker table tr td.active, .datepicker table tr td.active:hover, .datepicker table tr td.active.disabled, .datepicker table tr td.active.disabled:hover { background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -ms-linear-gradient(top, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); background-image: -o-linear-gradient(top, #0088cc, #0044cc); background-image: linear-gradient(top, #0088cc, #0044cc); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } .datepicker table tr td.active:hover, .datepicker table tr td.active:hover:hover, .datepicker table tr td.active.disabled:hover, .datepicker table tr td.active.disabled:hover:hover, .datepicker table tr td.active:active, .datepicker table tr td.active:hover:active, .datepicker table tr td.active.disabled:active, .datepicker table tr td.active.disabled:hover:active, .datepicker table tr td.active.active, .datepicker table tr td.active:hover.active, .datepicker table tr td.active.disabled.active, .datepicker table tr td.active.disabled:hover.active, .datepicker table tr td.active.disabled, .datepicker table tr td.active:hover.disabled, .datepicker table tr td.active.disabled.disabled, .datepicker table tr td.active.disabled:hover.disabled, .datepicker table tr td.active[disabled], .datepicker table tr td.active:hover[disabled], .datepicker table tr td.active.disabled[disabled], .datepicker table tr td.active.disabled:hover[disabled] { background-color: #0044cc; } .datepicker table tr td.active:active, .datepicker table tr td.active:hover:active, .datepicker table tr td.active.disabled:active, .datepicker table tr td.active.disabled:hover:active, .datepicker table tr td.active.active, .datepicker table tr td.active:hover.active, .datepicker table tr td.active.disabled.active, .datepicker table tr td.active.disabled:hover.active { background-color: #003399 \9; } .datepicker table tr td span { display: block; width: 23%; height: 54px; line-height: 54px; float: left; margin: 1%; cursor: pointer; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } .datepicker table tr td span:hover { background: #eeeeee; } .datepicker table tr td span.disabled, .datepicker table tr td span.disabled:hover { background: none; color: #999999; cursor: default; } .datepicker table tr td span.active, .datepicker table tr td span.active:hover, .datepicker table tr td span.active.disabled, .datepicker table tr td span.active.disabled:hover { background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -ms-linear-gradient(top, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); background-image: -o-linear-gradient(top, #0088cc, #0044cc); background-image: linear-gradient(top, #0088cc, #0044cc); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } .datepicker table tr td span.active:hover, .datepicker table tr td span.active:hover:hover, .datepicker table tr td span.active.disabled:hover, .datepicker table tr td span.active.disabled:hover:hover, .datepicker table tr td span.active:active, .datepicker table tr td span.active:hover:active, .datepicker table tr td span.active.disabled:active, .datepicker table tr td span.active.disabled:hover:active, .datepicker table tr td span.active.active, .datepicker table tr td span.active:hover.active, .datepicker table tr td span.active.disabled.active, .datepicker table tr td span.active.disabled:hover.active, .datepicker table tr td span.active.disabled, .datepicker table tr td span.active:hover.disabled, .datepicker table tr td span.active.disabled.disabled, .datepicker table tr td span.active.disabled:hover.disabled, .datepicker table tr td span.active[disabled], .datepicker table tr td span.active:hover[disabled], .datepicker table tr td span.active.disabled[disabled], .datepicker table tr td span.active.disabled:hover[disabled] { background-color: #0044cc; } .datepicker table tr td span.active:active, .datepicker table tr td span.active:hover:active, .datepicker table tr td span.active.disabled:active, .datepicker table tr td span.active.disabled:hover:active, .datepicker table tr td span.active.active, .datepicker table tr td span.active:hover.active, .datepicker table tr td span.active.disabled.active, .datepicker table tr td span.active.disabled:hover.active { background-color: #003399 \9; } .datepicker table tr td span.old, .datepicker table tr td span.new { color: #999999; } .datepicker th.datepicker-switch { width: 145px; } .datepicker thead tr:first-child th, .datepicker tfoot tr th { cursor: pointer; } .datepicker thead tr:first-child th:hover, .datepicker tfoot tr th:hover { background: #eeeeee; } .datepicker .cw { font-size: 10px; width: 12px; padding: 0 2px 0 5px; vertical-align: middle; } .datepicker thead tr:first-child th.cw { cursor: default; background-color: transparent; } .input-append.date .add-on i, .input-prepend.date .add-on i { display: block; cursor: pointer; width: 16px; height: 16px; } .input-daterange input { text-align: center; } .input-daterange input:first-child { -webkit-border-radius: 3px 0 0 3px; -moz-border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px; } .input-daterange input:last-child { -webkit-border-radius: 0 3px 3px 0; -moz-border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0; } .input-daterange .add-on { display: inline-block; width: auto; min-width: 16px; height: 18px; padding: 4px 5px; font-weight: normal; line-height: 18px; text-align: center; text-shadow: 0 1px 0 #ffffff; vertical-align: middle; background-color: #eeeeee; border: 1px solid #ccc; margin-left: -5px; margin-right: -5px; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/editable/bootstrap-table-editable.js ================================================ /** * @author zhixin wen * extensions: https://github.com/vitalets/x-editable */ var Utils = $.fn.bootstrapTable.utils Object.assign($.fn.bootstrapTable.defaults, { editable: true, onEditableInit () { return false }, onEditableSave (field, row, rowIndex, oldValue, $el) { return false }, onEditableShown (field, row, $el, editable) { return false }, onEditableHidden (field, row, $el, reason) { return false } }) Object.assign($.fn.bootstrapTable.columnDefaults, { alwaysUseFormatter: false }) Object.assign($.fn.bootstrapTable.events, { 'editable-init.bs.table': 'onEditableInit', 'editable-save.bs.table': 'onEditableSave', 'editable-shown.bs.table': 'onEditableShown', 'editable-hidden.bs.table': 'onEditableHidden' }) $.BootstrapTable = class extends $.BootstrapTable { initTable () { super.initTable() if (!this.options.editable) { return } this.editedCells = [] $.each(this.columns, (i, column) => { if (!column.editable) { return } const editableOptions = {} const editableDataPrefix = 'editable-' const processDataOptions = (key, value) => { // Replace camel case with dashes. const dashKey = key.replace(/([A-Z])/g, $1 => `-${$1.toLowerCase()}`) if (dashKey.indexOf(editableDataPrefix) === 0) { editableOptions[dashKey.replace(editableDataPrefix, 'data-')] = value } } const formatterIsSet = column.formatter ? true : false $.each(this.options, processDataOptions) column.formatter = column.formatter || (value => value) column._formatter = column._formatter ? column._formatter : column.formatter column.formatter = (value, row, index, field) => { let result = Utils.calculateObjectValue(column, column._formatter, [value, row, index, field], value) result = typeof result === 'undefined' || result === null ? this.options.undefinedText : result if (this.options.uniqueId !== undefined && !column.alwaysUseFormatter) { const uniqueId = Utils.getItemField(row, this.options.uniqueId, false) if ($.inArray(column.field + uniqueId, this.editedCells) !== -1) { result = value } } $.each(column, processDataOptions) const editableOpts = Utils.calculateObjectValue(column, column.editable, [index, row], {}) const noEditFormatter = editableOpts.hasOwnProperty('noEditFormatter') && editableOpts.noEditFormatter(value, row, index, field) if (noEditFormatter) { return noEditFormatter } let editableDataMarkup = '' $.each(editableOptions, (key, value) => { editableDataMarkup += ` ${key}="${value}"` }) return `${formatterIsSet ? result : ''}` // expand all data-editable-XXX } }) } initBody (fixedScroll) { super.initBody(fixedScroll) if (!this.options.editable) { return } $.each(this.columns, (i, column) => { if (!column.editable) { return } const data = this.getData({ escape: true }) const $field = this.$body.find(`a[data-name="${column.field}"]`) $field.each((i, element) => { const $element = $(element) const $tr = $element.closest('tr') const index = $tr.data('index') const row = data[index] const editableOpts = Utils.calculateObjectValue(column, column.editable, [index, row, $element], {}) $element.editable(editableOpts) }) $field.off('save').on('save', ({ currentTarget }, { submitValue }) => { const $this = $(currentTarget) const data = this.getData() const rowIndex = $this.parents('tr[data-index]').data('index') const row = data[rowIndex] const oldValue = row[column.field] if (this.options.uniqueId !== undefined && !column.alwaysUseFormatter) { const uniqueId = Utils.getItemField(row, this.options.uniqueId, false) if ($.inArray(column.field + uniqueId, this.editedCells) === -1) { this.editedCells.push(column.field + uniqueId) } } submitValue = Utils.escapeHTML(submitValue) $this.data('value', submitValue) row[column.field] = submitValue this.trigger('editable-save', column.field, row, rowIndex, oldValue, $this) this.initBody() }) $field.off('shown').on('shown', ({ currentTarget }, editable) => { const $this = $(currentTarget) const data = this.getData() const rowIndex = $this.parents('tr[data-index]').data('index') const row = data[rowIndex] this.trigger('editable-shown', column.field, row, $this, editable) }) $field.off('hidden').on('hidden', ({ currentTarget }, reason) => { const $this = $(currentTarget) const data = this.getData() const rowIndex = $this.parents('tr[data-index]').data('index') const row = data[rowIndex] this.trigger('editable-hidden', column.field, row, $this, reason) }) }) this.trigger('editable-init') } getData (params) { const data = super.getData(params) if (params && params.escape) { for (const row of data) { for (const [key, value] of Object.entries(row)) { if (typeof(value) !== "number") { row[key] = Utils.unescapeHTML(value) } } } } return data } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/export/bootstrap-table-export.js ================================================ /** * @author zhixin wen * extensions: https://github.com/hhurz/tableExport.jquery.plugin */ var Utils = $.fn.bootstrapTable.utils const TYPE_NAME = { json: 'JSON', xml: 'XML', png: 'PNG', csv: 'CSV', txt: 'TXT', sql: 'SQL', doc: 'MS-Word', excel: 'MS-Excel', xlsx: 'MS-Excel (OpenXML)', powerpoint: 'MS-Powerpoint', pdf: 'PDF' } Object.assign($.fn.bootstrapTable.defaults, { showExport: false, exportDataType: 'basic', // basic, all, selected exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'excel'], exportOptions: {}, exportFooter: false }) Object.assign($.fn.bootstrapTable.columnDefaults, { forceExport: false, forceHide: false }) Utils.assignIcons($.fn.bootstrapTable.icons, 'export', { glyphicon: 'glyphicon-export icon-share', fa: 'fa-download', bi: 'bi-download', icon: 'icon-download', 'material-icons': 'file_download' }) Object.assign($.fn.bootstrapTable.locales, { formatExport () { return 'Export data' } }) Object.assign($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales) $.fn.bootstrapTable.methods.push('exportTable') Object.assign($.fn.bootstrapTable.defaults, { // eslint-disable-next-line no-unused-vars onExportSaved (exportedRows) { return false }, onExportStarted () { return false } }) Object.assign($.fn.bootstrapTable.events, { 'export-saved.bs.table': 'onExportSaved', 'export-started.bs.table': 'onExportStarted' }) $.BootstrapTable = class extends $.BootstrapTable { initToolbar (...args) { const o = this.options let exportTypes = o.exportTypes this.showToolbar = this.showToolbar || o.showExport if (this.options.showExport) { if (typeof exportTypes === 'string') { const types = exportTypes.slice(1, -1).replace(/ /g, '').split(',') exportTypes = types.map(t => t.slice(1, -1)) } if (typeof o.exportOptions === 'string') { o.exportOptions = Utils.calculateObjectValue(null, o.exportOptions) } this.$export = this.$toolbar.find('>.columns div.export') if (this.$export.length) { this.updateExportButton() return } this.buttons = Object.assign(this.buttons, { export: { html: () => { if (exportTypes.length === 1) { return `
    ` } const html = [] html.push(`
    ${this.constants.html.toolbarDropdown[0]} `) for (const type of exportTypes) { if (TYPE_NAME.hasOwnProperty(type)) { const $item = $(Utils.sprintf(this.constants.html.pageDropdownItem, '', TYPE_NAME[type])) $item.attr('data-type', type) html.push($item.prop('outerHTML')) } } html.push(this.constants.html.toolbarDropdown[1], '
    ') return html.join('') } } }) } super.initToolbar(...args) this.$export = this.$toolbar.find('>.columns div.export') if (!this.options.showExport) { return } this.updateExportButton() let $exportButtons = this.$export.find('[data-type]') if (exportTypes.length === 1) { $exportButtons = this.$export } $exportButtons.click(e => { e.preventDefault() this.trigger('export-started') this.exportTable({ type: $(e.currentTarget).data('type') }) }) this.handleToolbar() } handleToolbar () { if (!this.$export) { return } if (super.handleToolbar) { super.handleToolbar() } } exportTable (options) { const o = this.options const stateField = this.header.stateField const isCardView = o.cardView const doExport = callback => { if (stateField) { this.hideColumn(stateField) } if (isCardView) { this.toggleView() } this.columns.forEach(row => { if (row.forceHide) { this.hideColumn(row.field) } }) const data = this.getData() if (o.detailView && o.detailViewIcon) { const detailViewIndex = o.detailViewAlign === 'left' ? 0 : this.getVisibleFields().length + Utils.getDetailViewIndexOffset(this.options) o.exportOptions.ignoreColumn = [detailViewIndex].concat(o.exportOptions.ignoreColumn || []) } if (o.exportFooter && o.height) { const $footerRow = this.$tableFooter.find('tr').first() const footerData = {} const footerHtml = [] $footerRow.children().forEach((footerCell, index) => { const footerCellHtml = $(footerCell).children('.th-inner').first().html() footerData[this.columns[index].field] = footerCellHtml === ' ' ? null : footerCellHtml // grab footer cell text into cell index-based array footerHtml.push(footerCellHtml) }) this.$body.append(this.$body.children().last()[0].outerHTML) const $lastTableRow = this.$body.children().last() $lastTableRow.children().forEach((lastTableRowCell, index) => { $(lastTableRowCell).html(footerHtml[index]) }) } const hiddenColumns = this.getHiddenColumns() hiddenColumns.forEach(row => { if (row.forceExport) { this.showColumn(row.field) } }) if (typeof o.exportOptions.fileName === 'function') { options.fileName = o.exportOptions.fileName() } this.$el.tableExport(Utils.extend({ onAfterSaveToFile: () => { if (o.exportFooter) { this.load(data) } if (stateField) { this.showColumn(stateField) } if (isCardView) { this.toggleView() } hiddenColumns.forEach(row => { if (row.forceExport) { this.hideColumn(row.field) } }) this.columns.forEach(row => { if (row.forceHide) { this.showColumn(row.field) } }) if (callback) callback() } }, o.exportOptions, options)) } if (o.exportDataType === 'all' && o.pagination) { const eventName = o.sidePagination === 'server' ? 'post-body.bs.table' : 'page-change.bs.table' const virtualScroll = this.options.virtualScroll this.$el.one(eventName, () => { setTimeout(() => { const data = this.getData() doExport(() => { this.options.virtualScroll = virtualScroll this.togglePagination() }) this.trigger('export-saved', data) }, 0) }) this.options.virtualScroll = false this.togglePagination() } else if (o.exportDataType === 'selected') { let data = this.getData() let selectedData = this.getSelections() const pagination = o.pagination if (!selectedData.length) { return } if (o.sidePagination === 'server') { data = { total: o.totalRows, [this.options.dataField]: data } selectedData = { total: selectedData.length, [this.options.dataField]: selectedData } } this.load(selectedData) if (pagination) { this.togglePagination() } doExport(() => { if (pagination) { this.togglePagination() } this.load(data) }) this.trigger('export-saved', selectedData) } else { doExport() this.trigger('export-saved', this.getData(true)) } } updateSelected () { super.updateSelected() this.updateExportButton() } updateExportButton () { if (this.options.exportDataType === 'selected') { this.$export.find('> button') .prop('disabled', !this.getSelections().length) } } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/mobile/bootstrap-table-mobile.js ================================================ /** * @author: Dennis Hernández * @update zhixin wen */ const debounce = (func, wait) => { let timeout = 0 return (...args) => { const later = () => { timeout = 0 func(...args) } clearTimeout(timeout) timeout = setTimeout(later, wait) } } Object.assign($.fn.bootstrapTable.defaults, { mobileResponsive: false, minWidth: 562, minHeight: undefined, heightThreshold: 100, // just slightly larger than mobile chrome's auto-hiding toolbar checkOnInit: true, columnsHidden: [] }) $.BootstrapTable = class extends $.BootstrapTable { init (...args) { super.init(...args) if (!this.options.mobileResponsive || !this.options.minWidth) { return } if (this.options.minWidth < 100 && this.options.resizable) { console.warn('The minWidth when the resizable extension is active should be greater or equal than 100') this.options.minWidth = 100 } let old = { width: $(window).width(), height: $(window).height() } $(window).on('resize orientationchange', debounce(() => { // reset view if height has only changed by at least the threshold. const width = $(window).width() const height = $(window).height() const $activeElement = $(document.activeElement) if ($activeElement.length && ['INPUT', 'SELECT', 'TEXTAREA'].includes($activeElement.prop('nodeName'))) { return } if ( Math.abs(old.height - height) > this.options.heightThreshold || old.width !== width ) { this.changeView(width, height) old = { width, height } } }, 200)) if (this.options.checkOnInit) { const width = $(window).width() const height = $(window).height() this.changeView(width, height) old = { width, height } } } conditionCardView () { this.changeTableView(false) this.showHideColumns(false) } conditionFullView () { this.changeTableView(true) this.showHideColumns(true) } changeTableView (cardViewState) { this.options.cardView = cardViewState this.toggleView() } showHideColumns (checked) { if (this.options.columnsHidden.length > 0) { this.columns.forEach(column => { if (this.options.columnsHidden.includes(column.field)) { if (column.visible !== checked) { this._toggleColumn(this.fieldsColumnsIndex[column.field], checked, true) } } }) } } changeView (width, height) { if (this.options.minHeight) { if (width <= this.options.minWidth && height <= this.options.minHeight) { this.conditionCardView() } else if (width > this.options.minWidth && height > this.options.minHeight) { this.conditionFullView() } } else if (width <= this.options.minWidth) { this.conditionCardView() } else if (width > this.options.minWidth) { this.conditionFullView() } this.resetView() } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/print/bootstrap-table-print.js ================================================ /** * @update zhixin wen */ var Utils = $.fn.bootstrapTable.utils function printPageBuilderDefault (table, styles) { return ` ${styles} Print Table

    Printed on: ${new Date}

    ${table}
    ` } Object.assign($.fn.bootstrapTable.locales, { formatPrint () { return 'Print' } }) Object.assign($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales) Object.assign($.fn.bootstrapTable.defaults, { showPrint: false, printAsFilteredAndSortedOnUI: true, printSortColumn: undefined, printSortOrder: 'asc', printStyles: [], printPageBuilder (table, styles) { return printPageBuilderDefault(table, styles) } }) Object.assign($.fn.bootstrapTable.columnDefaults, { printFilter: undefined, printIgnore: false, printFormatter: undefined }) Utils.assignIcons($.fn.bootstrapTable.icons, 'print', { glyphicon: 'glyphicon-print icon-share', fa: 'fa-print', bi: 'bi-printer', icon: 'icon-printer', 'material-icons': 'print' }) $.BootstrapTable = class extends $.BootstrapTable { init (...args) { super.init(...args) if (!this.options.showPrint) { return } this.mergedCells = [] } initToolbar (...args) { this.showToolbar = this.showToolbar || this.options.showPrint if (this.options.showPrint) { this.buttons = Object.assign(this.buttons, { print: { text: this.options.formatPrint(), icon: this.options.icons.print, event: () => { this.doPrint(this.options.printAsFilteredAndSortedOnUI ? this.getData() : this.options.data.slice(0)) }, attributes: { 'aria-label': this.options.formatPrint(), title: this.options.formatPrint() } } }) } super.initToolbar(...args) } mergeCells (options) { super.mergeCells(options) if (!this.options.showPrint) { return } let col = this.getVisibleFields().indexOf(options.field) if (Utils.hasDetailViewIcon(this.options)) { col += 1 } this.mergedCells.push({ row: options.index, col, rowspan: +options.rowspan || 1, colspan: +options.colspan || 1 }) } doPrint (data) { const canPrint = column => !column.printIgnore && column.visible const formatValue = (row, i, column) => { const value_ = Utils.getItemField(row, column.field, this.options.escape, column.escape) const value = Utils.calculateObjectValue(column, column.printFormatter || column.formatter, [value_, row, i], value_) return typeof value === 'undefined' || value === null ? this.options.undefinedText : $('
    ').html(value).html() } const buildTable = (data, columnsArray) => { const dir = this.$el.attr('dir') || 'ltr' const html = [``] for (const columns of columnsArray) { html.push('') for (let h = 0; h < columns.length; h++) { if (canPrint(columns[h])) { html.push( ``) } } html.push('') } html.push('') const notRender = [] if (this.mergedCells) { for (let mc = 0; mc < this.mergedCells.length; mc++) { const currentMergedCell = this.mergedCells[mc] for (let rs = 0; rs < currentMergedCell.rowspan; rs++) { const row = currentMergedCell.row + rs for (let cs = 0; cs < currentMergedCell.colspan; cs++) { const col = currentMergedCell.col + cs notRender.push(`${row},${col}`) } } } } for (let i = 0; i < data.length; i++) { html.push('') const columns = columnsArray.flat(1) columns.sort((c1, c2) => c1.colspanIndex - c2.colspanIndex) for (let j = 0; j < columns.length; j++) { if (columns[j].colspanGroup > 0) continue let rowspan = 0 let colspan = 0 if (this.mergedCells) { for (let mc = 0; mc < this.mergedCells.length; mc++) { const currentMergedCell = this.mergedCells[mc] if (currentMergedCell.col === j && currentMergedCell.row === i) { rowspan = currentMergedCell.rowspan colspan = currentMergedCell.colspan } } } if ( canPrint(columns[j]) && ( !notRender.includes(`${i},${j}`) || rowspan > 0 && colspan > 0 ) ) { if (rowspan > 0 && colspan > 0) { html.push(`') } else { html.push('') } } } html.push('') } html.push('') if (this.options.showFooter) { html.push('
    ') for (const columns of columnsArray) { for (let h = 0; h < columns.length; h++) { if (canPrint(columns)) { const footerData = Utils.trToData(columns, this.$el.find('>tfoot>tr')) const footerValue = Utils.calculateObjectValue(columns[h], columns[h].footerFormatter, [data], footerData[0] && footerData[0][columns[h].field] || '') html.push(``) } } } html.push('') } html.push('
    ${columns[h].title}
    `, formatValue(data[i], i, columns[j]), '', formatValue(data[i], i, columns[j]), '
    ${footerValue}
    ') return html.join('') } const sortRows = (data, colName, sortOrder) => { if (!colName) { return data } let reverse = sortOrder !== 'asc' reverse = -(+reverse || -1) return data.sort((a, b) => reverse * a[colName].localeCompare(b[colName])) } const filterRow = (row, filters) => { for (let index = 0; index < filters.length; ++index) { if (row[filters[index].colName] !== filters[index].value) { return false } } return true } const filterRows = (data, filters) => data.filter(row => filterRow(row, filters)) const getColumnFilters = columns => !columns || !columns[0] ? [] : columns[0].filter(col => col.printFilter).map(col => ({ colName: col.field, value: col.printFilter })) data = filterRows(data, getColumnFilters(this.options.columns)) data = sortRows(data, this.options.printSortColumn, this.options.printSortOrder) const table = buildTable(data, this.options.columns) const newWin = window.open('') const printStyles = typeof this.options.printStyles === 'string' ? this.options.printStyles.replace(/\[|\]| /g, '').toLowerCase().split(',') : this.options.printStyles const styles = printStyles.map(it => ``).join('') const calculatedPrintPage = Utils.calculateObjectValue(this, this.options.printPageBuilder, [table, styles], printPageBuilderDefault(table, styles)) const startPrint = () => { newWin.focus() newWin.print() newWin.close() } newWin.document.write(calculatedPrintPage) newWin.document.close() if (printStyles.length) { const links = document.getElementsByTagName('link') const lastLink = links[links.length - 1] lastLink.onload = startPrint } else { startPrint() } } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-columns/bootstrap-table-reorder-columns.js ================================================ /** * @author: Dennis Hernández * @update: https://github.com/wenzhixin * @version: v1.2.0 */ $.akottr.dragtable.prototype._restoreState = function (persistObj) { let i = 0 for (const [field, value] of Object.entries(persistObj)) { const $th = this.originalTable.el.find(`th[data-field="${field}"]`) if (!$th.length) { i++ continue } this.originalTable.startIndex = $th.prevAll().length + 1 this.originalTable.endIndex = parseInt(value, 10) + 1 - i this._bubbleCols() } } // From MDN site, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter const filterFn = () => { if (!Array.prototype.filter) { Array.prototype.filter = function (fun/* , thisArg*/) { if (this === undefined || this === null) { throw new TypeError() } const t = Object(this) const len = t.length >>> 0 if (typeof fun !== 'function') { throw new TypeError() } const res = [] const thisArg = arguments.length >= 2 ? arguments[1] : undefined for (let i = 0; i < len; i++) { if (i in t) { const val = t[i] // NOTE: Technically this should Object.defineProperty at // the next index, as push can be affected by // properties on Object.prototype and Array.prototype. // But this method's new, and collisions should be // rare, so use the more-compatible alternative. if (fun.call(thisArg, val, i, t)) { res.push(val) } } } return res } } } Object.assign($.fn.bootstrapTable.defaults, { reorderableColumns: false, maxMovingRows: 10, // eslint-disable-next-line no-unused-vars onReorderColumn (headerFields) { return false }, dragaccept: null }) Object.assign($.fn.bootstrapTable.events, { 'reorder-column.bs.table': 'onReorderColumn' }) $.fn.bootstrapTable.methods.push('orderColumns') $.BootstrapTable = class extends $.BootstrapTable { initHeader (...args) { super.initHeader(...args) if (!this.options.reorderableColumns) { return } this.makeColumnsReorderable() } _toggleColumn (...args) { super._toggleColumn(...args) if (!this.options.reorderableColumns) { return } this.makeColumnsReorderable() } toggleView (...args) { super.toggleView(...args) if (!this.options.reorderableColumns) { return } if (this.options.cardView) { return } this.makeColumnsReorderable() } resetView (...args) { super.resetView(...args) if (!this.options.reorderableColumns) { return } this.makeColumnsReorderable() } makeColumnsReorderable (order = null) { try { $(this.$el).dragtable('destroy') } catch (e) { console.error(e) } $(this.$el).dragtable({ maxMovingRows: this.options.maxMovingRows, dragaccept: this.options.dragaccept, clickDelay: 200, dragHandle: '.th-inner', restoreState: order ? order : this.columnsSortOrder, beforeStop: table => { const sortOrder = {} table.el.find('th').each((i, el) => { if (el.dataset.field !== undefined) { sortOrder[el.dataset.field] = i } }) this.columnsSortOrder = sortOrder if (this.options.cookie) { this.persistReorderColumnsState(this) } const ths = [] const formatters = [] const columns = [] let columnsHidden = [] let columnIndex = -1 const optionsColumns = [] this.$header.find('th:not(.detail)').each((i, el) => { ths.push($(el).data('field')) formatters.push($(el).data('formatter')) }) // Exist columns not shown if (ths.length < this.columns.length) { columnsHidden = this.columns.filter(column => !column.visible) for (let i = 0; i < columnsHidden.length; i++) { ths.push(columnsHidden[i].field) formatters.push(columnsHidden[i].formatter) } } for (let i = 0; i < ths.length; i++) { columnIndex = this.fieldsColumnsIndex[ths[i]] if (columnIndex !== -1) { this.fieldsColumnsIndex[ths[i]] = i this.columns[columnIndex].fieldIndex = i columns.push(this.columns[columnIndex]) } } this.columns = columns filterFn() // Support { if (!found && item['field'] === field) { optionsColumns.push(item) found = true return false } return true }) } this.options.columns[0] = optionsColumns this.header.fields = ths this.header.formatters = formatters this.initHeader() this.initToolbar() this.initSearchText() this.initBody() this.resetView() this.trigger('reorder-column', ths) } }) } orderColumns (order) { this.columnsSortOrder = order this.makeColumnsReorderable() } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-columns/jquery.dragtable.js ================================================ /** * Minified by jsDelivr using Terser v5.3.5. * Original file: /gh/akottr/dragtable@master/jquery.dragtable.js * * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files */ /*! * dragtable * * @Version 2.0.15 * * Copyright (c) 2010-2013, Andres akottr@gmail.com * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. * * Inspired by the the dragtable from Dan Vanderkam (danvk.org/dragtable/) * Thanks to the jquery and jqueryui comitters * * Any comment, bug report, feature-request is welcome * Feel free to contact me. */ !function(e){e.widget("akottr.dragtable",{options:{revert:!1,dragHandle:".table-handle",maxMovingRows:40,excludeFooter:!1,onlyHeaderThreshold:100,dragaccept:null,persistState:null,restoreState:null,exact:!0,clickDelay:10,containment:null,cursor:"move",cursorAt:!1,distance:0,tolerance:"pointer",axis:"x",beforeStart:e.noop,beforeMoving:e.noop,beforeReorganize:e.noop,beforeStop:e.noop},originalTable:{el:null,selectedHandle:null,sortOrder:null,startIndex:0,endIndex:0},sortableTable:{el:e(),selectedHandle:e(),movingRow:e()},persistState:function(){var t=this;this.originalTable.el.find("th").each((function(e){""!==this.id&&(t.originalTable.sortOrder[this.id]=e)})),e.ajax({url:this.options.persistState,data:this.originalTable.sortOrder})},_restoreState:function(t){for(var i in t)this.originalTable.startIndex=e("#"+i).closest("th").prevAll().length+1,this.originalTable.endIndex=parseInt(t[i],10)+1,this._bubbleCols()},_bubbleCols:function(){var e,t,i,o,a=this.originalTable.startIndex,l=this.originalTable.endIndex,s=this.originalTable.el.children();if(this.options.excludeFooter&&(s=s.not("tfoot")),a tr > td:nth-child("+e+")").add(s.find("> tr > th:nth-child("+e+")")),o=s.find("> tr > td:nth-child("+(e+1)+")").add(s.find("> tr > th:nth-child("+(e+1)+")")),t=0;tl;e--)for(i=s.find("> tr > td:nth-child("+e+")").add(s.find("> tr > th:nth-child("+e+")")),o=s.find("> tr > td:nth-child("+(e-1)+")").add(s.find("> tr > th:nth-child("+(e-1)+")")),t=0;t tr > th").each((function(t,i){var n=e(this).is(":visible")?e(this).outerWidth():0;r.push(n),d+=n})),i.options.exact){var c=d-i.originalTable.el.outerWidth();r[0]-=c}var b='
      ';h.find("> tr > th").each((function(t,n){var a=e(this).is(":visible")?e(this).outerWidth():0;b+='
    • ',b+="";var r=h.find("> tr > th:nth-child("+(t+1)+")");i.options.maxMovingRows>1&&(r=r.add(h.find("> tr > td:nth-child("+(t+1)+")").slice(0,i.options.maxMovingRows-1))),r.each((function(t){var i=e(this).clone().wrap("
      ").parent().html();0===i.toLowerCase().indexOf(""),b+="',b+=i,0===i.toLowerCase().indexOf(""),b+=""})),b+="
      ",b+="
    • "})),b+="
    ",this.sortableTable.el=this.originalTable.el.before(b).prev(),this.sortableTable.el.find("> li > table").each((function(t,i){e(this).css("width",r[t]+"px")})),this.sortableTable.selectedHandle=this.sortableTable.el.find("th .dragtable-handle-selected");var u,g=this.options.dragaccept?"li:has("+this.options.dragaccept+")":"li";this.sortableTable.el.sortable({items:g,stop:this._rearrangeTable(),revert:this.options.revert,tolerance:this.options.tolerance,containment:this.options.containment,cursor:this.options.cursor,cursorAt:this.options.cursorAt,distance:this.options.distance,axis:this.options.axis}),this.originalTable.startIndex=e(t.target).closest("th").prevAll().length+1,this.options.beforeMoving(this.originalTable,this.sortableTable),this.sortableTable.movingRow=this.sortableTable.el.find("> li:nth-child("+this.originalTable.startIndex+")"),u=e(''),e(document.head).append(u),e(document.body).attr("onselectstart","return false;").attr("unselectable","on"),window.getSelection?window.getSelection().removeAllRanges():document.selection.empty(),this.sortableTable.movingRow.trigger(e.extend(e.Event(t.type),{which:1,clientX:t.clientX,clientY:t.clientY,pageX:t.pageX,pageY:t.pageY,screenX:t.screenX,screenY:t.screenY}));var p=this.sortableTable.el.find(".ui-sortable-placeholder");!p.height()<=0&&p.css("height",this.sortableTable.el.find(".ui-sortable-helper").height()),p.html('
    ')},bindTo:{},_create:function(){this.originalTable={el:this.element,selectedHandle:e(),sortOrder:{},startIndex:0,endIndex:0},this.bindTo=this.originalTable.el.find("th"),this.options.dragaccept&&(this.bindTo=this.bindTo.filter(this.options.dragaccept)),this.bindTo.find(this.options.dragHandle).length>0&&(this.bindTo=this.bindTo.find(this.options.dragHandle)),null!==this.options.restoreState&&(e.isFunction(this.options.restoreState)?this.options.restoreState(this.originalTable):this._restoreState(this.options.restoreState));var t=this;this.bindTo.mousedown((function(i){1===i.which&&!1!==t.options.beforeStart(t.originalTable)&&(clearTimeout(this.downTimer),this.downTimer=setTimeout((function(){t.originalTable.selectedHandle=e(this),t.originalTable.selectedHandle.addClass("dragtable-handle-selected"),t._generateSortable(i)}),t.options.clickDelay))})).mouseup((function(e){clearTimeout(this.downTimer)}))},redraw:function(){this.destroy(),this._create()},destroy:function(){this.bindTo.unbind("mousedown"),e.Widget.prototype.destroy.apply(this,arguments)}});var t=e(document.body).attr("onselectstart"),i=e(document.body).attr("unselectable");function n(e,t){var i=e.parentNode,n=e.nextSibling===t?e:e.nextSibling;t.parentNode.insertBefore(e,t),i.insertBefore(t,n)}}(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-rows/bootstrap-table-reorder-rows.js ================================================ /** * @author: Dennis Hernández * @update zhixin wen */ const rowAttr = (row, index) => ({ id: `customId_${index}` }) Object.assign($.fn.bootstrapTable.defaults, { reorderableRows: false, onDragStyle: null, onDropStyle: null, onDragClass: 'reorder-rows-on-drag-class', dragHandle: '>tbody>tr>td:not(.bs-checkbox)', useRowAttrFunc: false, // eslint-disable-next-line no-unused-vars onReorderRowsDrag (row) { return false }, // eslint-disable-next-line no-unused-vars onReorderRowsDrop (row) { return false }, // eslint-disable-next-line no-unused-vars onReorderRow (newData) { return false }, onDragStop () {}, onAllowDrop () { return true } }) Object.assign($.fn.bootstrapTable.events, { 'reorder-row.bs.table': 'onReorderRow' }) $.BootstrapTable = class extends $.BootstrapTable { init (...args) { if (!this.options.reorderableRows) { super.init(...args) return } if (this.options.useRowAttrFunc) { this.options.rowAttributes = rowAttr } const onPostBody = this.options.onPostBody this.options.onPostBody = () => { setTimeout(() => { this.makeRowsReorderable() onPostBody.call(this.options, this.options.data) }, 1) } super.init(...args) } makeRowsReorderable () { this.$el.tableDnD({ onDragStyle: this.options.onDragStyle, onDropStyle: this.options.onDropStyle, onDragClass: this.options.onDragClass, onAllowDrop: (hoveredRow, draggedRow) => this.onAllowDrop(hoveredRow, draggedRow), onDragStop: (table, draggedRow) => this.onDragStop(table, draggedRow), onDragStart: (table, droppedRow) => this.onDropStart(table, droppedRow), onDrop: (table, droppedRow) => this.onDrop(table, droppedRow), dragHandle: this.options.dragHandle }) } onDropStart (table, draggingTd) { this.$draggingTd = $(draggingTd).css('cursor', 'move') this.draggingIndex = $(this.$draggingTd.parent()).data('index') // Call the user defined function this.options.onReorderRowsDrag(this.data[this.draggingIndex]) } onDragStop (table, draggedRow) { const rowIndexDraggedRow = $(draggedRow).data('index') const draggedRowItem = this.data[rowIndexDraggedRow] this.options.onDragStop(table, draggedRowItem, draggedRow) } onAllowDrop (hoveredRow, draggedRow) { const rowIndexDraggedRow = $(draggedRow).data('index') const rowIndexHoveredRow = $(hoveredRow).data('index') const draggedRowItem = this.data[rowIndexDraggedRow] const hoveredRowItem = this.data[rowIndexHoveredRow] return this.options.onAllowDrop(hoveredRowItem, draggedRowItem, hoveredRow, draggedRow) } onDrop (table) { this.$draggingTd.css('cursor', '') const pageNum = this.options.pageNumber const pageSize = this.options.pageSize const newData = [] for (let i = 0; i < table.tBodies[0].rows.length; i++) { const $tr = $(table.tBodies[0].rows[i]) newData.push(this.data[$tr.data('index')]) $tr.data('index', i) } const draggingRow = this.data[this.draggingIndex] const droppedIndex = newData.indexOf(this.data[this.draggingIndex]) const droppedRow = this.data[droppedIndex] const index = (pageNum - 1) * pageSize + this.options.data.indexOf(this.data[droppedIndex]) this.options.data.splice(this.options.data.indexOf(draggingRow), 1) this.options.data.splice(index, 0, draggingRow) this.initSearch() if (this.options.sidePagination === 'server') { this.data = [...this.options.data] } // Call the user defined function this.options.onReorderRowsDrop(droppedRow) // Call the event reorder-row this.trigger('reorder-row', newData, draggingRow, droppedRow) } initSearch () { this.ignoreInitSort = true super.initSearch() } initSort () { if (this.ignoreInitSort) { this.ignoreInitSort = false return } super.initSort() } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/reorder-rows/jquery.tablednd.js ================================================ /** * TableDnD plug-in for JQuery, allows you to drag and drop table rows * You can set up various options to control how the system will work * Copyright (c) Denis Howlett * License: MIT. * See https://github.com/isocra/TableDnD */ !function ($, window, document, undefined) { var startEvent = 'touchstart mousedown', moveEvent = 'touchmove mousemove', endEvent = 'touchend mouseup'; $(document).ready(function () { function parseStyle(css) { var objMap = {}, parts = css.match(/([^;:]+)/g) || []; while (parts.length) objMap[parts.shift()] = parts.shift().trim(); return objMap; } $('table').each(function () { if ($(this).data('table') === 'dnd') { $(this).tableDnD({ onDragStyle: $(this).data('ondragstyle') && parseStyle($(this).data('ondragstyle')) || null, onDropStyle: $(this).data('ondropstyle') && parseStyle($(this).data('ondropstyle')) || null, onDragClass: $(this).data('ondragclass') === undefined && "tDnD_whileDrag" || $(this).data('ondragclass'), onDrop: $(this).data('ondrop') && new Function('table', 'row', $(this).data('ondrop')), // 'return eval("'+$(this).data('ondrop')+'");') || null, onDragStart: $(this).data('ondragstart') && new Function('table', 'row' ,$(this).data('ondragstart')), // 'return eval("'+$(this).data('ondragstart')+'");') || null, onDragStop: $(this).data('ondragstop') && new Function('table', 'row' ,$(this).data('ondragstop')), scrollAmount: $(this).data('scrollamount') || 5, sensitivity: $(this).data('sensitivity') || 10, hierarchyLevel: $(this).data('hierarchylevel') || 0, indentArtifact: $(this).data('indentartifact') || '
     
    ', autoWidthAdjust: $(this).data('autowidthadjust') || true, autoCleanRelations: $(this).data('autocleanrelations') || true, jsonPretifySeparator: $(this).data('jsonpretifyseparator') || '\t', serializeRegexp: $(this).data('serializeregexp') && new RegExp($(this).data('serializeregexp')) || /[^\-]*$/, serializeParamName: $(this).data('serializeparamname') || false, dragHandle: $(this).data('draghandle') || null }); } }); }); jQuery.tableDnD = { /** Keep hold of the current table being dragged */ currentTable: null, /** Keep hold of the current drag object if any */ dragObject: null, /** The current mouse offset */ mouseOffset: null, /** Remember the old value of X and Y so that we don't do too much processing */ oldX: 0, oldY: 0, /** Actually build the structure */ build: function(options) { // Set up the defaults if any this.each(function() { // This is bound to each matching table, set up the defaults and override with user options this.tableDnDConfig = $.extend({ onDragStyle: null, onDropStyle: null, // Add in the default class for whileDragging onDragClass: "tDnD_whileDrag", onDrop: null, onDragStart: null, onDragStop: null, scrollAmount: 5, /** Sensitivity setting will throttle the trigger rate for movement detection */ sensitivity: 10, /** Hierarchy level to support parent child. 0 switches this functionality off */ hierarchyLevel: 0, /** The html artifact to prepend the first cell with as indentation */ indentArtifact: '
     
    ', /** Automatically adjust width of first cell */ autoWidthAdjust: true, /** Automatic clean-up to ensure relationship integrity */ autoCleanRelations: true, /** Specify a number (4) as number of spaces or any indent string for JSON.stringify */ jsonPretifySeparator: '\t', /** The regular expression to use to trim row IDs */ serializeRegexp: /[^\-]*$/, /** If you want to specify another parameter name instead of the table ID */ serializeParamName: false, /** If you give the name of a class here, then only Cells with this class will be draggable */ dragHandle: null }, options || {}); // Now make the rows draggable $.tableDnD.makeDraggable(this); // Prepare hierarchy support this.tableDnDConfig.hierarchyLevel && $.tableDnD.makeIndented(this); }); // Don't break the chain return this; }, makeIndented: function (table) { var config = table.tableDnDConfig, rows = table.rows, firstCell = $(rows).first().find('td:first')[0], indentLevel = 0, cellWidth = 0, longestCell, tableStyle; if ($(table).hasClass('indtd')) return null; tableStyle = $(table).addClass('indtd').attr('style'); $(table).css({whiteSpace: "nowrap"}); for (var w = 0; w < rows.length; w++) { if (cellWidth < $(rows[w]).find('td:first').text().length) { cellWidth = $(rows[w]).find('td:first').text().length; longestCell = w; } } $(firstCell).css({width: 'auto'}); for (w = 0; w < config.hierarchyLevel; w++) $(rows[longestCell]).find('td:first').prepend(config.indentArtifact); firstCell && $(firstCell).css({width: firstCell.offsetWidth}); tableStyle && $(table).css(tableStyle); for (w = 0; w < config.hierarchyLevel; w++) $(rows[longestCell]).find('td:first').children(':first').remove(); config.hierarchyLevel && $(rows).each(function () { indentLevel = $(this).data('level') || 0; indentLevel <= config.hierarchyLevel && $(this).data('level', indentLevel) || $(this).data('level', 0); for (var i = 0; i < $(this).data('level'); i++) $(this).find('td:first').prepend(config.indentArtifact); }); return this; }, /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */ makeDraggable: function(table) { var config = table.tableDnDConfig; config.dragHandle // We only need to add the event to the specified cells && $(config.dragHandle, table).each(function() { if (! $(this).hasClass("nodrag")) { // The cell is bound to "this" $(this).bind(startEvent, function(e) { if (e.target.tagName === "TD" && event.target.className !== "nodrag") { $.tableDnD.initialiseDrag($(this).parents('tr')[0], table, this, e, config); return false; } return true; }); } }) // For backwards compatibility, we add the event to the whole row // get all the rows as a wrapped set || $(table.rows).each(function() { // Iterate through each row, the row is bound to "this" if (! $(this).hasClass("nodrag")) { $(this).bind(startEvent, function(e) { if (e.target.tagName === "TD" && event.target.className !== "nodrag") { $.tableDnD.initialiseDrag(this, table, this, e, config); return false; } }).css("cursor", "move"); // Store the tableDnD object } else { $(this).css("cursor", ""); // Remove the cursor if we don't have the nodrag class } }); }, currentOrder: function() { var rows = this.currentTable.rows; return $.map(rows, function (val) { return ($(val).data('level') + val.id).replace(/\s/g, ''); }).join(''); }, initialiseDrag: function(dragObject, table, target, e, config) { this.dragObject = dragObject; this.currentTable = table; this.mouseOffset = this.getMouseOffset(target, e); this.originalOrder = this.currentOrder(); // Now we need to capture the mouse up and mouse move event // We can use bind so that we don't interfere with other event handlers $(document) .bind(moveEvent, this.mousemove) .bind(endEvent, this.mouseup); // Call the onDragStart method if there is one config.onDragStart && config.onDragStart(table, target); }, updateTables: function() { this.each(function() { // this is now bound to each matching table if (this.tableDnDConfig) $.tableDnD.makeDraggable(this); }); }, /** Get the mouse coordinates from the event (allowing for browser differences) */ mouseCoords: function(e) { if (e.originalEvent.changedTouches) return { x: e.originalEvent.changedTouches[0].clientX, y: e.originalEvent.changedTouches[0].clientY }; if(e.pageX || e.pageY) return { x: e.pageX, y: e.pageY }; return { x: e.clientX + document.body.scrollLeft - document.body.clientLeft, y: e.clientY + document.body.scrollTop - document.body.clientTop }; }, /** Given a target element and a mouse eent, get the mouse offset from that element. To do this we need the element's position and the mouse position */ getMouseOffset: function(target, e) { var mousePos, docPos; e = e || window.event; docPos = this.getPosition(target); mousePos = this.mouseCoords(e); return { x: mousePos.x - docPos.x, y: mousePos.y - docPos.y }; }, /** Get the position of an element by going up the DOM tree and adding up all the offsets */ getPosition: function(element) { var left = 0, top = 0; // Safari fix -- thanks to Luis Chato for this! // Safari 2 doesn't correctly grab the offsetTop of a table row // this is detailed here: // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/ // the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild. // note that firefox will return a text node as a first child, so designing a more thorough // solution may need to take that into account, for now this seems to work in firefox, safari, ie if (element.offsetHeight === 0) element = element.firstChild; // a table cell while (element.offsetParent) { left += element.offsetLeft; top += element.offsetTop; element = element.offsetParent; } left += element.offsetLeft; top += element.offsetTop; return { x: left, y: top }; }, autoScroll: function (mousePos) { var config = this.currentTable.tableDnDConfig, yOffset = window.pageYOffset, windowHeight = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight; // Windows version // yOffset=document.body.scrollTop; if (document.all) if (typeof document.compatMode !== 'undefined' && document.compatMode !== 'BackCompat') yOffset = document.documentElement.scrollTop; else if (typeof document.body !== 'undefined') yOffset = document.body.scrollTop; mousePos.y - yOffset < config.scrollAmount && window.scrollBy(0, - config.scrollAmount) || windowHeight - (mousePos.y - yOffset) < config.scrollAmount && window.scrollBy(0, config.scrollAmount); }, moveVerticle: function (moving, currentRow) { if (0 !== moving.vertical // If we're over a row then move the dragged row to there so that the user sees the // effect dynamically && currentRow && this.dragObject !== currentRow && this.dragObject.parentNode === currentRow.parentNode) 0 > moving.vertical && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow.nextSibling) || 0 < moving.vertical && this.dragObject.parentNode.insertBefore(this.dragObject, currentRow); }, moveHorizontal: function (moving, currentRow) { var config = this.currentTable.tableDnDConfig, currentLevel; if (!config.hierarchyLevel || 0 === moving.horizontal // We only care if moving left or right on the current row || !currentRow || this.dragObject !== currentRow) return null; currentLevel = $(currentRow).data('level'); 0 < moving.horizontal && currentLevel > 0 && $(currentRow).find('td:first').children(':first').remove() && $(currentRow).data('level', --currentLevel); 0 > moving.horizontal && currentLevel < config.hierarchyLevel && $(currentRow).prev().data('level') >= currentLevel && $(currentRow).children(':first').prepend(config.indentArtifact) && $(currentRow).data('level', ++currentLevel); }, mousemove: function(e) { var dragObj = $($.tableDnD.dragObject), config = $.tableDnD.currentTable.tableDnDConfig, currentRow, mousePos, moving, x, y; e && e.preventDefault(); if (!$.tableDnD.dragObject) return false; // prevent touch device screen scrolling e.type === 'touchmove' && event.preventDefault(); // TODO verify this is event and not really e // update the style to show we're dragging config.onDragClass && dragObj.addClass(config.onDragClass) || dragObj.css(config.onDragStyle); mousePos = $.tableDnD.mouseCoords(e); x = mousePos.x - $.tableDnD.mouseOffset.x; y = mousePos.y - $.tableDnD.mouseOffset.y; // auto scroll the window $.tableDnD.autoScroll(mousePos); currentRow = $.tableDnD.findDropTargetRow(dragObj, y); moving = $.tableDnD.findDragDirection(x, y); $.tableDnD.moveVerticle(moving, currentRow); $.tableDnD.moveHorizontal(moving, currentRow); return false; }, findDragDirection: function (x,y) { var sensitivity = this.currentTable.tableDnDConfig.sensitivity, oldX = this.oldX, oldY = this.oldY, xMin = oldX - sensitivity, xMax = oldX + sensitivity, yMin = oldY - sensitivity, yMax = oldY + sensitivity, moving = { horizontal: x >= xMin && x <= xMax ? 0 : x > oldX ? -1 : 1, vertical : y >= yMin && y <= yMax ? 0 : y > oldY ? -1 : 1 }; // update the old value if (moving.horizontal !== 0) this.oldX = x; if (moving.vertical !== 0) this.oldY = y; return moving; }, /** We're only worried about the y position really, because we can only move rows up and down */ findDropTargetRow: function(draggedRow, y) { var rowHeight = 0, rows = this.currentTable.rows, config = this.currentTable.tableDnDConfig, rowY = 0, row = null; for (var i = 0; i < rows.length; i++) { row = rows[i]; rowY = this.getPosition(row).y; rowHeight = parseInt(row.offsetHeight) / 2; if (row.offsetHeight === 0) { rowY = this.getPosition(row.firstChild).y; rowHeight = parseInt(row.firstChild.offsetHeight) / 2; } // Because we always have to insert before, we need to offset the height a bit if (y > (rowY - rowHeight) && y < (rowY + rowHeight)) // that's the row we're over // If it's the same as the current row, ignore it if (draggedRow.is(row) || (config.onAllowDrop && !config.onAllowDrop(draggedRow, row)) // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic) || $(row).hasClass("nodrop")) return null; else return row; } return null; }, processMouseup: function() { if (!this.currentTable || !this.dragObject) return null; var config = this.currentTable.tableDnDConfig, droppedRow = this.dragObject, parentLevel = 0, myLevel = 0; // Unbind the event handlers $(document) .unbind(moveEvent, this.mousemove) .unbind(endEvent, this.mouseup); config.hierarchyLevel && config.autoCleanRelations && $(this.currentTable.rows).first().find('td:first').children().each(function () { myLevel = $(this).parents('tr:first').data('level'); myLevel && $(this).parents('tr:first').data('level', --myLevel) && $(this).remove(); }) && config.hierarchyLevel > 1 && $(this.currentTable.rows).each(function () { myLevel = $(this).data('level'); if (myLevel > 1) { parentLevel = $(this).prev().data('level'); while (myLevel > parentLevel + 1) { $(this).find('td:first').children(':first').remove(); $(this).data('level', --myLevel); } } }); // If we have a dragObject, then we need to release it, // The row will already have been moved to the right place so we just reset stuff config.onDragClass && $(droppedRow).removeClass(config.onDragClass) || $(droppedRow).css(config.onDropStyle); this.dragObject = null; // Call the onDrop method if there is one config.onDrop && this.originalOrder !== this.currentOrder() && $(droppedRow).hide().fadeIn('fast') && config.onDrop(this.currentTable, droppedRow); // Call the onDragStop method if there is one config.onDragStop && config.onDragStop(this.currentTable, droppedRow); this.currentTable = null; // let go of the table too }, mouseup: function(e) { e && e.preventDefault(); $.tableDnD.processMouseup(); return false; }, jsonize: function(pretify) { var table = this.currentTable; if (pretify) return JSON.stringify( this.tableData(table), null, table.tableDnDConfig.jsonPretifySeparator ); return JSON.stringify(this.tableData(table)); }, serialize: function() { return $.param(this.tableData(this.currentTable)); }, serializeTable: function(table) { var result = ""; var paramName = table.tableDnDConfig.serializeParamName || table.id; var rows = table.rows; for (var i=0; i 0) result += "&"; var rowId = rows[i].id; if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) { rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0]; result += paramName + '[]=' + rowId; } } return result; }, serializeTables: function() { var result = []; $('table').each(function() { this.id && result.push($.param($.tableDnD.tableData(this))); }); return result.join('&'); }, tableData: function (table) { var config = table.tableDnDConfig, previousIDs = [], currentLevel = 0, indentLevel = 0, rowID = null, data = {}, getSerializeRegexp, paramName, currentID, rows; if (!table) table = this.currentTable; if (!table || !table.rows || !table.rows.length) return {error: { code: 500, message: "Not a valid table."}}; if (!table.id && !config.serializeParamName) return {error: { code: 500, message: "No serializable unique id provided."}}; rows = config.autoCleanRelations && table.rows || $.makeArray(table.rows); paramName = config.serializeParamName || table.id; currentID = paramName; getSerializeRegexp = function (rowId) { if (rowId && config && config.serializeRegexp) return rowId.match(config.serializeRegexp)[0]; return rowId; }; data[currentID] = []; !config.autoCleanRelations && $(rows[0]).data('level') && rows.unshift({id: 'undefined'}); for (var i=0; i < rows.length; i++) { if (config.hierarchyLevel) { indentLevel = $(rows[i]).data('level') || 0; if (indentLevel === 0) { currentID = paramName; previousIDs = []; } else if (indentLevel > currentLevel) { previousIDs.push([currentID, currentLevel]); currentID = getSerializeRegexp(rows[i-1].id); } else if (indentLevel < currentLevel) { for (var h = 0; h < previousIDs.length; h++) { if (previousIDs[h][1] === indentLevel) currentID = previousIDs[h][0]; if (previousIDs[h][1] >= currentLevel) previousIDs[h][1] = 0; } } currentLevel = indentLevel; if (!$.isArray(data[currentID])) data[currentID] = []; rowID = getSerializeRegexp(rows[i].id); rowID && data[currentID].push(rowID); } else { rowID = getSerializeRegexp(rows[i].id); rowID && data[currentID].push(rowID); } } return data; } }; jQuery.fn.extend( { tableDnD : $.tableDnD.build, tableDnDUpdate : $.tableDnD.updateTables, tableDnDSerialize : $.proxy($.tableDnD.serialize, $.tableDnD), tableDnDSerializeAll : $.tableDnD.serializeTables, tableDnDData : $.proxy($.tableDnD.tableData, $.tableDnD) } ); }(jQuery, window, window.document); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/resizable/bootstrap-table-resizable.js ================================================ /** * @author: Dennis Hernández * @version: v2.0.0 */ const isInit = that => that.$el.data('resizableColumns') !== undefined const initResizable = that => { if ( that.options.resizable && !that.options.cardView && !isInit(that) && that.$el.is(':visible') ) { that.$el.resizableColumns({ store: window.store }) } } const destroy = that => { if (isInit(that)) { that.$el.data('resizableColumns').destroy() } } const reInitResizable = that => { destroy(that) initResizable(that) } Object.assign($.fn.bootstrapTable.defaults, { resizable: false }) $.BootstrapTable = class extends $.BootstrapTable { initBody (...args) { super.initBody(...args) this.$el.off('column-switch.bs.table page-change.bs.table') .on('column-switch.bs.table page-change.bs.table', () => { reInitResizable(this) }) reInitResizable(this) } toggleView (...args) { super.toggleView(...args) if (this.options.resizable && this.options.cardView) { // Destroy the plugin destroy(this) } } resetView (...args) { super.resetView(...args) if (this.options.resizable) { // because in fitHeader function, we use setTimeout(func, 100); setTimeout(() => { initResizable(this) }, 100) } } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/extensions/tree/bootstrap-table-tree.js ================================================ /** * 基于bootstrapTreeTable/bootstrap-table-treegrid修改 * Copyright (c) 2019 ruoyi */ (function($) { "use strict"; $.fn.bootstrapTreeTable = function(options, param) { var target = $(this).data('bootstrap.tree.table'); target = target ? target : $(this); // 如果是调用方法 if (typeof options == 'string') { return $.fn.bootstrapTreeTable.methods[options](target, param); } // 如果是初始化组件 options = $.extend({}, $.fn.bootstrapTreeTable.defaults, options || {}); target.hasSelectItem = false; // 是否有radio或checkbox target.data_list = null; // 用于缓存格式化后的数据-按父分组 target.data_obj = null; // 用于缓存格式化后的数据-按id存对象 target.hiddenColumns = []; // 用于存放被隐藏列的field target.lastAjaxParams; // 用户最后一次请求的参数 target.isFixWidth=false; // 是否有固定宽度 target.totalRows = 0; // 记录总数 target.totalPages = 0; // 总页数 // 初始化 var init = function() { // 初始化容器 initContainer(); // 初始化工具栏 initToolbar(); // 初始化表头 initHeader(); // 初始化表体 initBody(); // 初始化数据服务 initServer(); // 动态设置表头宽度 autoTheadWidth(true); // 缓存target对象 target.data('bootstrap.tree.table', target); } // 初始化容器 var initContainer = function() { // 在外层包装一下div,样式用的bootstrap-table的 var $main_div = $("
    "); var $treetable = $("
    "); target.before($main_div); $main_div.append($treetable); $treetable.append(target); target.addClass("table"); if (options.striped) { target.addClass('table-striped'); } if (options.bordered) { target.addClass('table-bordered'); } if (options.hover) { target.addClass('table-hover'); } if (options.condensed) { target.addClass('table-condensed'); } target.html(""); } // 初始化工具栏 var initToolbar = function() { var $toolbar = $("
    "); if (options.toolbar) { $(options.toolbar).addClass('tool-left'); $toolbar.append($(options.toolbar)); } var $rightToolbar = $('
    '); $toolbar.append($rightToolbar); target.parent().before($toolbar); // ruoyi 是否显示检索信息 if (options.showSearch) { var $searchBtn = $(''); $rightToolbar.append($searchBtn); registerSearchBtnClickEvent($searchBtn); } // 是否显示刷新按钮 if (options.showRefresh) { var $refreshBtn = $(''); $rightToolbar.append($refreshBtn); registerRefreshBtnClickEvent($refreshBtn); } // 是否显示列选项 if (options.showColumns) { var $columns_div = $('
    '); var $columns_ul = $(''); $.each(options.columns, function(i, column) { if (column.field != 'selectItem') { var _li = null; if(typeof column.visible == "undefined"||column.visible==true){ _li = $('
  • '); }else{ _li = $('
  • '); target.hiddenColumns.push(column.field); } $columns_ul.append(_li); } }); $columns_div.append($columns_ul); $rightToolbar.append($columns_div); // 注册列选项事件 registerColumnClickEvent(); }else{ $.each(options.columns, function(i, column) { if (column.field != 'selectItem') { if(!(typeof column.visible == "undefined"||column.visible==true)){ target.hiddenColumns.push(column.field); } } }); } } // 初始化隐藏列 var initHiddenColumns = function(){ $.each(target.hiddenColumns, function(i, field) { target.find("."+field+"_cls").hide(); }); } // 初始化表头 var initHeader = function() { var $thr = $(''); $.each(options.columns, function(i, column) { var $th = null; // 判断有没有选择列 if (i == 0 && column.field == 'selectItem') { target.hasSelectItem = true; $th = $(''); } else { $th = $(''); if (column.align) { $th.css("text-align", column.align); } } if((!target.isFixWidth)&& column.width){ target.isFixWidth = column.width.indexOf("px")>-1?true:false; } $th.html(column.title); $thr.append($th); }); var $thead = $(''); $thead.append($thr); target.append($thead); } // 初始化表体 var initBody = function() { var $tbody = $(''); target.append($tbody); // 默认高度 if (options.height) { $tbody.css("height", options.height); } if (options.pagination) { var $pagination = $('
    '); target.append($pagination); } } // 初始化数据服务 var initServer = function(parms) { if (options.pagination) { if(parms == undefined || parms == null) { parms = {}; } parms[options.parentCode] = options.rootIdValue; } // 加载数据前先清空 target.data_list = {}; target.data_obj = {}; // 设置请求分页参数 if (options.pagination) { if (target.lastAjaxParams) { parms = $.extend({}, target.lastAjaxParams, parms); } var params = {}; params.offset = options.pageSize * (options.pageNumber - 1); params.limit = options.pageSize; var curParams = { pageSize: params.limit, pageNum: params.offset / params.limit + 1 }; parms = $.extend(curParams, parms); } var $tbody = target.find("tbody"); // 添加加载loading var $loading = '
    正在努力地加载数据中,请稍候……
    ' $tbody.html($loading); if (options.url) { $.ajax({ type: options.type, url: options.url, data: $.extend(parms, options.ajaxParams), dataType: "json", success: function(data, textStatus, jqXHR) { data = calculateObjectValue(options, options.responseHandler, [data], data); renderTable(data); calculateObjectValue(options, options.onLoadSuccess, [data], data); }, error: function(xhr, textStatus) { var _errorMsg = '
    ' + xhr.responseText + '
    ' $tbody.html(_errorMsg); } }); } else { renderTable(options.data); } } // 加载完数据后渲染表格 var renderTable = function(data) { var list, totalPage = 0, currPage = 0; if (options.pagination) { list = data.rows; // 数据 currPage = options.pageNumber; // 当前页 totalPage = ~~((data.total - 1) / options.pageSize) + 1 // 总页数 target.totalPages = totalPage; target.totalRows = data.total; // 总记录数 } else { list = data; } data = list; var $tbody = target.find("tbody"); // 先清空 $tbody.html(""); if (!data || data.length <= 0) { var _empty = '
    没有找到匹配的记录
    ' $tbody.html(_empty); options.pageNumber = 1; initPagination(0, 0); return; } // 缓存并格式化数据 formatData(data); // 获取所有根节点 var rootNode = target.data_list["_root_"]; // 开始绘制 if (rootNode) { $.each(rootNode, function(i, item) { var _child_row_id = "row_id_" + i recursionNode(item, 1, _child_row_id, "row_root", item[options.code]); }); } // 下边的操作主要是为了查询时让一些没有根节点的节点显示 $.each(data, function(i, item) { if (!item.isShow) { var tr = renderRow(item, false, 1, "", "", options.pagination, item[options.code]); $tbody.append(tr); } }); registerExpanderEvent(); registerRowClickEvent(); initHiddenColumns(); // 动态设置表头宽度 autoTheadWidth(); if (options.pagination) { initPagination(totalPage, currPage); } // 移动端适配 var treetableTable = $(target).parent('.treetable-table'); var availableHeight = treetableTable.outerWidth(); if($.common.isMobile() || availableHeight < 769){ var tableStyle = "width: " + availableHeight + "px;overflow: auto;position: relative;"; treetableTable.attr('style', tableStyle); var w = 0; $.each(options.columns, function(i, column) { if (i == 0 && column.field == 'selectItem') { w += 36; } else { w += 200; } }); $(target).attr('style','width:' + w +'px'); } } // 初始化分页 var initPagination = function (totalPage,currPage) { var $pagination = target.find(".fixed-table-pagination"); $pagination.empty(); var html = []; var pageFrom = (options.pageNumber - 1) * options.pageSize + 1; var pageTo = options.pageNumber * options.pageSize; if (pageTo > target.totalRows) { pageTo = target.totalRows; } if (pageFrom > pageTo) { pageFrom = pageTo; } html.push('
    '); html.push('' + formatShowingRows(pageFrom, pageTo, target.totalRows) + ''); var pageList = false; $.each(options.pageList, function (i, page) { if(target.totalRows > page){ pageList = true; } }) if(pageList){ var _page_list = []; _page_list.push(''); _page_list.push(''); _page_list.push(''); _page_list.push(''); _page_list.push(''); html.push(formatRecordsPerPage(_page_list.join(''))) html.push(''); } html.push('
    '); if(totalPage > 1){ html.push(''); } $pagination.append(html.join('')); var $pageList = $pagination.find('.page-list a'); var $pre = $pagination.find('.page-pre'); var $next = $pagination.find('.page-next'); var $number = $pagination.find('.page-number'); var $first = $pagination.find('.page-first'); var $last = $pagination.find('.page-last'); $pre.off('click').on('click', $.proxy(onPagePre, this)); $pageList.off('click').on('click', $.proxy(onPageListChange, this)); $number.off('click').on('click', $.proxy(onPageNumber, this)); $first.off('click').on('click', $.proxy(onPageFirst, this)); $last.off('click').on('click', $.proxy(onPageLast, this)); $next.off('click').on('click', $.proxy(onPageNext, this)); } var onPageListChange = function(event){ var $this = $(event.currentTarget); $this.parent().addClass('active').siblings().removeClass('active'); var $pagination = target.find(".fixed-table-pagination"); options.pageSize = $this.text().toUpperCase() === target.totalRows ? target.totalRows : + $this.text(); if(target.totalRows < options.pageSize * options.pageNumber){ options.pageNumber = 1; } $pagination.find('.page-size').text(options.pageSize); initServer(); } var onPagePre = function(event){ if ((options.pageNumber - 1) === 0) { options.pageNumber = target.totalPages; } else { options.pageNumber--; } initServer(); } var onPageNumber = function(event){ if (options.pageNumber == $(event.currentTarget).text()) { return; } options.pageNumber = $(event.currentTarget).text(); initServer(); } var onPageFirst = function(event){ options.pageNumber = 1; initServer(); } var onPageLast = function (event) { options.pageNumber = target.totalPages; initServer(); } var onPageNext = function(event){ if ((options.pageNumber + 1) > target.totalPages) { options.pageNumber = 1; } else { options.pageNumber++; } initServer(); } // 动态设置表头宽度 var autoTheadWidth = function(initFlag) { if(options.height>0){ var $thead = target.find("thead"); var $tbody = target.find("tbody"); var borderWidth = parseInt(target.css("border-left-width")) + parseInt(target.css("border-right-width")) $thead.css("width", $tbody.children(":first").width()); if(initFlag){ var resizeWaiter = false; $(window).resize(function() { if(!resizeWaiter){ resizeWaiter = true; setTimeout(function(){ if(!target.isFixWidth){ $tbody.css("width", target.parent().width()-borderWidth); } $thead.css("width", $tbody.children(":first").width()); resizeWaiter = false; }, 300); } }); } } } // 缓存并格式化数据 var formatData = function(data) { var _root = options.rootIdValue ? options.rootIdValue : null; // 父节点属性列表 var parentCodes = []; var rootFlag = false; $.each(data, function(index, item) { if($.inArray(item[options.parentCode], parentCodes) == -1){ parentCodes.push(item[options.parentCode]); } }); $.each(data, function(index, item) { // 添加一个默认属性,用来判断当前节点有没有被显示 item.isShow = false; // 是否分页 if (options.pagination) { if (item.isTreeLeaf == undefined || item.isTreeLeaf == null) { item.isTreeLeaf = false; } else { item.isTreeLeaf = (item["isTreeLeaf"] == 1 ? true: false) || ((item["isTreeLeaf"] == 'true' || item["isTreeLeaf"] == true) ? true: false); } } // 顶级节点校验判断,兼容0,'0','',null var _defaultRootFlag = item[options.parentCode] == '0' || item[options.parentCode] == 0 || item[options.parentCode] == null || item[options.parentCode] == '' || $.inArray(item[options.code], parentCodes) > 0 && !rootFlag; if (!item[options.parentCode] || (_root ? (item[options.parentCode] == options.rootIdValue) : _defaultRootFlag)) { rootFlag = true; if (!target.data_list["_root_"]) { target.data_list["_root_"] = []; } if (!target.data_obj["id_" + item[options.code]]) { target.data_list["_root_"].push(item); } } else { if (!target.data_list["_n_" + item[options.parentCode]]) { target.data_list["_n_" + item[options.parentCode]] = []; } if (!target.data_obj["id_" + item[options.code]]) { target.data_list["_n_" + item[options.parentCode]].push(item); } } target.data_obj["id_" + item[options.code]] = item; }); } // 递归获取子节点并且设置子节点 var recursionNode = function(parentNode, lv, row_id, p_id, k) { var $tbody = target.find("tbody"); var _ls = target.data_list["_n_" + parentNode[options.code]]; var $tr = renderRow(parentNode, _ls ? true : false, lv, row_id, p_id, options.pagination, k); $tbody.append($tr); if (_ls) { $.each(_ls, function(i, item) { var _child_row_id = row_id + "_" + i recursionNode(item, (lv + 1), _child_row_id, row_id, item[options.code]) }); } }; // 绘制行 var renderRow = function(item, isP, lv, row_id, p_id, _pagination, k) { // 标记已显示 item.isShow = true; item.row_id = row_id; item.p_id = p_id; item.lv = lv; var $tr = $(''); var _icon = options.expanderCollapsedClass; if (options.expandAll) { $tr.css("display", "table"); _icon = options.expanderExpandedClass; } else if (lv == 1) { $tr.css("display", "table"); _icon = (options.expandFirst) ? options.expanderExpandedClass : options.expanderCollapsedClass; } else if (lv == 2) { if (options.expandFirst) { $tr.css("display", "table"); } else { $tr.css("display", "none"); } _icon = options.expanderCollapsedClass; } else if (_pagination) { if (item.isTreeLeaf) { _icon = options.expanderCollapsedClass; } } else { $tr.css("display", "none"); _icon = options.expanderCollapsedClass; } $.each(options.columns, function(index, column) { // 判断有没有选择列 if (column.field == 'selectItem') { target.hasSelectItem = true; var $td = $(''); if (column.radio) { var _ipt = $(''); $td.append(_ipt); } if (column.checkbox) { var _ipt = $(''); $td.append(_ipt); } $tr.append($td); } else { var $td = $(''); if(column.width){ $td.css("width",column.width + (column.widthUnit ? column.widthUnit : 'px')); } if(column.align){ $td.css("text-align",column.align); } if(options.expandColumn == index){ $td.css("text-align","left"); } if(column.valign){ $td.css("vertical-align",column.valign); } if(options.showTitle){ $td.addClass("ellipsis"); } // 增加formatter渲染 if (column.formatter) { $td.html(column.formatter.call(this, getItemField(item, column.field), item, index)); } else { if(options.showTitle){ // 只在字段没有formatter时才添加title属性 $td.attr("title",item[column.field]); } $td.text(getItemField(item, column.field)); } if (options.expandColumn == index) { if (_pagination) { if (item["isTreeLeaf"]) { $td.prepend(''); } else { $td.prepend('') } } else { if (!isP) { $td.prepend('') } else { $td.prepend(''); } } for (var int = 0; int < (lv - options.expandColumn); int++) { $td.prepend('') } } $tr.append($td); } }); return $tr; } // 检索信息按钮点击事件 var registerSearchBtnClickEvent = function(btn) { $(btn).off('click').on('click', function () { $(".search-collapse").slideToggle(); }); } // 注册刷新按钮点击事件 var registerRefreshBtnClickEvent = function(btn) { $(btn).off('click').on('click', function () { target.refresh(); }); } // 注册列选项事件 var registerColumnClickEvent = function() { $(".bootstrap-tree-table .treetable-bars .columns label input").off('click').on('click', function () { var $this = $(this); if($this.prop('checked')){ target.showColumn($(this).val()); }else{ target.hideColumn($(this).val()); } }); } // 注册行点击选中事件 var registerRowClickEvent = function() { target.find("tbody").find("tr").unbind(); target.find("tbody").find("tr").click(function() { if (target.hasSelectItem) { var _ipt = $(this).find("input[name='select_item']"); if (_ipt.attr("type") == "radio") { _ipt.prop('checked', true); target.find("tbody").find("tr").removeClass("treetable-selected"); $(this).addClass("treetable-selected"); } else if (_ipt.attr("type") == "checkbox") { if (_ipt.prop('checked')) { _ipt.prop('checked', true); target.find("tbody").find("tr").removeClass("treetable-selected"); $(this).addClass("treetable-selected"); } else { _ipt.prop('checked', false); target.find("tbody").find("tr").removeClass("treetable-selected"); } } else { if (_ipt.prop('checked')) { _ipt.prop('checked', false); $(this).removeClass("treetable-selected"); } else { _ipt.prop('checked', true); $(this).addClass("treetable-selected"); } } var _rowData = target.data_obj["id_" + $(this).data('id')]; calculateObjectValue(options, options.onClickRow, [_rowData], _rowData); } }); } // 注册小图标点击事件--展开缩起 var registerExpanderEvent = function() { target.find("tbody").find("tr").find(".treetable-expander").unbind(); target.find("tbody").find("tr").find(".treetable-expander").click(function() { var _isExpanded = $(this).hasClass(options.expanderExpandedClass); var _isCollapsed = $(this).hasClass(options.expanderCollapsedClass); if (_isExpanded || _isCollapsed) { var tr = $(this).parent().parent(); var row_id = tr.attr("id"); var row_pid = tr.attr("pid"); var _id = tr.attr("data-id"); var _ls = target.find("tbody").find("tr[id^='" + row_id + "_']"); if (!options.pagination) { if (_isExpanded) { $(this).removeClass(options.expanderExpandedClass); $(this).addClass(options.expanderCollapsedClass); if (_ls && _ls.length > 0) { $.each(_ls, function(index, item) { $(item).css("display", "none"); }); } } else { $(this).removeClass(options.expanderCollapsedClass); $(this).addClass(options.expanderExpandedClass); if (_ls && _ls.length > 0) { $.each(_ls, function(index, item) { var _p_icon = $("#" + $(item).attr("pid")).children().eq(options.expandColumn).find(".treetable-expander"); var _p_display = $("#" + $(item).attr("pid")).css('display'); if (_p_icon.hasClass(options.expanderExpandedClass) && _p_display == 'table') { $(item).css("display", "table"); } }); } } } else { var _ls = target.find("tbody").find("tr[id^='" + row_id + "_']"); if (_ls && _ls.length > 0) { if (_isExpanded) { if (row_pid == "row_root") { $('table tr[id^="' + row_id + '_"]').css("display", "none"); $('table tr[id^="' + row_id + '_"]').each(function(i,n) { var _isExpanded = $(n).find(".treetable-expander").hasClass(options.expanderExpandedClass); if (_isExpanded) { $(n).find(".treetable-expander").trigger("click"); } }) } else { $.each(_ls, function(index, item) { $(item).css("display", "none"); var _isExpanded = $(item).find(".treetable-expander").hasClass(options.expanderExpandedClass); if (_isExpanded) { $(item).find(".treetable-expander").trigger("click"); } }); } } else { if (row_pid == "row_root") { $('table tr[pid="' + row_id + '"]').css("display", "table"); } else { $.each(_ls, function(index, item) { var _p_icon = $("#" + $(item).attr("pid")).children().eq(options.expandColumn).find(".treetable-expander"); var _isExpanded = _p_icon.hasClass(options.expanderExpandedClass); var _isCollapsed = _p_icon.hasClass(options.expanderCollapsedClass); if (row_id == $(item).attr("pid")) { $(item).css("display", "table"); } }); } } } else { if (options.pagination) { var parms = {}; parms[options.parentCode] = _id; if (options.dataUrl) { $.ajax({ type: options.type, url: options.dataUrl, data: parms, dataType: "json", success: function(data, textStatus, jqXHR) { $("#" + row_id + "_load").remove(); var list = data; data = list; target.appendData(data) }, error: function(xhr, textStatus) { var _errorMsg = '
    ' + xhr.responseText + '
    ' $("#" + row_id).after(_errorMsg); } }); } } } if (_isExpanded) { $(this).removeClass(options.expanderExpandedClass); $(this).addClass(options.expanderCollapsedClass); } else { $(this).removeClass(options.expanderCollapsedClass); $(this).addClass(options.expanderExpandedClass); } } } }); } // 刷新数据 target.refresh = function(parms) { if(parms){ target.lastAjaxParams=parms; } initServer(target.lastAjaxParams); } // 添加数据刷新表格 target.appendData = function(data) { data.reverse() // 下边的操作主要是为了查询时让一些没有根节点的节点显示 $.each(data, function(i, item) { if (options.pagination) { item.__nodes = (item["nodes"] == 1 ? true: false) || ((item["nodes"] == 'true' || item["nodes"] == true) ? true: false); } var _data = target.data_obj["id_" + item[options.code]]; var _p_data = target.data_obj["id_" + item[options.parentCode]]; var _c_list = target.data_list["_n_" + item[options.parentCode]]; var row_id = ""; //行id var p_id = ""; //父行id var _lv = 1; //如果没有父就是1默认显示 var tr; //要添加行的对象 if (_data && _data.row_id && _data.row_id != "") { row_id = _data.row_id; // 如果已经存在了,就直接引用原来的 } if (_p_data) { p_id = _p_data.row_id; if (row_id == "") { var _tmp = 0 if (_c_list && _c_list.length > 0) { _tmp = _c_list.length; } row_id = _p_data.row_id + "_" + _tmp; } _lv = _p_data.lv + 1; //如果有父 // 绘制行 tr = renderRow(item, true, _lv, row_id, p_id, options.pagination, item[options.code]); var _p_icon = $("#" + _p_data.row_id).children().eq(options.expandColumn).find(".treetable-expander"); var _isExpanded = _p_icon.hasClass(options.expanderExpandedClass); var _isCollapsed = _p_icon.hasClass(options.expanderCollapsedClass); // 父节点有没有展开收缩按钮 if (_isExpanded || _isCollapsed) { // 父节点展开状态显示新加行 if (_isExpanded) { tr.css("display", "table"); } } else { // 父节点没有展开收缩按钮则添加 _p_icon.addClass(options.expanderCollapsedClass); } if (_data) { $("#" + _data.row_id).before(tr); $("#" + _data.row_id).remove(); } else { // 计算父的同级下一行 var _tmp_ls = _p_data.row_id.split("_"); var _p_next = _p_data.row_id.substring(0, _p_data.row_id.length - (_tmp_ls[_tmp_ls.length - 1] + "").length) + (parseInt(_tmp_ls[_tmp_ls.length - 1]) + 1); $("#" + _p_data.row_id).after(tr); } } else { tr = renderRow(item, false, _lv, row_id, p_id, options.pagination, item[options.code]); if (_data) { $("#" + _data.row_id).before(tr); $("#" + _data.row_id).remove(); } else { // 画上 var tbody = target.find("tbody"); tbody.append(tr); } } item.isShow = true; // 缓存并格式化数据 formatData([item]); }); registerExpanderEvent(); registerRowClickEvent(); initHiddenColumns(); } // 展开/折叠指定的行 target.toggleRow=function(id) { var _rowData = target.data_obj["id_" + id]; var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander"); $row_expander.trigger("click"); } // 展开指定的行 target.expandRow=function(id) { var _rowData = target.data_obj["id_" + id]; var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander"); var _isCollapsed = $row_expander.hasClass(target.options.expanderCollapsedClass); if (_isCollapsed) { $row_expander.trigger("click"); } } // 折叠 指定的行 target.collapseRow=function(id) { var _rowData = target.data_obj["id_" + id]; var $row_expander = $("#"+_rowData.row_id).find(".treetable-expander"); var _isExpanded = $row_expander.hasClass(target.options.expanderExpandedClass); if (_isExpanded) { $row_expander.trigger("click"); } } // 展开所有的行 target.expandAll=function() { target.find("tbody").find("tr").find(".treetable-expander").each(function(i,n){ var _isCollapsed = $(n).hasClass(options.expanderCollapsedClass); if (_isCollapsed) { $(n).trigger("click"); } }) } // 折叠所有的行 target.collapseAll=function() { target.find("tbody").find("tr").find(".treetable-expander").each(function(i,n){ var _isExpanded = $(n).hasClass(options.expanderExpandedClass); if (_isExpanded) { $(n).trigger("click"); } }) } // 显示指定列 target.showColumn=function(field,flag) { var _index = $.inArray(field, target.hiddenColumns); if (_index > -1) { target.hiddenColumns.splice(_index, 1); } target.find("."+field+"_cls").show(); //是否更新列选项状态 if(flag&&options.showColumns){ var $input = $(".bootstrap-tree-table .treetable-bars .columns label").find("input[value='"+field+"']") $input.prop("checked", 'checked'); } } // 隐藏指定列 target.hideColumn=function(field,flag) { target.hiddenColumns.push(field); target.find("."+field+"_cls").hide(); //是否更新列选项状态 if(flag&&options.showColumns){ var $input = $(".bootstrap-tree-table .treetable-bars .columns label").find("input[value='"+field+"']") $input.prop("checked", ''); } } // ruoyi 解析数据,支持多层级访问 var getItemField = function (item, field) { var value = item; if (typeof field !== 'string' || item.hasOwnProperty(field)) { return item[field]; } var props = field.split('.'); for (var p in props) { value = value && value[props[p]]; } return value; }; // ruoyi 发起对目标(target)函数的调用 var calculateObjectValue = function (self, name, args, defaultValue) { var func = name; if (typeof name === 'string') { var names = name.split('.'); if (names.length > 1) { func = window; $.each(names, function (i, f) { func = func[f]; }); } else { func = window[name]; } } if (typeof func === 'object') { return func; } if (typeof func === 'function') { return func.apply(self, args); } if (!func && typeof name === 'string' && sprintf.apply(this, [name].concat(args))) { return sprintf.apply(this, [name].concat(args)); } return defaultValue; }; var formatRecordsPerPage = function (pageNumber) { return '每页显示 ' + pageNumber + ' 条记录'; }; var formatShowingRows = function (pageFrom, pageTo, totalRows) { return '显示第 ' + pageFrom + ' 到第 ' + pageTo + ' 条记录,总共 ' + totalRows + ' 条记录。'; }; // 初始化 init(); return target; }; // 组件方法封装........ $.fn.bootstrapTreeTable.methods = { // 为了兼容bootstrap-table的写法,统一返回数组,这里返回了表格显示列的数据 getSelections: function(target, data) { // 所有被选中的记录input var _ipt = target.find("tbody").find("tr").find("input[name='select_item']:checked"); var chk_value = []; // 如果是radio if (_ipt.attr("type") == "radio") { var _data = target.data_obj["id_" + _ipt.val()]; chk_value.push(_data); } else { _ipt.each(function(_i, _item) { var _data = target.data_obj["id_" + $(_item).val()]; chk_value.push(_data); }); } return chk_value; }, // 刷新记录 refresh: function(target, parms) { if (parms) { target.refresh(parms); } else { target.refresh(); } }, // 添加数据到表格 appendData: function(target, data) { if (data) { target.appendData(data); } }, // 展开/折叠指定的行 toggleRow: function(target, id) { target.toggleRow(id); }, // 展开指定的行 expandRow: function(target, id) { target.expandRow(id); }, // 折叠 指定的行 collapseRow: function(target, id) { target.collapseRow(id); }, // 展开所有的行 expandAll: function(target) { target.expandAll(); }, // 折叠所有的行 collapseAll: function(target) { target.collapseAll(); }, // 显示指定列 showColumn: function(target,field) { target.showColumn(field,true); }, // 隐藏指定列 hideColumn: function(target,field) { target.hideColumn(field,true); } // 组件的其他方法也可以进行类似封装........ }; $.fn.bootstrapTreeTable.defaults = { code: 'code', // 选取记录返回的值,用于设置父子关系 parentCode: 'parentCode', // 用于设置父子关系 rootIdValue: 0, // 设置根节点id值----可指定根节点,默认为null,"",0,"0" data: null, // 构造table的数据集合 type: "GET", // 请求数据的ajax类型 url: null, // 请求数据的ajax的url ajaxParams: {}, // 请求数据的ajax的data属性 expandColumn: 1, // 在哪一列上面显示展开按钮 expandAll: false, // 是否全部展开 expandFirst: true, // 是否默认第一级展开--expandAll为false时生效 striped: false, // 是否各行渐变色 bordered: false, // 是否显示边框 hover: true, // 是否鼠标悬停 condensed: false, // 是否紧缩表格 columns: [], // 列 toolbar: null, // 顶部工具条 height: 0, // 表格高度 pagination: false, // 是否显示分页 dataUrl: null, // 加载子节点异步请求数据url pageNumber: 1, // 当前页条数 pageSize: 10, // 每页的记录行数 onClickRow: null, // 单击某行事件 pageList: [10, 25, 50], // 可供选择的每页的行数 showTitle: true, // 是否采用title属性显示字段内容(被formatter格式化的字段不会显示) showSearch: true, // 是否显示检索信息 showColumns: true, // 是否显示内容列下拉框 showRefresh: true, // 是否显示刷新按钮 paginationPreText: '‹', paginationNextText: '›', expanderExpandedClass: 'glyphicon glyphicon-chevron-down', // 展开的按钮的图标 expanderCollapsedClass: 'glyphicon glyphicon-chevron-right', // 缩起的按钮的图标 responseHandler: function(res) { return false; }, onLoadSuccess: function(res) { return false; } }; })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/bootstrap-table/locale/bootstrap-table-zh-CN.js ================================================ /** * Bootstrap Table Chinese translation * Author: Zhixin Wen */ $.fn.bootstrapTable.locales['zh-CN'] = { formatShowSearch: function formatShowSearch() { return '隐藏/显示搜索'; }, formatPageGo: function formatPageGo() { return '跳转'; }, formatCopyRows: function formatCopyRows() { return '复制行'; }, formatPrint: function formatPrint() { return '打印'; }, formatLoadingMessage: function formatLoadingMessage() { return '正在努力地加载数据中,请稍候'; }, formatRecordsPerPage: function formatRecordsPerPage(pageNumber) { return "每页显示 ".concat(pageNumber, " 条记录"); }, formatShowingRows: function formatShowingRows(pageFrom, pageTo, totalRows, totalNotFiltered) { if (totalNotFiltered !== undefined && totalNotFiltered > 0 && totalNotFiltered > totalRows) { return "显示第 ".concat(pageFrom, " 到第 ").concat(pageTo, " 条记录,总共 ").concat(totalRows, " 条记录(从 ").concat(totalNotFiltered, " 总记录中过滤)"); } return "显示第 ".concat(pageFrom, " 到第 ").concat(pageTo, " 条记录,总共 ").concat(totalRows, " 条记录"); }, formatSRPaginationPreText: function formatSRPaginationPreText() { return '上一页'; }, formatSRPaginationPageText: function formatSRPaginationPageText(page) { return "第".concat(page, "页"); }, formatSRPaginationNextText: function formatSRPaginationNextText() { return '下一页'; }, formatDetailPagination: function formatDetailPagination(totalRows) { return "总共 ".concat(totalRows, " 条记录"); }, formatClearSearch: function formatClearSearch() { return '清空过滤'; }, formatSearch: function formatSearch() { return '搜索'; }, formatNoMatches: function formatNoMatches() { return '没有找到匹配的记录'; }, formatPaginationSwitch: function formatPaginationSwitch() { return '隐藏/显示分页'; }, formatPaginationSwitchDown: function formatPaginationSwitchDown() { return '显示分页'; }, formatPaginationSwitchUp: function formatPaginationSwitchUp() { return '隐藏分页'; }, formatRefresh: function formatRefresh() { return '刷新'; }, formatToggle: function formatToggle() { return '切换'; }, formatToggleOn: function formatToggleOn() { return '显示卡片视图'; }, formatToggleOff: function formatToggleOff() { return '隐藏卡片视图'; }, formatColumns: function formatColumns() { return '列'; }, formatColumnsToggleAll: function formatColumnsToggleAll() { return '切换所有'; }, formatFullscreen: function formatFullscreen() { return '全屏'; }, formatAllRows: function formatAllRows() { return '所有'; }, formatAutoRefresh: function formatAutoRefresh() { return '自动刷新'; }, formatExport: function formatExport() { return '导出数据'; }, formatJumpTo: function formatJumpTo() { return '跳转'; }, formatAdvancedSearch: function formatAdvancedSearch() { return '高级搜索'; }, formatAdvancedCloseButton: function formatAdvancedCloseButton() { return '关闭'; }, formatFilterControlSwitch: function formatFilterControlSwitch() { return '隐藏/显示过滤控制'; }, formatFilterControlSwitchHide: function formatFilterControlSwitchHide() { return '隐藏过滤控制'; }, formatFilterControlSwitchShow: function formatFilterControlSwitchShow() { return '显示过滤控制'; } }; $.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['zh-CN']); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.css ================================================ /*! * Cropper.js v1.5.12 * https://fengyuanchen.github.io/cropperjs * * Copyright 2015-present Chen Fengyuan * Released under the MIT license * * Date: 2021-06-12T08:00:11.623Z */ .cropper-container { direction: ltr; font-size: 0; line-height: 0; position: relative; -ms-touch-action: none; touch-action: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .cropper-container img { display: block; height: 100%; image-orientation: 0deg; max-height: none !important; max-width: none !important; min-height: 0 !important; min-width: 0 !important; width: 100%; } .cropper-wrap-box, .cropper-canvas, .cropper-drag-box, .cropper-crop-box, .cropper-modal { bottom: 0; left: 0; position: absolute; right: 0; top: 0; } .cropper-wrap-box, .cropper-canvas { overflow: hidden; } .cropper-drag-box { background-color: #fff; opacity: 0; } .cropper-modal { background-color: #000; opacity: 0.5; } .cropper-view-box { display: block; height: 100%; outline: 1px solid #39f; outline-color: rgba(51, 153, 255, 0.75); overflow: hidden; width: 100%; } .cropper-dashed { border: 0 dashed #eee; display: block; opacity: 0.5; position: absolute; } .cropper-dashed.dashed-h { border-bottom-width: 1px; border-top-width: 1px; height: calc(100% / 3); left: 0; top: calc(100% / 3); width: 100%; } .cropper-dashed.dashed-v { border-left-width: 1px; border-right-width: 1px; height: 100%; left: calc(100% / 3); top: 0; width: calc(100% / 3); } .cropper-center { display: block; height: 0; left: 50%; opacity: 0.75; position: absolute; top: 50%; width: 0; } .cropper-center::before, .cropper-center::after { background-color: #eee; content: ' '; display: block; position: absolute; } .cropper-center::before { height: 1px; left: -3px; top: 0; width: 7px; } .cropper-center::after { height: 7px; left: 0; top: -3px; width: 1px; } .cropper-face, .cropper-line, .cropper-point { display: block; height: 100%; opacity: 0.1; position: absolute; width: 100%; } .cropper-face { background-color: #fff; left: 0; top: 0; } .cropper-line { background-color: #39f; } .cropper-line.line-e { cursor: ew-resize; right: -3px; top: 0; width: 5px; } .cropper-line.line-n { cursor: ns-resize; height: 5px; left: 0; top: -3px; } .cropper-line.line-w { cursor: ew-resize; left: -3px; top: 0; width: 5px; } .cropper-line.line-s { bottom: -3px; cursor: ns-resize; height: 5px; left: 0; } .cropper-point { background-color: #39f; height: 5px; opacity: 0.75; width: 5px; } .cropper-point.point-e { cursor: ew-resize; margin-top: -3px; right: -3px; top: 50%; } .cropper-point.point-n { cursor: ns-resize; left: 50%; margin-left: -3px; top: -3px; } .cropper-point.point-w { cursor: ew-resize; left: -3px; margin-top: -3px; top: 50%; } .cropper-point.point-s { bottom: -3px; cursor: s-resize; left: 50%; margin-left: -3px; } .cropper-point.point-ne { cursor: nesw-resize; right: -3px; top: -3px; } .cropper-point.point-nw { cursor: nwse-resize; left: -3px; top: -3px; } .cropper-point.point-sw { bottom: -3px; cursor: nesw-resize; left: -3px; } .cropper-point.point-se { bottom: -3px; cursor: nwse-resize; height: 20px; opacity: 1; right: -3px; width: 20px; } @media (min-width: 768px) { .cropper-point.point-se { height: 15px; width: 15px; } } @media (min-width: 992px) { .cropper-point.point-se { height: 10px; width: 10px; } } @media (min-width: 1200px) { .cropper-point.point-se { height: 5px; opacity: 0.75; width: 5px; } } .cropper-point.point-se::before { background-color: #39f; bottom: -50%; content: ' '; display: block; height: 200%; opacity: 0; position: absolute; right: -50%; width: 200%; } .cropper-invisible { opacity: 0; } .cropper-bg { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC'); } .cropper-hide { display: block; height: 0; position: absolute; width: 0; } .cropper-hidden { display: none !important; } .cropper-move { cursor: move; } .cropper-crop { cursor: crosshair; } .cropper-disabled .cropper-drag-box, .cropper-disabled .cropper-face, .cropper-disabled .cropper-line, .cropper-disabled .cropper-point { cursor: not-allowed; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/cropper/cropper.js ================================================ /*! * Cropper.js v1.5.12 * https://fengyuanchen.github.io/cropperjs * * Copyright 2015-present Chen Fengyuan * Released under the MIT license * * Date: 2021-06-12T08:00:17.411Z */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Cropper = factory()); }(this, (function () { 'use strict'; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined'; var WINDOW = IS_BROWSER ? window : {}; var IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false; var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false; var NAMESPACE = 'cropper'; // Actions var ACTION_ALL = 'all'; var ACTION_CROP = 'crop'; var ACTION_MOVE = 'move'; var ACTION_ZOOM = 'zoom'; var ACTION_EAST = 'e'; var ACTION_WEST = 'w'; var ACTION_SOUTH = 's'; var ACTION_NORTH = 'n'; var ACTION_NORTH_EAST = 'ne'; var ACTION_NORTH_WEST = 'nw'; var ACTION_SOUTH_EAST = 'se'; var ACTION_SOUTH_WEST = 'sw'; // Classes var CLASS_CROP = "".concat(NAMESPACE, "-crop"); var CLASS_DISABLED = "".concat(NAMESPACE, "-disabled"); var CLASS_HIDDEN = "".concat(NAMESPACE, "-hidden"); var CLASS_HIDE = "".concat(NAMESPACE, "-hide"); var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible"); var CLASS_MODAL = "".concat(NAMESPACE, "-modal"); var CLASS_MOVE = "".concat(NAMESPACE, "-move"); // Data keys var DATA_ACTION = "".concat(NAMESPACE, "Action"); var DATA_PREVIEW = "".concat(NAMESPACE, "Preview"); // Drag modes var DRAG_MODE_CROP = 'crop'; var DRAG_MODE_MOVE = 'move'; var DRAG_MODE_NONE = 'none'; // Events var EVENT_CROP = 'crop'; var EVENT_CROP_END = 'cropend'; var EVENT_CROP_MOVE = 'cropmove'; var EVENT_CROP_START = 'cropstart'; var EVENT_DBLCLICK = 'dblclick'; var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown'; var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove'; var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup'; var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START; var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE; var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END; var EVENT_READY = 'ready'; var EVENT_RESIZE = 'resize'; var EVENT_WHEEL = 'wheel'; var EVENT_ZOOM = 'zoom'; // Mime types var MIME_TYPE_JPEG = 'image/jpeg'; // RegExps var REGEXP_ACTIONS = /^e|w|s|n|se|sw|ne|nw|all|crop|move|zoom$/; var REGEXP_DATA_URL = /^data:/; var REGEXP_DATA_URL_JPEG = /^data:image\/jpeg;base64,/; var REGEXP_TAG_NAME = /^img|canvas$/i; // Misc // Inspired by the default width and height of a canvas element. var MIN_CONTAINER_WIDTH = 200; var MIN_CONTAINER_HEIGHT = 100; var DEFAULTS = { // Define the view mode of the cropper viewMode: 0, // 0, 1, 2, 3 // Define the dragging mode of the cropper dragMode: DRAG_MODE_CROP, // 'crop', 'move' or 'none' // Define the initial aspect ratio of the crop box initialAspectRatio: NaN, // Define the aspect ratio of the crop box aspectRatio: NaN, // An object with the previous cropping result data data: null, // A selector for adding extra containers to preview preview: '', // Re-render the cropper when resize the window responsive: true, // Restore the cropped area after resize the window restore: true, // Check if the current image is a cross-origin image checkCrossOrigin: true, // Check the current image's Exif Orientation information checkOrientation: true, // Show the black modal modal: true, // Show the dashed lines for guiding guides: true, // Show the center indicator for guiding center: true, // Show the white modal to highlight the crop box highlight: true, // Show the grid background background: true, // Enable to crop the image automatically when initialize autoCrop: true, // Define the percentage of automatic cropping area when initializes autoCropArea: 0.8, // Enable to move the image movable: true, // Enable to rotate the image rotatable: true, // Enable to scale the image scalable: true, // Enable to zoom the image zoomable: true, // Enable to zoom the image by dragging touch zoomOnTouch: true, // Enable to zoom the image by wheeling mouse zoomOnWheel: true, // Define zoom ratio when zoom the image by wheeling mouse wheelZoomRatio: 0.1, // Enable to move the crop box cropBoxMovable: true, // Enable to resize the crop box cropBoxResizable: true, // Toggle drag mode between "crop" and "move" when click twice on the cropper toggleDragModeOnDblclick: true, // Size limitation minCanvasWidth: 0, minCanvasHeight: 0, minCropBoxWidth: 0, minCropBoxHeight: 0, minContainerWidth: MIN_CONTAINER_WIDTH, minContainerHeight: MIN_CONTAINER_HEIGHT, // Shortcuts of events ready: null, cropstart: null, cropmove: null, cropend: null, crop: null, zoom: null }; var TEMPLATE = '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '
    ' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
    ' + '
    '; /** * Check if the given value is not a number. */ var isNaN = Number.isNaN || WINDOW.isNaN; /** * Check if the given value is a number. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is a number, else `false`. */ function isNumber(value) { return typeof value === 'number' && !isNaN(value); } /** * Check if the given value is a positive number. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is a positive number, else `false`. */ var isPositiveNumber = function isPositiveNumber(value) { return value > 0 && value < Infinity; }; /** * Check if the given value is undefined. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is undefined, else `false`. */ function isUndefined(value) { return typeof value === 'undefined'; } /** * Check if the given value is an object. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is an object, else `false`. */ function isObject(value) { return _typeof(value) === 'object' && value !== null; } var hasOwnProperty = Object.prototype.hasOwnProperty; /** * Check if the given value is a plain object. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is a plain object, else `false`. */ function isPlainObject(value) { if (!isObject(value)) { return false; } try { var _constructor = value.constructor; var prototype = _constructor.prototype; return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf'); } catch (error) { return false; } } /** * Check if the given value is a function. * @param {*} value - The value to check. * @returns {boolean} Returns `true` if the given value is a function, else `false`. */ function isFunction(value) { return typeof value === 'function'; } var slice = Array.prototype.slice; /** * Convert array-like or iterable object to an array. * @param {*} value - The value to convert. * @returns {Array} Returns a new array. */ function toArray(value) { return Array.from ? Array.from(value) : slice.call(value); } /** * Iterate the given data. * @param {*} data - The data to iterate. * @param {Function} callback - The process function for each element. * @returns {*} The original data. */ function forEach(data, callback) { if (data && isFunction(callback)) { if (Array.isArray(data) || isNumber(data.length) /* array-like */ ) { toArray(data).forEach(function (value, key) { callback.call(data, value, key, data); }); } else if (isObject(data)) { Object.keys(data).forEach(function (key) { callback.call(data, data[key], key, data); }); } } return data; } /** * Extend the given object. * @param {*} target - The target object to extend. * @param {*} args - The rest objects for merging to the target object. * @returns {Object} The extended object. */ var assign = Object.assign || function assign(target) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } if (isObject(target) && args.length > 0) { args.forEach(function (arg) { if (isObject(arg)) { Object.keys(arg).forEach(function (key) { target[key] = arg[key]; }); } }); } return target; }; var REGEXP_DECIMALS = /\.\d*(?:0|9){12}\d*$/; /** * Normalize decimal number. * Check out {@link https://0.30000000000000004.com/} * @param {number} value - The value to normalize. * @param {number} [times=100000000000] - The times for normalizing. * @returns {number} Returns the normalized number. */ function normalizeDecimalNumber(value) { var times = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100000000000; return REGEXP_DECIMALS.test(value) ? Math.round(value * times) / times : value; } var REGEXP_SUFFIX = /^width|height|left|top|marginLeft|marginTop$/; /** * Apply styles to the given element. * @param {Element} element - The target element. * @param {Object} styles - The styles for applying. */ function setStyle(element, styles) { var style = element.style; forEach(styles, function (value, property) { if (REGEXP_SUFFIX.test(property) && isNumber(value)) { value = "".concat(value, "px"); } style[property] = value; }); } /** * Check if the given element has a special class. * @param {Element} element - The element to check. * @param {string} value - The class to search. * @returns {boolean} Returns `true` if the special class was found. */ function hasClass(element, value) { return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1; } /** * Add classes to the given element. * @param {Element} element - The target element. * @param {string} value - The classes to be added. */ function addClass(element, value) { if (!value) { return; } if (isNumber(element.length)) { forEach(element, function (elem) { addClass(elem, value); }); return; } if (element.classList) { element.classList.add(value); return; } var className = element.className.trim(); if (!className) { element.className = value; } else if (className.indexOf(value) < 0) { element.className = "".concat(className, " ").concat(value); } } /** * Remove classes from the given element. * @param {Element} element - The target element. * @param {string} value - The classes to be removed. */ function removeClass(element, value) { if (!value) { return; } if (isNumber(element.length)) { forEach(element, function (elem) { removeClass(elem, value); }); return; } if (element.classList) { element.classList.remove(value); return; } if (element.className.indexOf(value) >= 0) { element.className = element.className.replace(value, ''); } } /** * Add or remove classes from the given element. * @param {Element} element - The target element. * @param {string} value - The classes to be toggled. * @param {boolean} added - Add only. */ function toggleClass(element, value, added) { if (!value) { return; } if (isNumber(element.length)) { forEach(element, function (elem) { toggleClass(elem, value, added); }); return; } // IE10-11 doesn't support the second parameter of `classList.toggle` if (added) { addClass(element, value); } else { removeClass(element, value); } } var REGEXP_CAMEL_CASE = /([a-z\d])([A-Z])/g; /** * Transform the given string from camelCase to kebab-case * @param {string} value - The value to transform. * @returns {string} The transformed value. */ function toParamCase(value) { return value.replace(REGEXP_CAMEL_CASE, '$1-$2').toLowerCase(); } /** * Get data from the given element. * @param {Element} element - The target element. * @param {string} name - The data key to get. * @returns {string} The data value. */ function getData(element, name) { if (isObject(element[name])) { return element[name]; } if (element.dataset) { return element.dataset[name]; } return element.getAttribute("data-".concat(toParamCase(name))); } /** * Set data to the given element. * @param {Element} element - The target element. * @param {string} name - The data key to set. * @param {string} data - The data value. */ function setData(element, name, data) { if (isObject(data)) { element[name] = data; } else if (element.dataset) { element.dataset[name] = data; } else { element.setAttribute("data-".concat(toParamCase(name)), data); } } /** * Remove data from the given element. * @param {Element} element - The target element. * @param {string} name - The data key to remove. */ function removeData(element, name) { if (isObject(element[name])) { try { delete element[name]; } catch (error) { element[name] = undefined; } } else if (element.dataset) { // #128 Safari not allows to delete dataset property try { delete element.dataset[name]; } catch (error) { element.dataset[name] = undefined; } } else { element.removeAttribute("data-".concat(toParamCase(name))); } } var REGEXP_SPACES = /\s\s*/; var onceSupported = function () { var supported = false; if (IS_BROWSER) { var once = false; var listener = function listener() {}; var options = Object.defineProperty({}, 'once', { get: function get() { supported = true; return once; }, /** * This setter can fix a `TypeError` in strict mode * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only} * @param {boolean} value - The value to set */ set: function set(value) { once = value; } }); WINDOW.addEventListener('test', listener, options); WINDOW.removeEventListener('test', listener, options); } return supported; }(); /** * Remove event listener from the target element. * @param {Element} element - The event target. * @param {string} type - The event type(s). * @param {Function} listener - The event listener. * @param {Object} options - The event options. */ function removeListener(element, type, listener) { var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var handler = listener; type.trim().split(REGEXP_SPACES).forEach(function (event) { if (!onceSupported) { var listeners = element.listeners; if (listeners && listeners[event] && listeners[event][listener]) { handler = listeners[event][listener]; delete listeners[event][listener]; if (Object.keys(listeners[event]).length === 0) { delete listeners[event]; } if (Object.keys(listeners).length === 0) { delete element.listeners; } } } element.removeEventListener(event, handler, options); }); } /** * Add event listener to the target element. * @param {Element} element - The event target. * @param {string} type - The event type(s). * @param {Function} listener - The event listener. * @param {Object} options - The event options. */ function addListener(element, type, listener) { var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var _handler = listener; type.trim().split(REGEXP_SPACES).forEach(function (event) { if (options.once && !onceSupported) { var _element$listeners = element.listeners, listeners = _element$listeners === void 0 ? {} : _element$listeners; _handler = function handler() { delete listeners[event][listener]; element.removeEventListener(event, _handler, options); for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } listener.apply(element, args); }; if (!listeners[event]) { listeners[event] = {}; } if (listeners[event][listener]) { element.removeEventListener(event, listeners[event][listener], options); } listeners[event][listener] = _handler; element.listeners = listeners; } element.addEventListener(event, _handler, options); }); } /** * Dispatch event on the target element. * @param {Element} element - The event target. * @param {string} type - The event type(s). * @param {Object} data - The additional event data. * @returns {boolean} Indicate if the event is default prevented or not. */ function dispatchEvent(element, type, data) { var event; // Event and CustomEvent on IE9-11 are global objects, not constructors if (isFunction(Event) && isFunction(CustomEvent)) { event = new CustomEvent(type, { detail: data, bubbles: true, cancelable: true }); } else { event = document.createEvent('CustomEvent'); event.initCustomEvent(type, true, true, data); } return element.dispatchEvent(event); } /** * Get the offset base on the document. * @param {Element} element - The target element. * @returns {Object} The offset data. */ function getOffset(element) { var box = element.getBoundingClientRect(); return { left: box.left + (window.pageXOffset - document.documentElement.clientLeft), top: box.top + (window.pageYOffset - document.documentElement.clientTop) }; } var location = WINDOW.location; var REGEXP_ORIGINS = /^(\w+:)\/\/([^:/?#]*):?(\d*)/i; /** * Check if the given URL is a cross origin URL. * @param {string} url - The target URL. * @returns {boolean} Returns `true` if the given URL is a cross origin URL, else `false`. */ function isCrossOriginURL(url) { var parts = url.match(REGEXP_ORIGINS); return parts !== null && (parts[1] !== location.protocol || parts[2] !== location.hostname || parts[3] !== location.port); } /** * Add timestamp to the given URL. * @param {string} url - The target URL. * @returns {string} The result URL. */ function addTimestamp(url) { var timestamp = "timestamp=".concat(new Date().getTime()); return url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp; } /** * Get transforms base on the given object. * @param {Object} obj - The target object. * @returns {string} A string contains transform values. */ function getTransforms(_ref) { var rotate = _ref.rotate, scaleX = _ref.scaleX, scaleY = _ref.scaleY, translateX = _ref.translateX, translateY = _ref.translateY; var values = []; if (isNumber(translateX) && translateX !== 0) { values.push("translateX(".concat(translateX, "px)")); } if (isNumber(translateY) && translateY !== 0) { values.push("translateY(".concat(translateY, "px)")); } // Rotate should come first before scale to match orientation transform if (isNumber(rotate) && rotate !== 0) { values.push("rotate(".concat(rotate, "deg)")); } if (isNumber(scaleX) && scaleX !== 1) { values.push("scaleX(".concat(scaleX, ")")); } if (isNumber(scaleY) && scaleY !== 1) { values.push("scaleY(".concat(scaleY, ")")); } var transform = values.length ? values.join(' ') : 'none'; return { WebkitTransform: transform, msTransform: transform, transform: transform }; } /** * Get the max ratio of a group of pointers. * @param {string} pointers - The target pointers. * @returns {number} The result ratio. */ function getMaxZoomRatio(pointers) { var pointers2 = _objectSpread2({}, pointers); var maxRatio = 0; forEach(pointers, function (pointer, pointerId) { delete pointers2[pointerId]; forEach(pointers2, function (pointer2) { var x1 = Math.abs(pointer.startX - pointer2.startX); var y1 = Math.abs(pointer.startY - pointer2.startY); var x2 = Math.abs(pointer.endX - pointer2.endX); var y2 = Math.abs(pointer.endY - pointer2.endY); var z1 = Math.sqrt(x1 * x1 + y1 * y1); var z2 = Math.sqrt(x2 * x2 + y2 * y2); var ratio = (z2 - z1) / z1; if (Math.abs(ratio) > Math.abs(maxRatio)) { maxRatio = ratio; } }); }); return maxRatio; } /** * Get a pointer from an event object. * @param {Object} event - The target event object. * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not. * @returns {Object} The result pointer contains start and/or end point coordinates. */ function getPointer(_ref2, endOnly) { var pageX = _ref2.pageX, pageY = _ref2.pageY; var end = { endX: pageX, endY: pageY }; return endOnly ? end : _objectSpread2({ startX: pageX, startY: pageY }, end); } /** * Get the center point coordinate of a group of pointers. * @param {Object} pointers - The target pointers. * @returns {Object} The center point coordinate. */ function getPointersCenter(pointers) { var pageX = 0; var pageY = 0; var count = 0; forEach(pointers, function (_ref3) { var startX = _ref3.startX, startY = _ref3.startY; pageX += startX; pageY += startY; count += 1; }); pageX /= count; pageY /= count; return { pageX: pageX, pageY: pageY }; } /** * Get the max sizes in a rectangle under the given aspect ratio. * @param {Object} data - The original sizes. * @param {string} [type='contain'] - The adjust type. * @returns {Object} The result sizes. */ function getAdjustedSizes(_ref4) // or 'cover' { var aspectRatio = _ref4.aspectRatio, height = _ref4.height, width = _ref4.width; var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'contain'; var isValidWidth = isPositiveNumber(width); var isValidHeight = isPositiveNumber(height); if (isValidWidth && isValidHeight) { var adjustedWidth = height * aspectRatio; if (type === 'contain' && adjustedWidth > width || type === 'cover' && adjustedWidth < width) { height = width / aspectRatio; } else { width = height * aspectRatio; } } else if (isValidWidth) { height = width / aspectRatio; } else if (isValidHeight) { width = height * aspectRatio; } return { width: width, height: height }; } /** * Get the new sizes of a rectangle after rotated. * @param {Object} data - The original sizes. * @returns {Object} The result sizes. */ function getRotatedSizes(_ref5) { var width = _ref5.width, height = _ref5.height, degree = _ref5.degree; degree = Math.abs(degree) % 180; if (degree === 90) { return { width: height, height: width }; } var arc = degree % 90 * Math.PI / 180; var sinArc = Math.sin(arc); var cosArc = Math.cos(arc); var newWidth = width * cosArc + height * sinArc; var newHeight = width * sinArc + height * cosArc; return degree > 90 ? { width: newHeight, height: newWidth } : { width: newWidth, height: newHeight }; } /** * Get a canvas which drew the given image. * @param {HTMLImageElement} image - The image for drawing. * @param {Object} imageData - The image data. * @param {Object} canvasData - The canvas data. * @param {Object} options - The options. * @returns {HTMLCanvasElement} The result canvas. */ function getSourceCanvas(image, _ref6, _ref7, _ref8) { var imageAspectRatio = _ref6.aspectRatio, imageNaturalWidth = _ref6.naturalWidth, imageNaturalHeight = _ref6.naturalHeight, _ref6$rotate = _ref6.rotate, rotate = _ref6$rotate === void 0 ? 0 : _ref6$rotate, _ref6$scaleX = _ref6.scaleX, scaleX = _ref6$scaleX === void 0 ? 1 : _ref6$scaleX, _ref6$scaleY = _ref6.scaleY, scaleY = _ref6$scaleY === void 0 ? 1 : _ref6$scaleY; var aspectRatio = _ref7.aspectRatio, naturalWidth = _ref7.naturalWidth, naturalHeight = _ref7.naturalHeight; var _ref8$fillColor = _ref8.fillColor, fillColor = _ref8$fillColor === void 0 ? 'transparent' : _ref8$fillColor, _ref8$imageSmoothingE = _ref8.imageSmoothingEnabled, imageSmoothingEnabled = _ref8$imageSmoothingE === void 0 ? true : _ref8$imageSmoothingE, _ref8$imageSmoothingQ = _ref8.imageSmoothingQuality, imageSmoothingQuality = _ref8$imageSmoothingQ === void 0 ? 'low' : _ref8$imageSmoothingQ, _ref8$maxWidth = _ref8.maxWidth, maxWidth = _ref8$maxWidth === void 0 ? Infinity : _ref8$maxWidth, _ref8$maxHeight = _ref8.maxHeight, maxHeight = _ref8$maxHeight === void 0 ? Infinity : _ref8$maxHeight, _ref8$minWidth = _ref8.minWidth, minWidth = _ref8$minWidth === void 0 ? 0 : _ref8$minWidth, _ref8$minHeight = _ref8.minHeight, minHeight = _ref8$minHeight === void 0 ? 0 : _ref8$minHeight; var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); var maxSizes = getAdjustedSizes({ aspectRatio: aspectRatio, width: maxWidth, height: maxHeight }); var minSizes = getAdjustedSizes({ aspectRatio: aspectRatio, width: minWidth, height: minHeight }, 'cover'); var width = Math.min(maxSizes.width, Math.max(minSizes.width, naturalWidth)); var height = Math.min(maxSizes.height, Math.max(minSizes.height, naturalHeight)); // Note: should always use image's natural sizes for drawing as // imageData.naturalWidth === canvasData.naturalHeight when rotate % 180 === 90 var destMaxSizes = getAdjustedSizes({ aspectRatio: imageAspectRatio, width: maxWidth, height: maxHeight }); var destMinSizes = getAdjustedSizes({ aspectRatio: imageAspectRatio, width: minWidth, height: minHeight }, 'cover'); var destWidth = Math.min(destMaxSizes.width, Math.max(destMinSizes.width, imageNaturalWidth)); var destHeight = Math.min(destMaxSizes.height, Math.max(destMinSizes.height, imageNaturalHeight)); var params = [-destWidth / 2, -destHeight / 2, destWidth, destHeight]; canvas.width = normalizeDecimalNumber(width); canvas.height = normalizeDecimalNumber(height); context.fillStyle = fillColor; context.fillRect(0, 0, width, height); context.save(); context.translate(width / 2, height / 2); context.rotate(rotate * Math.PI / 180); context.scale(scaleX, scaleY); context.imageSmoothingEnabled = imageSmoothingEnabled; context.imageSmoothingQuality = imageSmoothingQuality; context.drawImage.apply(context, [image].concat(_toConsumableArray(params.map(function (param) { return Math.floor(normalizeDecimalNumber(param)); })))); context.restore(); return canvas; } var fromCharCode = String.fromCharCode; /** * Get string from char code in data view. * @param {DataView} dataView - The data view for read. * @param {number} start - The start index. * @param {number} length - The read length. * @returns {string} The read result. */ function getStringFromCharCode(dataView, start, length) { var str = ''; length += start; for (var i = start; i < length; i += 1) { str += fromCharCode(dataView.getUint8(i)); } return str; } var REGEXP_DATA_URL_HEAD = /^data:.*,/; /** * Transform Data URL to array buffer. * @param {string} dataURL - The Data URL to transform. * @returns {ArrayBuffer} The result array buffer. */ function dataURLToArrayBuffer(dataURL) { var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, ''); var binary = atob(base64); var arrayBuffer = new ArrayBuffer(binary.length); var uint8 = new Uint8Array(arrayBuffer); forEach(uint8, function (value, i) { uint8[i] = binary.charCodeAt(i); }); return arrayBuffer; } /** * Transform array buffer to Data URL. * @param {ArrayBuffer} arrayBuffer - The array buffer to transform. * @param {string} mimeType - The mime type of the Data URL. * @returns {string} The result Data URL. */ function arrayBufferToDataURL(arrayBuffer, mimeType) { var chunks = []; // Chunk Typed Array for better performance (#435) var chunkSize = 8192; var uint8 = new Uint8Array(arrayBuffer); while (uint8.length > 0) { // XXX: Babel's `toConsumableArray` helper will throw error in IE or Safari 9 // eslint-disable-next-line prefer-spread chunks.push(fromCharCode.apply(null, toArray(uint8.subarray(0, chunkSize)))); uint8 = uint8.subarray(chunkSize); } return "data:".concat(mimeType, ";base64,").concat(btoa(chunks.join(''))); } /** * Get orientation value from given array buffer. * @param {ArrayBuffer} arrayBuffer - The array buffer to read. * @returns {number} The read orientation value. */ function resetAndGetOrientation(arrayBuffer) { var dataView = new DataView(arrayBuffer); var orientation; // Ignores range error when the image does not have correct Exif information try { var littleEndian; var app1Start; var ifdStart; // Only handle JPEG image (start by 0xFFD8) if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) { var length = dataView.byteLength; var offset = 2; while (offset + 1 < length) { if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) { app1Start = offset; break; } offset += 1; } } if (app1Start) { var exifIDCode = app1Start + 4; var tiffOffset = app1Start + 10; if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') { var endianness = dataView.getUint16(tiffOffset); littleEndian = endianness === 0x4949; if (littleEndian || endianness === 0x4D4D /* bigEndian */ ) { if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) { var firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian); if (firstIFDOffset >= 0x00000008) { ifdStart = tiffOffset + firstIFDOffset; } } } } } if (ifdStart) { var _length = dataView.getUint16(ifdStart, littleEndian); var _offset; var i; for (i = 0; i < _length; i += 1) { _offset = ifdStart + i * 12 + 2; if (dataView.getUint16(_offset, littleEndian) === 0x0112 /* Orientation */ ) { // 8 is the offset of the current tag's value _offset += 8; // Get the original orientation value orientation = dataView.getUint16(_offset, littleEndian); // Override the orientation with its default value dataView.setUint16(_offset, 1, littleEndian); break; } } } } catch (error) { orientation = 1; } return orientation; } /** * Parse Exif Orientation value. * @param {number} orientation - The orientation to parse. * @returns {Object} The parsed result. */ function parseOrientation(orientation) { var rotate = 0; var scaleX = 1; var scaleY = 1; switch (orientation) { // Flip horizontal case 2: scaleX = -1; break; // Rotate left 180° case 3: rotate = -180; break; // Flip vertical case 4: scaleY = -1; break; // Flip vertical and rotate right 90° case 5: rotate = 90; scaleY = -1; break; // Rotate right 90° case 6: rotate = 90; break; // Flip horizontal and rotate right 90° case 7: rotate = 90; scaleX = -1; break; // Rotate left 90° case 8: rotate = -90; break; } return { rotate: rotate, scaleX: scaleX, scaleY: scaleY }; } var render = { render: function render() { this.initContainer(); this.initCanvas(); this.initCropBox(); this.renderCanvas(); if (this.cropped) { this.renderCropBox(); } }, initContainer: function initContainer() { var element = this.element, options = this.options, container = this.container, cropper = this.cropper; var minWidth = Number(options.minContainerWidth); var minHeight = Number(options.minContainerHeight); addClass(cropper, CLASS_HIDDEN); removeClass(element, CLASS_HIDDEN); var containerData = { width: Math.max(container.offsetWidth, minWidth >= 0 ? minWidth : MIN_CONTAINER_WIDTH), height: Math.max(container.offsetHeight, minHeight >= 0 ? minHeight : MIN_CONTAINER_HEIGHT) }; this.containerData = containerData; setStyle(cropper, { width: containerData.width, height: containerData.height }); addClass(element, CLASS_HIDDEN); removeClass(cropper, CLASS_HIDDEN); }, // Canvas (image wrapper) initCanvas: function initCanvas() { var containerData = this.containerData, imageData = this.imageData; var viewMode = this.options.viewMode; var rotated = Math.abs(imageData.rotate) % 180 === 90; var naturalWidth = rotated ? imageData.naturalHeight : imageData.naturalWidth; var naturalHeight = rotated ? imageData.naturalWidth : imageData.naturalHeight; var aspectRatio = naturalWidth / naturalHeight; var canvasWidth = containerData.width; var canvasHeight = containerData.height; if (containerData.height * aspectRatio > containerData.width) { if (viewMode === 3) { canvasWidth = containerData.height * aspectRatio; } else { canvasHeight = containerData.width / aspectRatio; } } else if (viewMode === 3) { canvasHeight = containerData.width / aspectRatio; } else { canvasWidth = containerData.height * aspectRatio; } var canvasData = { aspectRatio: aspectRatio, naturalWidth: naturalWidth, naturalHeight: naturalHeight, width: canvasWidth, height: canvasHeight }; this.canvasData = canvasData; this.limited = viewMode === 1 || viewMode === 2; this.limitCanvas(true, true); canvasData.width = Math.min(Math.max(canvasData.width, canvasData.minWidth), canvasData.maxWidth); canvasData.height = Math.min(Math.max(canvasData.height, canvasData.minHeight), canvasData.maxHeight); canvasData.left = (containerData.width - canvasData.width) / 2; canvasData.top = (containerData.height - canvasData.height) / 2; canvasData.oldLeft = canvasData.left; canvasData.oldTop = canvasData.top; this.initialCanvasData = assign({}, canvasData); }, limitCanvas: function limitCanvas(sizeLimited, positionLimited) { var options = this.options, containerData = this.containerData, canvasData = this.canvasData, cropBoxData = this.cropBoxData; var viewMode = options.viewMode; var aspectRatio = canvasData.aspectRatio; var cropped = this.cropped && cropBoxData; if (sizeLimited) { var minCanvasWidth = Number(options.minCanvasWidth) || 0; var minCanvasHeight = Number(options.minCanvasHeight) || 0; if (viewMode > 1) { minCanvasWidth = Math.max(minCanvasWidth, containerData.width); minCanvasHeight = Math.max(minCanvasHeight, containerData.height); if (viewMode === 3) { if (minCanvasHeight * aspectRatio > minCanvasWidth) { minCanvasWidth = minCanvasHeight * aspectRatio; } else { minCanvasHeight = minCanvasWidth / aspectRatio; } } } else if (viewMode > 0) { if (minCanvasWidth) { minCanvasWidth = Math.max(minCanvasWidth, cropped ? cropBoxData.width : 0); } else if (minCanvasHeight) { minCanvasHeight = Math.max(minCanvasHeight, cropped ? cropBoxData.height : 0); } else if (cropped) { minCanvasWidth = cropBoxData.width; minCanvasHeight = cropBoxData.height; if (minCanvasHeight * aspectRatio > minCanvasWidth) { minCanvasWidth = minCanvasHeight * aspectRatio; } else { minCanvasHeight = minCanvasWidth / aspectRatio; } } } var _getAdjustedSizes = getAdjustedSizes({ aspectRatio: aspectRatio, width: minCanvasWidth, height: minCanvasHeight }); minCanvasWidth = _getAdjustedSizes.width; minCanvasHeight = _getAdjustedSizes.height; canvasData.minWidth = minCanvasWidth; canvasData.minHeight = minCanvasHeight; canvasData.maxWidth = Infinity; canvasData.maxHeight = Infinity; } if (positionLimited) { if (viewMode > (cropped ? 0 : 1)) { var newCanvasLeft = containerData.width - canvasData.width; var newCanvasTop = containerData.height - canvasData.height; canvasData.minLeft = Math.min(0, newCanvasLeft); canvasData.minTop = Math.min(0, newCanvasTop); canvasData.maxLeft = Math.max(0, newCanvasLeft); canvasData.maxTop = Math.max(0, newCanvasTop); if (cropped && this.limited) { canvasData.minLeft = Math.min(cropBoxData.left, cropBoxData.left + (cropBoxData.width - canvasData.width)); canvasData.minTop = Math.min(cropBoxData.top, cropBoxData.top + (cropBoxData.height - canvasData.height)); canvasData.maxLeft = cropBoxData.left; canvasData.maxTop = cropBoxData.top; if (viewMode === 2) { if (canvasData.width >= containerData.width) { canvasData.minLeft = Math.min(0, newCanvasLeft); canvasData.maxLeft = Math.max(0, newCanvasLeft); } if (canvasData.height >= containerData.height) { canvasData.minTop = Math.min(0, newCanvasTop); canvasData.maxTop = Math.max(0, newCanvasTop); } } } } else { canvasData.minLeft = -canvasData.width; canvasData.minTop = -canvasData.height; canvasData.maxLeft = containerData.width; canvasData.maxTop = containerData.height; } } }, renderCanvas: function renderCanvas(changed, transformed) { var canvasData = this.canvasData, imageData = this.imageData; if (transformed) { var _getRotatedSizes = getRotatedSizes({ width: imageData.naturalWidth * Math.abs(imageData.scaleX || 1), height: imageData.naturalHeight * Math.abs(imageData.scaleY || 1), degree: imageData.rotate || 0 }), naturalWidth = _getRotatedSizes.width, naturalHeight = _getRotatedSizes.height; var width = canvasData.width * (naturalWidth / canvasData.naturalWidth); var height = canvasData.height * (naturalHeight / canvasData.naturalHeight); canvasData.left -= (width - canvasData.width) / 2; canvasData.top -= (height - canvasData.height) / 2; canvasData.width = width; canvasData.height = height; canvasData.aspectRatio = naturalWidth / naturalHeight; canvasData.naturalWidth = naturalWidth; canvasData.naturalHeight = naturalHeight; this.limitCanvas(true, false); } if (canvasData.width > canvasData.maxWidth || canvasData.width < canvasData.minWidth) { canvasData.left = canvasData.oldLeft; } if (canvasData.height > canvasData.maxHeight || canvasData.height < canvasData.minHeight) { canvasData.top = canvasData.oldTop; } canvasData.width = Math.min(Math.max(canvasData.width, canvasData.minWidth), canvasData.maxWidth); canvasData.height = Math.min(Math.max(canvasData.height, canvasData.minHeight), canvasData.maxHeight); this.limitCanvas(false, true); canvasData.left = Math.min(Math.max(canvasData.left, canvasData.minLeft), canvasData.maxLeft); canvasData.top = Math.min(Math.max(canvasData.top, canvasData.minTop), canvasData.maxTop); canvasData.oldLeft = canvasData.left; canvasData.oldTop = canvasData.top; setStyle(this.canvas, assign({ width: canvasData.width, height: canvasData.height }, getTransforms({ translateX: canvasData.left, translateY: canvasData.top }))); this.renderImage(changed); if (this.cropped && this.limited) { this.limitCropBox(true, true); } }, renderImage: function renderImage(changed) { var canvasData = this.canvasData, imageData = this.imageData; var width = imageData.naturalWidth * (canvasData.width / canvasData.naturalWidth); var height = imageData.naturalHeight * (canvasData.height / canvasData.naturalHeight); assign(imageData, { width: width, height: height, left: (canvasData.width - width) / 2, top: (canvasData.height - height) / 2 }); setStyle(this.image, assign({ width: imageData.width, height: imageData.height }, getTransforms(assign({ translateX: imageData.left, translateY: imageData.top }, imageData)))); if (changed) { this.output(); } }, initCropBox: function initCropBox() { var options = this.options, canvasData = this.canvasData; var aspectRatio = options.aspectRatio || options.initialAspectRatio; var autoCropArea = Number(options.autoCropArea) || 0.8; var cropBoxData = { width: canvasData.width, height: canvasData.height }; if (aspectRatio) { if (canvasData.height * aspectRatio > canvasData.width) { cropBoxData.height = cropBoxData.width / aspectRatio; } else { cropBoxData.width = cropBoxData.height * aspectRatio; } } this.cropBoxData = cropBoxData; this.limitCropBox(true, true); // Initialize auto crop area cropBoxData.width = Math.min(Math.max(cropBoxData.width, cropBoxData.minWidth), cropBoxData.maxWidth); cropBoxData.height = Math.min(Math.max(cropBoxData.height, cropBoxData.minHeight), cropBoxData.maxHeight); // The width/height of auto crop area must large than "minWidth/Height" cropBoxData.width = Math.max(cropBoxData.minWidth, cropBoxData.width * autoCropArea); cropBoxData.height = Math.max(cropBoxData.minHeight, cropBoxData.height * autoCropArea); cropBoxData.left = canvasData.left + (canvasData.width - cropBoxData.width) / 2; cropBoxData.top = canvasData.top + (canvasData.height - cropBoxData.height) / 2; cropBoxData.oldLeft = cropBoxData.left; cropBoxData.oldTop = cropBoxData.top; this.initialCropBoxData = assign({}, cropBoxData); }, limitCropBox: function limitCropBox(sizeLimited, positionLimited) { var options = this.options, containerData = this.containerData, canvasData = this.canvasData, cropBoxData = this.cropBoxData, limited = this.limited; var aspectRatio = options.aspectRatio; if (sizeLimited) { var minCropBoxWidth = Number(options.minCropBoxWidth) || 0; var minCropBoxHeight = Number(options.minCropBoxHeight) || 0; var maxCropBoxWidth = limited ? Math.min(containerData.width, canvasData.width, canvasData.width + canvasData.left, containerData.width - canvasData.left) : containerData.width; var maxCropBoxHeight = limited ? Math.min(containerData.height, canvasData.height, canvasData.height + canvasData.top, containerData.height - canvasData.top) : containerData.height; // The min/maxCropBoxWidth/Height must be less than container's width/height minCropBoxWidth = Math.min(minCropBoxWidth, containerData.width); minCropBoxHeight = Math.min(minCropBoxHeight, containerData.height); if (aspectRatio) { if (minCropBoxWidth && minCropBoxHeight) { if (minCropBoxHeight * aspectRatio > minCropBoxWidth) { minCropBoxHeight = minCropBoxWidth / aspectRatio; } else { minCropBoxWidth = minCropBoxHeight * aspectRatio; } } else if (minCropBoxWidth) { minCropBoxHeight = minCropBoxWidth / aspectRatio; } else if (minCropBoxHeight) { minCropBoxWidth = minCropBoxHeight * aspectRatio; } if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) { maxCropBoxHeight = maxCropBoxWidth / aspectRatio; } else { maxCropBoxWidth = maxCropBoxHeight * aspectRatio; } } // The minWidth/Height must be less than maxWidth/Height cropBoxData.minWidth = Math.min(minCropBoxWidth, maxCropBoxWidth); cropBoxData.minHeight = Math.min(minCropBoxHeight, maxCropBoxHeight); cropBoxData.maxWidth = maxCropBoxWidth; cropBoxData.maxHeight = maxCropBoxHeight; } if (positionLimited) { if (limited) { cropBoxData.minLeft = Math.max(0, canvasData.left); cropBoxData.minTop = Math.max(0, canvasData.top); cropBoxData.maxLeft = Math.min(containerData.width, canvasData.left + canvasData.width) - cropBoxData.width; cropBoxData.maxTop = Math.min(containerData.height, canvasData.top + canvasData.height) - cropBoxData.height; } else { cropBoxData.minLeft = 0; cropBoxData.minTop = 0; cropBoxData.maxLeft = containerData.width - cropBoxData.width; cropBoxData.maxTop = containerData.height - cropBoxData.height; } } }, renderCropBox: function renderCropBox() { var options = this.options, containerData = this.containerData, cropBoxData = this.cropBoxData; if (cropBoxData.width > cropBoxData.maxWidth || cropBoxData.width < cropBoxData.minWidth) { cropBoxData.left = cropBoxData.oldLeft; } if (cropBoxData.height > cropBoxData.maxHeight || cropBoxData.height < cropBoxData.minHeight) { cropBoxData.top = cropBoxData.oldTop; } cropBoxData.width = Math.min(Math.max(cropBoxData.width, cropBoxData.minWidth), cropBoxData.maxWidth); cropBoxData.height = Math.min(Math.max(cropBoxData.height, cropBoxData.minHeight), cropBoxData.maxHeight); this.limitCropBox(false, true); cropBoxData.left = Math.min(Math.max(cropBoxData.left, cropBoxData.minLeft), cropBoxData.maxLeft); cropBoxData.top = Math.min(Math.max(cropBoxData.top, cropBoxData.minTop), cropBoxData.maxTop); cropBoxData.oldLeft = cropBoxData.left; cropBoxData.oldTop = cropBoxData.top; if (options.movable && options.cropBoxMovable) { // Turn to move the canvas when the crop box is equal to the container setData(this.face, DATA_ACTION, cropBoxData.width >= containerData.width && cropBoxData.height >= containerData.height ? ACTION_MOVE : ACTION_ALL); } setStyle(this.cropBox, assign({ width: cropBoxData.width, height: cropBoxData.height }, getTransforms({ translateX: cropBoxData.left, translateY: cropBoxData.top }))); if (this.cropped && this.limited) { this.limitCanvas(true, true); } if (!this.disabled) { this.output(); } }, output: function output() { this.preview(); dispatchEvent(this.element, EVENT_CROP, this.getData()); } }; var preview = { initPreview: function initPreview() { var element = this.element, crossOrigin = this.crossOrigin; var preview = this.options.preview; var url = crossOrigin ? this.crossOriginUrl : this.url; var alt = element.alt || 'The image to preview'; var image = document.createElement('img'); if (crossOrigin) { image.crossOrigin = crossOrigin; } image.src = url; image.alt = alt; this.viewBox.appendChild(image); this.viewBoxImage = image; if (!preview) { return; } var previews = preview; if (typeof preview === 'string') { previews = element.ownerDocument.querySelectorAll(preview); } else if (preview.querySelector) { previews = [preview]; } this.previews = previews; forEach(previews, function (el) { var img = document.createElement('img'); // Save the original size for recover setData(el, DATA_PREVIEW, { width: el.offsetWidth, height: el.offsetHeight, html: el.innerHTML }); if (crossOrigin) { img.crossOrigin = crossOrigin; } img.src = url; img.alt = alt; /** * Override img element styles * Add `display:block` to avoid margin top issue * Add `height:auto` to override `height` attribute on IE8 * (Occur only when margin-top <= -height) */ img.style.cssText = 'display:block;' + 'width:100%;' + 'height:auto;' + 'min-width:0!important;' + 'min-height:0!important;' + 'max-width:none!important;' + 'max-height:none!important;' + 'image-orientation:0deg!important;"'; el.innerHTML = ''; el.appendChild(img); }); }, resetPreview: function resetPreview() { forEach(this.previews, function (element) { var data = getData(element, DATA_PREVIEW); setStyle(element, { width: data.width, height: data.height }); element.innerHTML = data.html; removeData(element, DATA_PREVIEW); }); }, preview: function preview() { var imageData = this.imageData, canvasData = this.canvasData, cropBoxData = this.cropBoxData; var cropBoxWidth = cropBoxData.width, cropBoxHeight = cropBoxData.height; var width = imageData.width, height = imageData.height; var left = cropBoxData.left - canvasData.left - imageData.left; var top = cropBoxData.top - canvasData.top - imageData.top; if (!this.cropped || this.disabled) { return; } setStyle(this.viewBoxImage, assign({ width: width, height: height }, getTransforms(assign({ translateX: -left, translateY: -top }, imageData)))); forEach(this.previews, function (element) { var data = getData(element, DATA_PREVIEW); var originalWidth = data.width; var originalHeight = data.height; var newWidth = originalWidth; var newHeight = originalHeight; var ratio = 1; if (cropBoxWidth) { ratio = originalWidth / cropBoxWidth; newHeight = cropBoxHeight * ratio; } if (cropBoxHeight && newHeight > originalHeight) { ratio = originalHeight / cropBoxHeight; newWidth = cropBoxWidth * ratio; newHeight = originalHeight; } setStyle(element, { width: newWidth, height: newHeight }); setStyle(element.getElementsByTagName('img')[0], assign({ width: width * ratio, height: height * ratio }, getTransforms(assign({ translateX: -left * ratio, translateY: -top * ratio }, imageData)))); }); } }; var events = { bind: function bind() { var element = this.element, options = this.options, cropper = this.cropper; if (isFunction(options.cropstart)) { addListener(element, EVENT_CROP_START, options.cropstart); } if (isFunction(options.cropmove)) { addListener(element, EVENT_CROP_MOVE, options.cropmove); } if (isFunction(options.cropend)) { addListener(element, EVENT_CROP_END, options.cropend); } if (isFunction(options.crop)) { addListener(element, EVENT_CROP, options.crop); } if (isFunction(options.zoom)) { addListener(element, EVENT_ZOOM, options.zoom); } addListener(cropper, EVENT_POINTER_DOWN, this.onCropStart = this.cropStart.bind(this)); if (options.zoomable && options.zoomOnWheel) { addListener(cropper, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), { passive: false, capture: true }); } if (options.toggleDragModeOnDblclick) { addListener(cropper, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this)); } addListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove = this.cropMove.bind(this)); addListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd = this.cropEnd.bind(this)); if (options.responsive) { addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this)); } }, unbind: function unbind() { var element = this.element, options = this.options, cropper = this.cropper; if (isFunction(options.cropstart)) { removeListener(element, EVENT_CROP_START, options.cropstart); } if (isFunction(options.cropmove)) { removeListener(element, EVENT_CROP_MOVE, options.cropmove); } if (isFunction(options.cropend)) { removeListener(element, EVENT_CROP_END, options.cropend); } if (isFunction(options.crop)) { removeListener(element, EVENT_CROP, options.crop); } if (isFunction(options.zoom)) { removeListener(element, EVENT_ZOOM, options.zoom); } removeListener(cropper, EVENT_POINTER_DOWN, this.onCropStart); if (options.zoomable && options.zoomOnWheel) { removeListener(cropper, EVENT_WHEEL, this.onWheel, { passive: false, capture: true }); } if (options.toggleDragModeOnDblclick) { removeListener(cropper, EVENT_DBLCLICK, this.onDblclick); } removeListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove); removeListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd); if (options.responsive) { removeListener(window, EVENT_RESIZE, this.onResize); } } }; var handlers = { resize: function resize() { if (this.disabled) { return; } var options = this.options, container = this.container, containerData = this.containerData; var ratioX = container.offsetWidth / containerData.width; var ratioY = container.offsetHeight / containerData.height; var ratio = Math.abs(ratioX - 1) > Math.abs(ratioY - 1) ? ratioX : ratioY; // Resize when width changed or height changed if (ratio !== 1) { var canvasData; var cropBoxData; if (options.restore) { canvasData = this.getCanvasData(); cropBoxData = this.getCropBoxData(); } this.render(); if (options.restore) { this.setCanvasData(forEach(canvasData, function (n, i) { canvasData[i] = n * ratio; })); this.setCropBoxData(forEach(cropBoxData, function (n, i) { cropBoxData[i] = n * ratio; })); } } }, dblclick: function dblclick() { if (this.disabled || this.options.dragMode === DRAG_MODE_NONE) { return; } this.setDragMode(hasClass(this.dragBox, CLASS_CROP) ? DRAG_MODE_MOVE : DRAG_MODE_CROP); }, wheel: function wheel(event) { var _this = this; var ratio = Number(this.options.wheelZoomRatio) || 0.1; var delta = 1; if (this.disabled) { return; } event.preventDefault(); // Limit wheel speed to prevent zoom too fast (#21) if (this.wheeling) { return; } this.wheeling = true; setTimeout(function () { _this.wheeling = false; }, 50); if (event.deltaY) { delta = event.deltaY > 0 ? 1 : -1; } else if (event.wheelDelta) { delta = -event.wheelDelta / 120; } else if (event.detail) { delta = event.detail > 0 ? 1 : -1; } this.zoom(-delta * ratio, event); }, cropStart: function cropStart(event) { var buttons = event.buttons, button = event.button; if (this.disabled // Handle mouse event and pointer event and ignore touch event || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && ( // No primary button (Usually the left button) isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu || event.ctrlKey)) { return; } var options = this.options, pointers = this.pointers; var action; if (event.changedTouches) { // Handle touch event forEach(event.changedTouches, function (touch) { pointers[touch.identifier] = getPointer(touch); }); } else { // Handle mouse event and pointer event pointers[event.pointerId || 0] = getPointer(event); } if (Object.keys(pointers).length > 1 && options.zoomable && options.zoomOnTouch) { action = ACTION_ZOOM; } else { action = getData(event.target, DATA_ACTION); } if (!REGEXP_ACTIONS.test(action)) { return; } if (dispatchEvent(this.element, EVENT_CROP_START, { originalEvent: event, action: action }) === false) { return; } // This line is required for preventing page zooming in iOS browsers event.preventDefault(); this.action = action; this.cropping = false; if (action === ACTION_CROP) { this.cropping = true; addClass(this.dragBox, CLASS_MODAL); } }, cropMove: function cropMove(event) { var action = this.action; if (this.disabled || !action) { return; } var pointers = this.pointers; event.preventDefault(); if (dispatchEvent(this.element, EVENT_CROP_MOVE, { originalEvent: event, action: action }) === false) { return; } if (event.changedTouches) { forEach(event.changedTouches, function (touch) { // The first parameter should not be undefined (#432) assign(pointers[touch.identifier] || {}, getPointer(touch, true)); }); } else { assign(pointers[event.pointerId || 0] || {}, getPointer(event, true)); } this.change(event); }, cropEnd: function cropEnd(event) { if (this.disabled) { return; } var action = this.action, pointers = this.pointers; if (event.changedTouches) { forEach(event.changedTouches, function (touch) { delete pointers[touch.identifier]; }); } else { delete pointers[event.pointerId || 0]; } if (!action) { return; } event.preventDefault(); if (!Object.keys(pointers).length) { this.action = ''; } if (this.cropping) { this.cropping = false; toggleClass(this.dragBox, CLASS_MODAL, this.cropped && this.options.modal); } dispatchEvent(this.element, EVENT_CROP_END, { originalEvent: event, action: action }); } }; var change = { change: function change(event) { var options = this.options, canvasData = this.canvasData, containerData = this.containerData, cropBoxData = this.cropBoxData, pointers = this.pointers; var action = this.action; var aspectRatio = options.aspectRatio; var left = cropBoxData.left, top = cropBoxData.top, width = cropBoxData.width, height = cropBoxData.height; var right = left + width; var bottom = top + height; var minLeft = 0; var minTop = 0; var maxWidth = containerData.width; var maxHeight = containerData.height; var renderable = true; var offset; // Locking aspect ratio in "free mode" by holding shift key if (!aspectRatio && event.shiftKey) { aspectRatio = width && height ? width / height : 1; } if (this.limited) { minLeft = cropBoxData.minLeft; minTop = cropBoxData.minTop; maxWidth = minLeft + Math.min(containerData.width, canvasData.width, canvasData.left + canvasData.width); maxHeight = minTop + Math.min(containerData.height, canvasData.height, canvasData.top + canvasData.height); } var pointer = pointers[Object.keys(pointers)[0]]; var range = { x: pointer.endX - pointer.startX, y: pointer.endY - pointer.startY }; var check = function check(side) { switch (side) { case ACTION_EAST: if (right + range.x > maxWidth) { range.x = maxWidth - right; } break; case ACTION_WEST: if (left + range.x < minLeft) { range.x = minLeft - left; } break; case ACTION_NORTH: if (top + range.y < minTop) { range.y = minTop - top; } break; case ACTION_SOUTH: if (bottom + range.y > maxHeight) { range.y = maxHeight - bottom; } break; } }; switch (action) { // Move crop box case ACTION_ALL: left += range.x; top += range.y; break; // Resize crop box case ACTION_EAST: if (range.x >= 0 && (right >= maxWidth || aspectRatio && (top <= minTop || bottom >= maxHeight))) { renderable = false; break; } check(ACTION_EAST); width += range.x; if (width < 0) { action = ACTION_WEST; width = -width; left -= width; } if (aspectRatio) { height = width / aspectRatio; top += (cropBoxData.height - height) / 2; } break; case ACTION_NORTH: if (range.y <= 0 && (top <= minTop || aspectRatio && (left <= minLeft || right >= maxWidth))) { renderable = false; break; } check(ACTION_NORTH); height -= range.y; top += range.y; if (height < 0) { action = ACTION_SOUTH; height = -height; top -= height; } if (aspectRatio) { width = height * aspectRatio; left += (cropBoxData.width - width) / 2; } break; case ACTION_WEST: if (range.x <= 0 && (left <= minLeft || aspectRatio && (top <= minTop || bottom >= maxHeight))) { renderable = false; break; } check(ACTION_WEST); width -= range.x; left += range.x; if (width < 0) { action = ACTION_EAST; width = -width; left -= width; } if (aspectRatio) { height = width / aspectRatio; top += (cropBoxData.height - height) / 2; } break; case ACTION_SOUTH: if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && (left <= minLeft || right >= maxWidth))) { renderable = false; break; } check(ACTION_SOUTH); height += range.y; if (height < 0) { action = ACTION_NORTH; height = -height; top -= height; } if (aspectRatio) { width = height * aspectRatio; left += (cropBoxData.width - width) / 2; } break; case ACTION_NORTH_EAST: if (aspectRatio) { if (range.y <= 0 && (top <= minTop || right >= maxWidth)) { renderable = false; break; } check(ACTION_NORTH); height -= range.y; top += range.y; width = height * aspectRatio; } else { check(ACTION_NORTH); check(ACTION_EAST); if (range.x >= 0) { if (right < maxWidth) { width += range.x; } else if (range.y <= 0 && top <= minTop) { renderable = false; } } else { width += range.x; } if (range.y <= 0) { if (top > minTop) { height -= range.y; top += range.y; } } else { height -= range.y; top += range.y; } } if (width < 0 && height < 0) { action = ACTION_SOUTH_WEST; height = -height; width = -width; top -= height; left -= width; } else if (width < 0) { action = ACTION_NORTH_WEST; width = -width; left -= width; } else if (height < 0) { action = ACTION_SOUTH_EAST; height = -height; top -= height; } break; case ACTION_NORTH_WEST: if (aspectRatio) { if (range.y <= 0 && (top <= minTop || left <= minLeft)) { renderable = false; break; } check(ACTION_NORTH); height -= range.y; top += range.y; width = height * aspectRatio; left += cropBoxData.width - width; } else { check(ACTION_NORTH); check(ACTION_WEST); if (range.x <= 0) { if (left > minLeft) { width -= range.x; left += range.x; } else if (range.y <= 0 && top <= minTop) { renderable = false; } } else { width -= range.x; left += range.x; } if (range.y <= 0) { if (top > minTop) { height -= range.y; top += range.y; } } else { height -= range.y; top += range.y; } } if (width < 0 && height < 0) { action = ACTION_SOUTH_EAST; height = -height; width = -width; top -= height; left -= width; } else if (width < 0) { action = ACTION_NORTH_EAST; width = -width; left -= width; } else if (height < 0) { action = ACTION_SOUTH_WEST; height = -height; top -= height; } break; case ACTION_SOUTH_WEST: if (aspectRatio) { if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) { renderable = false; break; } check(ACTION_WEST); width -= range.x; left += range.x; height = width / aspectRatio; } else { check(ACTION_SOUTH); check(ACTION_WEST); if (range.x <= 0) { if (left > minLeft) { width -= range.x; left += range.x; } else if (range.y >= 0 && bottom >= maxHeight) { renderable = false; } } else { width -= range.x; left += range.x; } if (range.y >= 0) { if (bottom < maxHeight) { height += range.y; } } else { height += range.y; } } if (width < 0 && height < 0) { action = ACTION_NORTH_EAST; height = -height; width = -width; top -= height; left -= width; } else if (width < 0) { action = ACTION_SOUTH_EAST; width = -width; left -= width; } else if (height < 0) { action = ACTION_NORTH_WEST; height = -height; top -= height; } break; case ACTION_SOUTH_EAST: if (aspectRatio) { if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) { renderable = false; break; } check(ACTION_EAST); width += range.x; height = width / aspectRatio; } else { check(ACTION_SOUTH); check(ACTION_EAST); if (range.x >= 0) { if (right < maxWidth) { width += range.x; } else if (range.y >= 0 && bottom >= maxHeight) { renderable = false; } } else { width += range.x; } if (range.y >= 0) { if (bottom < maxHeight) { height += range.y; } } else { height += range.y; } } if (width < 0 && height < 0) { action = ACTION_NORTH_WEST; height = -height; width = -width; top -= height; left -= width; } else if (width < 0) { action = ACTION_SOUTH_WEST; width = -width; left -= width; } else if (height < 0) { action = ACTION_NORTH_EAST; height = -height; top -= height; } break; // Move canvas case ACTION_MOVE: this.move(range.x, range.y); renderable = false; break; // Zoom canvas case ACTION_ZOOM: this.zoom(getMaxZoomRatio(pointers), event); renderable = false; break; // Create crop box case ACTION_CROP: if (!range.x || !range.y) { renderable = false; break; } offset = getOffset(this.cropper); left = pointer.startX - offset.left; top = pointer.startY - offset.top; width = cropBoxData.minWidth; height = cropBoxData.minHeight; if (range.x > 0) { action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST; } else if (range.x < 0) { left -= width; action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST; } if (range.y < 0) { top -= height; } // Show the crop box if is hidden if (!this.cropped) { removeClass(this.cropBox, CLASS_HIDDEN); this.cropped = true; if (this.limited) { this.limitCropBox(true, true); } } break; } if (renderable) { cropBoxData.width = width; cropBoxData.height = height; cropBoxData.left = left; cropBoxData.top = top; this.action = action; this.renderCropBox(); } // Override forEach(pointers, function (p) { p.startX = p.endX; p.startY = p.endY; }); } }; var methods = { // Show the crop box manually crop: function crop() { if (this.ready && !this.cropped && !this.disabled) { this.cropped = true; this.limitCropBox(true, true); if (this.options.modal) { addClass(this.dragBox, CLASS_MODAL); } removeClass(this.cropBox, CLASS_HIDDEN); this.setCropBoxData(this.initialCropBoxData); } return this; }, // Reset the image and crop box to their initial states reset: function reset() { if (this.ready && !this.disabled) { this.imageData = assign({}, this.initialImageData); this.canvasData = assign({}, this.initialCanvasData); this.cropBoxData = assign({}, this.initialCropBoxData); this.renderCanvas(); if (this.cropped) { this.renderCropBox(); } } return this; }, // Clear the crop box clear: function clear() { if (this.cropped && !this.disabled) { assign(this.cropBoxData, { left: 0, top: 0, width: 0, height: 0 }); this.cropped = false; this.renderCropBox(); this.limitCanvas(true, true); // Render canvas after crop box rendered this.renderCanvas(); removeClass(this.dragBox, CLASS_MODAL); addClass(this.cropBox, CLASS_HIDDEN); } return this; }, /** * Replace the image's src and rebuild the cropper * @param {string} url - The new URL. * @param {boolean} [hasSameSize] - Indicate if the new image has the same size as the old one. * @returns {Cropper} this */ replace: function replace(url) { var hasSameSize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (!this.disabled && url) { if (this.isImg) { this.element.src = url; } if (hasSameSize) { this.url = url; this.image.src = url; if (this.ready) { this.viewBoxImage.src = url; forEach(this.previews, function (element) { element.getElementsByTagName('img')[0].src = url; }); } } else { if (this.isImg) { this.replaced = true; } this.options.data = null; this.uncreate(); this.load(url); } } return this; }, // Enable (unfreeze) the cropper enable: function enable() { if (this.ready && this.disabled) { this.disabled = false; removeClass(this.cropper, CLASS_DISABLED); } return this; }, // Disable (freeze) the cropper disable: function disable() { if (this.ready && !this.disabled) { this.disabled = true; addClass(this.cropper, CLASS_DISABLED); } return this; }, /** * Destroy the cropper and remove the instance from the image * @returns {Cropper} this */ destroy: function destroy() { var element = this.element; if (!element[NAMESPACE]) { return this; } element[NAMESPACE] = undefined; if (this.isImg && this.replaced) { element.src = this.originalUrl; } this.uncreate(); return this; }, /** * Move the canvas with relative offsets * @param {number} offsetX - The relative offset distance on the x-axis. * @param {number} [offsetY=offsetX] - The relative offset distance on the y-axis. * @returns {Cropper} this */ move: function move(offsetX) { var offsetY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : offsetX; var _this$canvasData = this.canvasData, left = _this$canvasData.left, top = _this$canvasData.top; return this.moveTo(isUndefined(offsetX) ? offsetX : left + Number(offsetX), isUndefined(offsetY) ? offsetY : top + Number(offsetY)); }, /** * Move the canvas to an absolute point * @param {number} x - The x-axis coordinate. * @param {number} [y=x] - The y-axis coordinate. * @returns {Cropper} this */ moveTo: function moveTo(x) { var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x; var canvasData = this.canvasData; var changed = false; x = Number(x); y = Number(y); if (this.ready && !this.disabled && this.options.movable) { if (isNumber(x)) { canvasData.left = x; changed = true; } if (isNumber(y)) { canvasData.top = y; changed = true; } if (changed) { this.renderCanvas(true); } } return this; }, /** * Zoom the canvas with a relative ratio * @param {number} ratio - The target ratio. * @param {Event} _originalEvent - The original event if any. * @returns {Cropper} this */ zoom: function zoom(ratio, _originalEvent) { var canvasData = this.canvasData; ratio = Number(ratio); if (ratio < 0) { ratio = 1 / (1 - ratio); } else { ratio = 1 + ratio; } return this.zoomTo(canvasData.width * ratio / canvasData.naturalWidth, null, _originalEvent); }, /** * Zoom the canvas to an absolute ratio * @param {number} ratio - The target ratio. * @param {Object} pivot - The zoom pivot point coordinate. * @param {Event} _originalEvent - The original event if any. * @returns {Cropper} this */ zoomTo: function zoomTo(ratio, pivot, _originalEvent) { var options = this.options, canvasData = this.canvasData; var width = canvasData.width, height = canvasData.height, naturalWidth = canvasData.naturalWidth, naturalHeight = canvasData.naturalHeight; ratio = Number(ratio); if (ratio >= 0 && this.ready && !this.disabled && options.zoomable) { var newWidth = naturalWidth * ratio; var newHeight = naturalHeight * ratio; if (dispatchEvent(this.element, EVENT_ZOOM, { ratio: ratio, oldRatio: width / naturalWidth, originalEvent: _originalEvent }) === false) { return this; } if (_originalEvent) { var pointers = this.pointers; var offset = getOffset(this.cropper); var center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : { pageX: _originalEvent.pageX, pageY: _originalEvent.pageY }; // Zoom from the triggering point of the event canvasData.left -= (newWidth - width) * ((center.pageX - offset.left - canvasData.left) / width); canvasData.top -= (newHeight - height) * ((center.pageY - offset.top - canvasData.top) / height); } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) { canvasData.left -= (newWidth - width) * ((pivot.x - canvasData.left) / width); canvasData.top -= (newHeight - height) * ((pivot.y - canvasData.top) / height); } else { // Zoom from the center of the canvas canvasData.left -= (newWidth - width) / 2; canvasData.top -= (newHeight - height) / 2; } canvasData.width = newWidth; canvasData.height = newHeight; this.renderCanvas(true); } return this; }, /** * Rotate the canvas with a relative degree * @param {number} degree - The rotate degree. * @returns {Cropper} this */ rotate: function rotate(degree) { return this.rotateTo((this.imageData.rotate || 0) + Number(degree)); }, /** * Rotate the canvas to an absolute degree * @param {number} degree - The rotate degree. * @returns {Cropper} this */ rotateTo: function rotateTo(degree) { degree = Number(degree); if (isNumber(degree) && this.ready && !this.disabled && this.options.rotatable) { this.imageData.rotate = degree % 360; this.renderCanvas(true, true); } return this; }, /** * Scale the image on the x-axis. * @param {number} scaleX - The scale ratio on the x-axis. * @returns {Cropper} this */ scaleX: function scaleX(_scaleX) { var scaleY = this.imageData.scaleY; return this.scale(_scaleX, isNumber(scaleY) ? scaleY : 1); }, /** * Scale the image on the y-axis. * @param {number} scaleY - The scale ratio on the y-axis. * @returns {Cropper} this */ scaleY: function scaleY(_scaleY) { var scaleX = this.imageData.scaleX; return this.scale(isNumber(scaleX) ? scaleX : 1, _scaleY); }, /** * Scale the image * @param {number} scaleX - The scale ratio on the x-axis. * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis. * @returns {Cropper} this */ scale: function scale(scaleX) { var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX; var imageData = this.imageData; var transformed = false; scaleX = Number(scaleX); scaleY = Number(scaleY); if (this.ready && !this.disabled && this.options.scalable) { if (isNumber(scaleX)) { imageData.scaleX = scaleX; transformed = true; } if (isNumber(scaleY)) { imageData.scaleY = scaleY; transformed = true; } if (transformed) { this.renderCanvas(true, true); } } return this; }, /** * Get the cropped area position and size data (base on the original image) * @param {boolean} [rounded=false] - Indicate if round the data values or not. * @returns {Object} The result cropped data. */ getData: function getData() { var rounded = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var options = this.options, imageData = this.imageData, canvasData = this.canvasData, cropBoxData = this.cropBoxData; var data; if (this.ready && this.cropped) { data = { x: cropBoxData.left - canvasData.left, y: cropBoxData.top - canvasData.top, width: cropBoxData.width, height: cropBoxData.height }; var ratio = imageData.width / imageData.naturalWidth; forEach(data, function (n, i) { data[i] = n / ratio; }); if (rounded) { // In case rounding off leads to extra 1px in right or bottom border // we should round the top-left corner and the dimension (#343). var bottom = Math.round(data.y + data.height); var right = Math.round(data.x + data.width); data.x = Math.round(data.x); data.y = Math.round(data.y); data.width = right - data.x; data.height = bottom - data.y; } } else { data = { x: 0, y: 0, width: 0, height: 0 }; } if (options.rotatable) { data.rotate = imageData.rotate || 0; } if (options.scalable) { data.scaleX = imageData.scaleX || 1; data.scaleY = imageData.scaleY || 1; } return data; }, /** * Set the cropped area position and size with new data * @param {Object} data - The new data. * @returns {Cropper} this */ setData: function setData(data) { var options = this.options, imageData = this.imageData, canvasData = this.canvasData; var cropBoxData = {}; if (this.ready && !this.disabled && isPlainObject(data)) { var transformed = false; if (options.rotatable) { if (isNumber(data.rotate) && data.rotate !== imageData.rotate) { imageData.rotate = data.rotate; transformed = true; } } if (options.scalable) { if (isNumber(data.scaleX) && data.scaleX !== imageData.scaleX) { imageData.scaleX = data.scaleX; transformed = true; } if (isNumber(data.scaleY) && data.scaleY !== imageData.scaleY) { imageData.scaleY = data.scaleY; transformed = true; } } if (transformed) { this.renderCanvas(true, true); } var ratio = imageData.width / imageData.naturalWidth; if (isNumber(data.x)) { cropBoxData.left = data.x * ratio + canvasData.left; } if (isNumber(data.y)) { cropBoxData.top = data.y * ratio + canvasData.top; } if (isNumber(data.width)) { cropBoxData.width = data.width * ratio; } if (isNumber(data.height)) { cropBoxData.height = data.height * ratio; } this.setCropBoxData(cropBoxData); } return this; }, /** * Get the container size data. * @returns {Object} The result container data. */ getContainerData: function getContainerData() { return this.ready ? assign({}, this.containerData) : {}; }, /** * Get the image position and size data. * @returns {Object} The result image data. */ getImageData: function getImageData() { return this.sized ? assign({}, this.imageData) : {}; }, /** * Get the canvas position and size data. * @returns {Object} The result canvas data. */ getCanvasData: function getCanvasData() { var canvasData = this.canvasData; var data = {}; if (this.ready) { forEach(['left', 'top', 'width', 'height', 'naturalWidth', 'naturalHeight'], function (n) { data[n] = canvasData[n]; }); } return data; }, /** * Set the canvas position and size with new data. * @param {Object} data - The new canvas data. * @returns {Cropper} this */ setCanvasData: function setCanvasData(data) { var canvasData = this.canvasData; var aspectRatio = canvasData.aspectRatio; if (this.ready && !this.disabled && isPlainObject(data)) { if (isNumber(data.left)) { canvasData.left = data.left; } if (isNumber(data.top)) { canvasData.top = data.top; } if (isNumber(data.width)) { canvasData.width = data.width; canvasData.height = data.width / aspectRatio; } else if (isNumber(data.height)) { canvasData.height = data.height; canvasData.width = data.height * aspectRatio; } this.renderCanvas(true); } return this; }, /** * Get the crop box position and size data. * @returns {Object} The result crop box data. */ getCropBoxData: function getCropBoxData() { var cropBoxData = this.cropBoxData; var data; if (this.ready && this.cropped) { data = { left: cropBoxData.left, top: cropBoxData.top, width: cropBoxData.width, height: cropBoxData.height }; } return data || {}; }, /** * Set the crop box position and size with new data. * @param {Object} data - The new crop box data. * @returns {Cropper} this */ setCropBoxData: function setCropBoxData(data) { var cropBoxData = this.cropBoxData; var aspectRatio = this.options.aspectRatio; var widthChanged; var heightChanged; if (this.ready && this.cropped && !this.disabled && isPlainObject(data)) { if (isNumber(data.left)) { cropBoxData.left = data.left; } if (isNumber(data.top)) { cropBoxData.top = data.top; } if (isNumber(data.width) && data.width !== cropBoxData.width) { widthChanged = true; cropBoxData.width = data.width; } if (isNumber(data.height) && data.height !== cropBoxData.height) { heightChanged = true; cropBoxData.height = data.height; } if (aspectRatio) { if (widthChanged) { cropBoxData.height = cropBoxData.width / aspectRatio; } else if (heightChanged) { cropBoxData.width = cropBoxData.height * aspectRatio; } } this.renderCropBox(); } return this; }, /** * Get a canvas drawn the cropped image. * @param {Object} [options={}] - The config options. * @returns {HTMLCanvasElement} - The result canvas. */ getCroppedCanvas: function getCroppedCanvas() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (!this.ready || !window.HTMLCanvasElement) { return null; } var canvasData = this.canvasData; var source = getSourceCanvas(this.image, this.imageData, canvasData, options); // Returns the source canvas if it is not cropped. if (!this.cropped) { return source; } var _this$getData = this.getData(), initialX = _this$getData.x, initialY = _this$getData.y, initialWidth = _this$getData.width, initialHeight = _this$getData.height; var ratio = source.width / Math.floor(canvasData.naturalWidth); if (ratio !== 1) { initialX *= ratio; initialY *= ratio; initialWidth *= ratio; initialHeight *= ratio; } var aspectRatio = initialWidth / initialHeight; var maxSizes = getAdjustedSizes({ aspectRatio: aspectRatio, width: options.maxWidth || Infinity, height: options.maxHeight || Infinity }); var minSizes = getAdjustedSizes({ aspectRatio: aspectRatio, width: options.minWidth || 0, height: options.minHeight || 0 }, 'cover'); var _getAdjustedSizes = getAdjustedSizes({ aspectRatio: aspectRatio, width: options.width || (ratio !== 1 ? source.width : initialWidth), height: options.height || (ratio !== 1 ? source.height : initialHeight) }), width = _getAdjustedSizes.width, height = _getAdjustedSizes.height; width = Math.min(maxSizes.width, Math.max(minSizes.width, width)); height = Math.min(maxSizes.height, Math.max(minSizes.height, height)); var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); canvas.width = normalizeDecimalNumber(width); canvas.height = normalizeDecimalNumber(height); context.fillStyle = options.fillColor || 'transparent'; context.fillRect(0, 0, width, height); var _options$imageSmoothi = options.imageSmoothingEnabled, imageSmoothingEnabled = _options$imageSmoothi === void 0 ? true : _options$imageSmoothi, imageSmoothingQuality = options.imageSmoothingQuality; context.imageSmoothingEnabled = imageSmoothingEnabled; if (imageSmoothingQuality) { context.imageSmoothingQuality = imageSmoothingQuality; } // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage var sourceWidth = source.width; var sourceHeight = source.height; // Source canvas parameters var srcX = initialX; var srcY = initialY; var srcWidth; var srcHeight; // Destination canvas parameters var dstX; var dstY; var dstWidth; var dstHeight; if (srcX <= -initialWidth || srcX > sourceWidth) { srcX = 0; srcWidth = 0; dstX = 0; dstWidth = 0; } else if (srcX <= 0) { dstX = -srcX; srcX = 0; srcWidth = Math.min(sourceWidth, initialWidth + srcX); dstWidth = srcWidth; } else if (srcX <= sourceWidth) { dstX = 0; srcWidth = Math.min(initialWidth, sourceWidth - srcX); dstWidth = srcWidth; } if (srcWidth <= 0 || srcY <= -initialHeight || srcY > sourceHeight) { srcY = 0; srcHeight = 0; dstY = 0; dstHeight = 0; } else if (srcY <= 0) { dstY = -srcY; srcY = 0; srcHeight = Math.min(sourceHeight, initialHeight + srcY); dstHeight = srcHeight; } else if (srcY <= sourceHeight) { dstY = 0; srcHeight = Math.min(initialHeight, sourceHeight - srcY); dstHeight = srcHeight; } var params = [srcX, srcY, srcWidth, srcHeight]; // Avoid "IndexSizeError" if (dstWidth > 0 && dstHeight > 0) { var scale = width / initialWidth; params.push(dstX * scale, dstY * scale, dstWidth * scale, dstHeight * scale); } // All the numerical parameters should be integer for `drawImage` // https://github.com/fengyuanchen/cropper/issues/476 context.drawImage.apply(context, [source].concat(_toConsumableArray(params.map(function (param) { return Math.floor(normalizeDecimalNumber(param)); })))); return canvas; }, /** * Change the aspect ratio of the crop box. * @param {number} aspectRatio - The new aspect ratio. * @returns {Cropper} this */ setAspectRatio: function setAspectRatio(aspectRatio) { var options = this.options; if (!this.disabled && !isUndefined(aspectRatio)) { // 0 -> NaN options.aspectRatio = Math.max(0, aspectRatio) || NaN; if (this.ready) { this.initCropBox(); if (this.cropped) { this.renderCropBox(); } } } return this; }, /** * Change the drag mode. * @param {string} mode - The new drag mode. * @returns {Cropper} this */ setDragMode: function setDragMode(mode) { var options = this.options, dragBox = this.dragBox, face = this.face; if (this.ready && !this.disabled) { var croppable = mode === DRAG_MODE_CROP; var movable = options.movable && mode === DRAG_MODE_MOVE; mode = croppable || movable ? mode : DRAG_MODE_NONE; options.dragMode = mode; setData(dragBox, DATA_ACTION, mode); toggleClass(dragBox, CLASS_CROP, croppable); toggleClass(dragBox, CLASS_MOVE, movable); if (!options.cropBoxMovable) { // Sync drag mode to crop box when it is not movable setData(face, DATA_ACTION, mode); toggleClass(face, CLASS_CROP, croppable); toggleClass(face, CLASS_MOVE, movable); } } return this; } }; var AnotherCropper = WINDOW.Cropper; var Cropper = /*#__PURE__*/function () { /** * Create a new Cropper. * @param {Element} element - The target element for cropping. * @param {Object} [options={}] - The configuration options. */ function Cropper(element) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, Cropper); if (!element || !REGEXP_TAG_NAME.test(element.tagName)) { throw new Error('The first argument is required and must be an or element.'); } this.element = element; this.options = assign({}, DEFAULTS, isPlainObject(options) && options); this.cropped = false; this.disabled = false; this.pointers = {}; this.ready = false; this.reloading = false; this.replaced = false; this.sized = false; this.sizing = false; this.init(); } _createClass(Cropper, [{ key: "init", value: function init() { var element = this.element; var tagName = element.tagName.toLowerCase(); var url; if (element[NAMESPACE]) { return; } element[NAMESPACE] = this; if (tagName === 'img') { this.isImg = true; // e.g.: "img/picture.jpg" url = element.getAttribute('src') || ''; this.originalUrl = url; // Stop when it's a blank image if (!url) { return; } // e.g.: "https://example.com/img/picture.jpg" url = element.src; } else if (tagName === 'canvas' && window.HTMLCanvasElement) { url = element.toDataURL(); } this.load(url); } }, { key: "load", value: function load(url) { var _this = this; if (!url) { return; } this.url = url; this.imageData = {}; var element = this.element, options = this.options; if (!options.rotatable && !options.scalable) { options.checkOrientation = false; } // Only IE10+ supports Typed Arrays if (!options.checkOrientation || !window.ArrayBuffer) { this.clone(); return; } // Detect the mime type of the image directly if it is a Data URL if (REGEXP_DATA_URL.test(url)) { // Read ArrayBuffer from Data URL of JPEG images directly for better performance if (REGEXP_DATA_URL_JPEG.test(url)) { this.read(dataURLToArrayBuffer(url)); } else { // Only a JPEG image may contains Exif Orientation information, // the rest types of Data URLs are not necessary to check orientation at all. this.clone(); } return; } // 1. Detect the mime type of the image by a XMLHttpRequest. // 2. Load the image as ArrayBuffer for reading orientation if its a JPEG image. var xhr = new XMLHttpRequest(); var clone = this.clone.bind(this); this.reloading = true; this.xhr = xhr; // 1. Cross origin requests are only supported for protocol schemes: // http, https, data, chrome, chrome-extension. // 2. Access to XMLHttpRequest from a Data URL will be blocked by CORS policy // in some browsers as IE11 and Safari. xhr.onabort = clone; xhr.onerror = clone; xhr.ontimeout = clone; xhr.onprogress = function () { // Abort the request directly if it not a JPEG image for better performance if (xhr.getResponseHeader('content-type') !== MIME_TYPE_JPEG) { xhr.abort(); } }; xhr.onload = function () { _this.read(xhr.response); }; xhr.onloadend = function () { _this.reloading = false; _this.xhr = null; }; // Bust cache when there is a "crossOrigin" property to avoid browser cache error if (options.checkCrossOrigin && isCrossOriginURL(url) && element.crossOrigin) { url = addTimestamp(url); } // The third parameter is required for avoiding side-effect (#682) xhr.open('GET', url, true); xhr.responseType = 'arraybuffer'; xhr.withCredentials = element.crossOrigin === 'use-credentials'; xhr.send(); } }, { key: "read", value: function read(arrayBuffer) { var options = this.options, imageData = this.imageData; // Reset the orientation value to its default value 1 // as some iOS browsers will render image with its orientation var orientation = resetAndGetOrientation(arrayBuffer); var rotate = 0; var scaleX = 1; var scaleY = 1; if (orientation > 1) { // Generate a new URL which has the default orientation value this.url = arrayBufferToDataURL(arrayBuffer, MIME_TYPE_JPEG); var _parseOrientation = parseOrientation(orientation); rotate = _parseOrientation.rotate; scaleX = _parseOrientation.scaleX; scaleY = _parseOrientation.scaleY; } if (options.rotatable) { imageData.rotate = rotate; } if (options.scalable) { imageData.scaleX = scaleX; imageData.scaleY = scaleY; } this.clone(); } }, { key: "clone", value: function clone() { var element = this.element, url = this.url; var crossOrigin = element.crossOrigin; var crossOriginUrl = url; if (this.options.checkCrossOrigin && isCrossOriginURL(url)) { if (!crossOrigin) { crossOrigin = 'anonymous'; } // Bust cache when there is not a "crossOrigin" property (#519) crossOriginUrl = addTimestamp(url); } this.crossOrigin = crossOrigin; this.crossOriginUrl = crossOriginUrl; var image = document.createElement('img'); if (crossOrigin) { image.crossOrigin = crossOrigin; } image.src = crossOriginUrl || url; image.alt = element.alt || 'The image to crop'; this.image = image; image.onload = this.start.bind(this); image.onerror = this.stop.bind(this); addClass(image, CLASS_HIDE); element.parentNode.insertBefore(image, element.nextSibling); } }, { key: "start", value: function start() { var _this2 = this; var image = this.image; image.onload = null; image.onerror = null; this.sizing = true; // Match all browsers that use WebKit as the layout engine in iOS devices, // such as Safari for iOS, Chrome for iOS, and in-app browsers. var isIOSWebKit = WINDOW.navigator && /(?:iPad|iPhone|iPod).*?AppleWebKit/i.test(WINDOW.navigator.userAgent); var done = function done(naturalWidth, naturalHeight) { assign(_this2.imageData, { naturalWidth: naturalWidth, naturalHeight: naturalHeight, aspectRatio: naturalWidth / naturalHeight }); _this2.initialImageData = assign({}, _this2.imageData); _this2.sizing = false; _this2.sized = true; _this2.build(); }; // Most modern browsers (excepts iOS WebKit) if (image.naturalWidth && !isIOSWebKit) { done(image.naturalWidth, image.naturalHeight); return; } var sizingImage = document.createElement('img'); var body = document.body || document.documentElement; this.sizingImage = sizingImage; sizingImage.onload = function () { done(sizingImage.width, sizingImage.height); if (!isIOSWebKit) { body.removeChild(sizingImage); } }; sizingImage.src = image.src; // iOS WebKit will convert the image automatically // with its orientation once append it into DOM (#279) if (!isIOSWebKit) { sizingImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;'; body.appendChild(sizingImage); } } }, { key: "stop", value: function stop() { var image = this.image; image.onload = null; image.onerror = null; image.parentNode.removeChild(image); this.image = null; } }, { key: "build", value: function build() { if (!this.sized || this.ready) { return; } var element = this.element, options = this.options, image = this.image; // Create cropper elements var container = element.parentNode; var template = document.createElement('div'); template.innerHTML = TEMPLATE; var cropper = template.querySelector(".".concat(NAMESPACE, "-container")); var canvas = cropper.querySelector(".".concat(NAMESPACE, "-canvas")); var dragBox = cropper.querySelector(".".concat(NAMESPACE, "-drag-box")); var cropBox = cropper.querySelector(".".concat(NAMESPACE, "-crop-box")); var face = cropBox.querySelector(".".concat(NAMESPACE, "-face")); this.container = container; this.cropper = cropper; this.canvas = canvas; this.dragBox = dragBox; this.cropBox = cropBox; this.viewBox = cropper.querySelector(".".concat(NAMESPACE, "-view-box")); this.face = face; canvas.appendChild(image); // Hide the original image addClass(element, CLASS_HIDDEN); // Inserts the cropper after to the current image container.insertBefore(cropper, element.nextSibling); // Show the image if is hidden if (!this.isImg) { removeClass(image, CLASS_HIDE); } this.initPreview(); this.bind(); options.initialAspectRatio = Math.max(0, options.initialAspectRatio) || NaN; options.aspectRatio = Math.max(0, options.aspectRatio) || NaN; options.viewMode = Math.max(0, Math.min(3, Math.round(options.viewMode))) || 0; addClass(cropBox, CLASS_HIDDEN); if (!options.guides) { addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-dashed")), CLASS_HIDDEN); } if (!options.center) { addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-center")), CLASS_HIDDEN); } if (options.background) { addClass(cropper, "".concat(NAMESPACE, "-bg")); } if (!options.highlight) { addClass(face, CLASS_INVISIBLE); } if (options.cropBoxMovable) { addClass(face, CLASS_MOVE); setData(face, DATA_ACTION, ACTION_ALL); } if (!options.cropBoxResizable) { addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-line")), CLASS_HIDDEN); addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-point")), CLASS_HIDDEN); } this.render(); this.ready = true; this.setDragMode(options.dragMode); if (options.autoCrop) { this.crop(); } this.setData(options.data); if (isFunction(options.ready)) { addListener(element, EVENT_READY, options.ready, { once: true }); } dispatchEvent(element, EVENT_READY); } }, { key: "unbuild", value: function unbuild() { if (!this.ready) { return; } this.ready = false; this.unbind(); this.resetPreview(); this.cropper.parentNode.removeChild(this.cropper); removeClass(this.element, CLASS_HIDDEN); } }, { key: "uncreate", value: function uncreate() { if (this.ready) { this.unbuild(); this.ready = false; this.cropped = false; } else if (this.sizing) { this.sizingImage.onload = null; this.sizing = false; this.sized = false; } else if (this.reloading) { this.xhr.onabort = null; this.xhr.abort(); } else if (this.image) { this.stop(); } } /** * Get the no conflict cropper class. * @returns {Cropper} The cropper class. */ }], [{ key: "noConflict", value: function noConflict() { window.Cropper = AnotherCropper; return Cropper; } /** * Change the default options. * @param {Object} options - The new default options. */ }, { key: "setDefaults", value: function setDefaults(options) { assign(DEFAULTS, isPlainObject(options) && options); } }]); return Cropper; }(); assign(Cropper.prototype, render, preview, events, handlers, change, methods); return Cropper; }))); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/cxselect/jquery.cxselect.js ================================================ /*! * jQuery cxSelect * @name jquery.cxselect.js * @version 1.4.2 * @date 2017-09-26 * @author ciaoca * @email ciaoca@gmail.com * @site https://github.com/ciaoca/cxSelect * @license Released under the MIT license */ (function(factory) { if (typeof define === 'function' && define.amd) { define(['jquery'], factory); } else { factory(window.jQuery || window.Zepto || window.$); }; }(function($) { var cxSelect = function() { var self = this; var dom, settings, callback; // 分配参数 for (var i = 0, l = arguments.length; i < l; i++) { if (cxSelect.isJquery(arguments[i]) || cxSelect.isZepto(arguments[i])) { dom = arguments[i]; } else if (cxSelect.isElement(arguments[i])) { dom = $(arguments[i]); } else if (typeof arguments[i] === 'function') { callback = arguments[i]; } else if (typeof arguments[i] === 'object') { settings = arguments[i]; }; }; var api = new cxSelect.init(dom, settings); if (typeof callback === 'function') { callback(api); }; return api; }; cxSelect.isElement = function(o){ if (o && (typeof HTMLElement === 'function' || typeof HTMLElement === 'object') && o instanceof HTMLElement) { return true; } else { return (o && o.nodeType && o.nodeType === 1) ? true : false; }; }; cxSelect.isJquery = function(o){ return (o && o.length && (typeof jQuery === 'function' || typeof jQuery === 'object') && o instanceof jQuery) ? true : false; }; cxSelect.isZepto = function(o){ return (o && o.length && (typeof Zepto === 'function' || typeof Zepto === 'object') && Zepto.zepto.isZ(o)) ? true : false; }; cxSelect.getIndex = function(n, required) { return required ? n : n - 1; }; cxSelect.getData = function(data, space) { if (typeof space === 'string' && space.length) { space = space.split('.'); for (var i = 0, l = space.length; i < l; i++) { data = data[space[i]]; }; }; return data; }; cxSelect.init = function(dom, settings) { var self = this; if (!cxSelect.isJquery(dom) && !cxSelect.isZepto(dom)) {return}; var theSelect = { dom: { box: dom } }; self.attach = cxSelect.attach.bind(theSelect); self.detach = cxSelect.detach.bind(theSelect); self.setOptions = cxSelect.setOptions.bind(theSelect); self.clear = cxSelect.clear.bind(theSelect); theSelect.changeEvent = function() { cxSelect.selectChange.call(theSelect, this.className); }; theSelect.settings = $.extend({}, $.cxSelect.defaults, settings, { url: theSelect.dom.box.data('url'), emptyStyle: theSelect.dom.box.data('emptyStyle'), required: theSelect.dom.box.data('required'), firstTitle: theSelect.dom.box.data('firstTitle'), firstValue: theSelect.dom.box.data('firstValue'), jsonSpace: theSelect.dom.box.data('jsonSpace'), jsonName: theSelect.dom.box.data('jsonName'), jsonValue: theSelect.dom.box.data('jsonValue'), jsonSub: theSelect.dom.box.data('jsonSub') }); var _dataSelects = theSelect.dom.box.data('selects'); if (typeof _dataSelects === 'string' && _dataSelects.length) { theSelect.settings.selects = _dataSelects.split(','); }; self.setOptions(); self.attach(); // 使用独立接口获取数据 if (!theSelect.settings.url && !theSelect.settings.data) { cxSelect.start.apply(theSelect); // 设置自定义数据 } else if ($.isArray(theSelect.settings.data)) { cxSelect.start.call(theSelect, theSelect.settings.data); // 设置 URL,通过 Ajax 获取数据 } else if (typeof theSelect.settings.url === 'string' && theSelect.settings.url.length) { $.getJSON(theSelect.settings.url, function(json) { cxSelect.start.call(theSelect, json); }); }; }; // 设置参数 cxSelect.setOptions = function(opts) { var self = this; if (opts) { $.extend(self.settings, opts); }; // 初次或重设选择器组 if (!$.isArray(self.selectArray) || !self.selectArray.length || (opts && opts.selects)) { self.selectArray = []; if ($.isArray(self.settings.selects) && self.settings.selects.length) { var _tempSelect; for (var i = 0, l = self.settings.selects.length; i < l; i++) { _tempSelect = self.dom.box.find('select.' + self.settings.selects[i]); if (!_tempSelect || !_tempSelect.length) {break}; self.selectArray.push(_tempSelect); }; }; }; if (opts) { if (!$.isArray(opts.data) && typeof opts.url === 'string' && opts.url.length) { $.getJSON(self.settings.url, function(json) { cxSelect.start.call(self, json); }); } else { cxSelect.start.call(self, opts.data); }; }; }; // 绑定 cxSelect.attach = function() { var self = this; if (!self.attachStatus) { self.dom.box.on('change', 'select', self.changeEvent); }; if (typeof self.attachStatus === 'boolean') { cxSelect.start.call(self); }; self.attachStatus = true; }; // 移除绑定 cxSelect.detach = function() { var self = this; self.dom.box.off('change', 'select', self.changeEvent); self.attachStatus = false; }; // 清空选项 cxSelect.clear = function(index) { var self = this; var _style = { display: '', visibility: '' }; index = isNaN(index) ? 0 : index; // 清空后面的 select for (var i = index, l = self.selectArray.length; i < l; i++) { self.selectArray[i].empty().prop('disabled', true); if (self.settings.emptyStyle === 'none') { _style.display = 'none'; } else if (self.settings.emptyStyle === 'hidden') { _style.visibility = 'hidden'; }; self.selectArray[i].css(_style); }; }; cxSelect.start = function(data) { var self = this; if ($.isArray(data)) { self.settings.data = cxSelect.getData(data, self.settings.jsonSpace); }; if (!self.selectArray.length) {return}; // 保存默认值 for (var i = 0, l = self.selectArray.length; i < l; i++) { if (typeof self.selectArray[i].attr('data-value') !== 'string' && self.selectArray[i][0].options.length) { self.selectArray[i].attr('data-value', self.selectArray[i].val()); }; }; if (self.settings.data || (typeof self.selectArray[0].data('url') === 'string' && self.selectArray[0].data('url').length)) { cxSelect.getOptionData.call(self, 0); } else if (self.selectArray[0][0].options.length && typeof self.selectArray[0].attr('data-value') === 'string' && self.selectArray[0].attr('data-value').length) { self.selectArray[0].val(self.selectArray[0].attr('data-value')); cxSelect.getOptionData.call(self, 1); } else { self.selectArray[0].prop('disabled', false).css({ 'display': '', 'visibility': '' }); }; }; // 获取选项数据 cxSelect.getOptionData = function(index) { var self = this; if (typeof index !== 'number' || isNaN(index) || index < 0 || index >= self.selectArray.length) {return}; var _indexPrev = index - 1; var _select = self.selectArray[index]; var _selectData; var _valueIndex; var _dataUrl = _select.data('url'); var _jsonSpace = typeof _select.data('jsonSpace') === 'undefined' ? self.settings.jsonSpace : _select.data('jsonSpace'); var _query = {}; var _queryName; var _selectName; var _selectValue; cxSelect.clear.call(self, index); // 使用独立接口 if (typeof _dataUrl === 'string' && _dataUrl.length) { if (index > 0) { for (var i = 0, j = 1; i < index; i++, j++) { _queryName = self.selectArray[j].data('queryName'); _selectName = self.selectArray[i].attr('name'); _selectValue = self.selectArray[i].val(); if (typeof _queryName === 'string' && _queryName.length) { _query[_queryName] = _selectValue; } else if (typeof _selectName === 'string' && _selectName.length) { _query[_selectName] = _selectValue; }; }; }; $.getJSON(_dataUrl, _query, function(json) { _selectData = cxSelect.getData(json, _jsonSpace); cxSelect.buildOption.call(self, index, _selectData); }); // 使用整合数据 } else if (self.settings.data && typeof self.settings.data === 'object') { _selectData = self.settings.data; for (var i = 0; i < index; i++) { _valueIndex = cxSelect.getIndex(self.selectArray[i][0].selectedIndex, typeof self.selectArray[i].data('required') === 'boolean' ? self.selectArray[i].data('required') : self.settings.required); if (typeof _selectData[_valueIndex] === 'object' && $.isArray(_selectData[_valueIndex][self.settings.jsonSub]) && _selectData[_valueIndex][self.settings.jsonSub].length) { _selectData = _selectData[_valueIndex][self.settings.jsonSub]; } else { _selectData = null; break; }; }; cxSelect.buildOption.call(self, index, _selectData); }; }; // 构建选项列表 cxSelect.buildOption = function(index, data) { var self = this; var _select = self.selectArray[index]; var _required = typeof _select.data('required') === 'boolean' ? _select.data('required') : self.settings.required; var _firstTitle = typeof _select.data('firstTitle') === 'undefined' ? self.settings.firstTitle : _select.data('firstTitle'); var _firstValue = typeof _select.data('firstValue') === 'undefined' ? self.settings.firstValue : _select.data('firstValue'); var _jsonName = typeof _select.data('jsonName') === 'undefined' ? self.settings.jsonName : _select.data('jsonName'); var _jsonValue = typeof _select.data('jsonValue') === 'undefined' ? self.settings.jsonValue : _select.data('jsonValue'); if (!$.isArray(data)) {return}; var _html = !_required ? '' : ''; // 区分标题、值的数据 if (typeof _jsonName === 'string' && _jsonName.length) { // 无值字段时使用标题作为值 if (typeof _jsonValue !== 'string' || !_jsonValue.length) { _jsonValue = _jsonName; }; for (var i = 0, l = data.length; i < l; i++) { _html += ''; }; // 数组即为值的数据 } else { for (var i = 0, l = data.length; i < l; i++) { _html += ''; }; }; _select.html(_html).prop('disabled', false).css({ 'display': '', 'visibility': '' }); // 初次加载设置默认值 if (typeof _select.attr('data-value') === 'string') { _select.val(String(_select.attr('data-value'))).removeAttr('data-value'); if (_select[0].selectedIndex < 0) { _select[0].options[0].selected = true; }; }; if (_required || _select[0].selectedIndex > 0) { _select.trigger('change'); }; }; // 改变选择时的处理 cxSelect.selectChange = function(name) { var self = this; if (typeof name !== 'string' || !name.length) {return}; var index; name = name.replace(/\s+/g, ','); name = ',' + name + ','; // 获取当前 select 位置 for (var i = 0, l = self.selectArray.length; i < l; i++) { if (name.indexOf(',' + self.settings.selects[i] + ',') > -1) { index = i; break; }; }; if (typeof index === 'number' && index > -1) { index += 1; cxSelect.getOptionData.call(self, index); }; }; $.cxSelect = function() { return cxSelect.apply(this, arguments); }; // 默认值 $.cxSelect.defaults = { selects: [], // 下拉选框组 url: null, // 列表数据文件路径(URL)或数组数据 data: null, // 自定义数据 emptyStyle: null, // 无数据状态显示方式 required: false, // 是否为必选 firstTitle: '请选择', // 第一个选项的标题 firstValue: '', // 第一个选项的值 jsonSpace: '', // 数据命名空间 jsonName: 'n', // 数据标题字段名称 jsonValue: '', // 数据值字段名称 jsonSub: 's' // 子集数据字段名称 }; $.fn.cxSelect = function(settings, callback) { this.each(function(i) { $.cxSelect(this, settings, callback); }); return this; }; })); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.css ================================================ /*! * Datetimepicker for Bootstrap * * Copyright 2012 Stefan Petre * Improvements by Andrew Rowls * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0 * */ .datetimepicker { padding: 4px; margin-top: 1px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; direction: ltr; } .datetimepicker-inline { width: 220px; } .datetimepicker.datetimepicker-rtl { direction: rtl; } .datetimepicker.datetimepicker-rtl table tr td span { float: right; } .datetimepicker-dropdown, .datetimepicker-dropdown-left { top: 0; left: 0; } [class*=" datetimepicker-dropdown"]:before { content: ''; display: inline-block; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 7px solid #cccccc; border-bottom-color: rgba(0, 0, 0, 0.2); position: absolute; } [class*=" datetimepicker-dropdown"]:after { content: ''; display: inline-block; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 6px solid #ffffff; position: absolute; } [class*=" datetimepicker-dropdown-top"]:before { content: ''; display: inline-block; border-left: 7px solid transparent; border-right: 7px solid transparent; border-top: 7px solid #cccccc; border-top-color: rgba(0, 0, 0, 0.2); border-bottom: 0; } [class*=" datetimepicker-dropdown-top"]:after { content: ''; display: inline-block; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 6px solid #ffffff; border-bottom: 0; } .datetimepicker-dropdown-bottom-left:before { top: -7px; right: 6px; } .datetimepicker-dropdown-bottom-left:after { top: -6px; right: 7px; } .datetimepicker-dropdown-bottom-right:before { top: -7px; left: 6px; } .datetimepicker-dropdown-bottom-right:after { top: -6px; left: 7px; } .datetimepicker-dropdown-top-left:before { bottom: -7px; right: 6px; } .datetimepicker-dropdown-top-left:after { bottom: -6px; right: 7px; } .datetimepicker-dropdown-top-right:before { bottom: -7px; left: 6px; } .datetimepicker-dropdown-top-right:after { bottom: -6px; left: 7px; } .datetimepicker > div { display: none; } .datetimepicker.minutes div.datetimepicker-minutes { display: block; } .datetimepicker.hours div.datetimepicker-hours { display: block; } .datetimepicker.days div.datetimepicker-days { display: block; } .datetimepicker.months div.datetimepicker-months { display: block; } .datetimepicker.years div.datetimepicker-years { display: block; } .datetimepicker table { margin: 0; } .datetimepicker td, .datetimepicker th { text-align: center; width: 20px; height: 20px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; border: none; } .table-striped .datetimepicker table tr td, .table-striped .datetimepicker table tr th { background-color: transparent; } .datetimepicker table tr td.minute:hover { background: #eeeeee; cursor: pointer; } .datetimepicker table tr td.hour:hover { background: #eeeeee; cursor: pointer; } .datetimepicker table tr td.day:hover { background: #eeeeee; cursor: pointer; } .datetimepicker table tr td.old, .datetimepicker table tr td.new { color: #999999; } .datetimepicker table tr td.disabled, .datetimepicker table tr td.disabled:hover { background: none; color: #999999; cursor: default; } .datetimepicker table tr td.today, .datetimepicker table tr td.today:hover, .datetimepicker table tr td.today.disabled, .datetimepicker table tr td.today.disabled:hover { background-color: #fde19a; background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a); background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a); background-image: -o-linear-gradient(top, #fdd49a, #fdf59a); background-image: linear-gradient(to bottom, #fdd49a, #fdf59a); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); border-color: #fdf59a #fdf59a #fbed50; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); } .datetimepicker table tr td.today:hover, .datetimepicker table tr td.today:hover:hover, .datetimepicker table tr td.today.disabled:hover, .datetimepicker table tr td.today.disabled:hover:hover, .datetimepicker table tr td.today:active, .datetimepicker table tr td.today:hover:active, .datetimepicker table tr td.today.disabled:active, .datetimepicker table tr td.today.disabled:hover:active, .datetimepicker table tr td.today.active, .datetimepicker table tr td.today:hover.active, .datetimepicker table tr td.today.disabled.active, .datetimepicker table tr td.today.disabled:hover.active, .datetimepicker table tr td.today.disabled, .datetimepicker table tr td.today:hover.disabled, .datetimepicker table tr td.today.disabled.disabled, .datetimepicker table tr td.today.disabled:hover.disabled, .datetimepicker table tr td.today[disabled], .datetimepicker table tr td.today:hover[disabled], .datetimepicker table tr td.today.disabled[disabled], .datetimepicker table tr td.today.disabled:hover[disabled] { background-color: #fdf59a; } .datetimepicker table tr td.today:active, .datetimepicker table tr td.today:hover:active, .datetimepicker table tr td.today.disabled:active, .datetimepicker table tr td.today.disabled:hover:active, .datetimepicker table tr td.today.active, .datetimepicker table tr td.today:hover.active, .datetimepicker table tr td.today.disabled.active, .datetimepicker table tr td.today.disabled:hover.active { background-color: #fbf069; } .datetimepicker table tr td.active, .datetimepicker table tr td.active:hover, .datetimepicker table tr td.active.disabled, .datetimepicker table tr td.active.disabled:hover { background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -ms-linear-gradient(top, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); background-image: -o-linear-gradient(top, #0088cc, #0044cc); background-image: linear-gradient(to bottom, #0088cc, #0044cc); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #ffffff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } .datetimepicker table tr td.active:hover, .datetimepicker table tr td.active:hover:hover, .datetimepicker table tr td.active.disabled:hover, .datetimepicker table tr td.active.disabled:hover:hover, .datetimepicker table tr td.active:active, .datetimepicker table tr td.active:hover:active, .datetimepicker table tr td.active.disabled:active, .datetimepicker table tr td.active.disabled:hover:active, .datetimepicker table tr td.active.active, .datetimepicker table tr td.active:hover.active, .datetimepicker table tr td.active.disabled.active, .datetimepicker table tr td.active.disabled:hover.active, .datetimepicker table tr td.active.disabled, .datetimepicker table tr td.active:hover.disabled, .datetimepicker table tr td.active.disabled.disabled, .datetimepicker table tr td.active.disabled:hover.disabled, .datetimepicker table tr td.active[disabled], .datetimepicker table tr td.active:hover[disabled], .datetimepicker table tr td.active.disabled[disabled], .datetimepicker table tr td.active.disabled:hover[disabled] { background-color: #0044cc; } .datetimepicker table tr td.active:active, .datetimepicker table tr td.active:hover:active, .datetimepicker table tr td.active.disabled:active, .datetimepicker table tr td.active.disabled:hover:active, .datetimepicker table tr td.active.active, .datetimepicker table tr td.active:hover.active, .datetimepicker table tr td.active.disabled.active, .datetimepicker table tr td.active.disabled:hover.active { background-color: #003399; } .datetimepicker table tr td span { display: block; width: 23%; height: 54px; line-height: 54px; float: left; margin: 1%; cursor: pointer; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } .datetimepicker .datetimepicker-hours span { height: 26px; line-height: 26px; } .datetimepicker .datetimepicker-hours table tr td span.hour_am, .datetimepicker .datetimepicker-hours table tr td span.hour_pm { width: 14.6%; } .datetimepicker .datetimepicker-hours fieldset legend, .datetimepicker .datetimepicker-minutes fieldset legend { margin-bottom: inherit; line-height: 30px; } .datetimepicker .datetimepicker-minutes span { height: 26px; line-height: 26px; } .datetimepicker table tr td span:hover { background: #eeeeee; } .datetimepicker table tr td span.disabled, .datetimepicker table tr td span.disabled:hover { background: none; color: #999999; cursor: default; } .datetimepicker table tr td span.active, .datetimepicker table tr td span.active:hover, .datetimepicker table tr td span.active.disabled, .datetimepicker table tr td span.active.disabled:hover { background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -ms-linear-gradient(top, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); background-image: -o-linear-gradient(top, #0088cc, #0044cc); background-image: linear-gradient(to bottom, #0088cc, #0044cc); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #ffffff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } .datetimepicker table tr td span.active:hover, .datetimepicker table tr td span.active:hover:hover, .datetimepicker table tr td span.active.disabled:hover, .datetimepicker table tr td span.active.disabled:hover:hover, .datetimepicker table tr td span.active:active, .datetimepicker table tr td span.active:hover:active, .datetimepicker table tr td span.active.disabled:active, .datetimepicker table tr td span.active.disabled:hover:active, .datetimepicker table tr td span.active.active, .datetimepicker table tr td span.active:hover.active, .datetimepicker table tr td span.active.disabled.active, .datetimepicker table tr td span.active.disabled:hover.active, .datetimepicker table tr td span.active.disabled, .datetimepicker table tr td span.active:hover.disabled, .datetimepicker table tr td span.active.disabled.disabled, .datetimepicker table tr td span.active.disabled:hover.disabled, .datetimepicker table tr td span.active[disabled], .datetimepicker table tr td span.active:hover[disabled], .datetimepicker table tr td span.active.disabled[disabled], .datetimepicker table tr td span.active.disabled:hover[disabled] { background-color: #0044cc; } .datetimepicker table tr td span.active:active, .datetimepicker table tr td span.active:hover:active, .datetimepicker table tr td span.active.disabled:active, .datetimepicker table tr td span.active.disabled:hover:active, .datetimepicker table tr td span.active.active, .datetimepicker table tr td span.active:hover.active, .datetimepicker table tr td span.active.disabled.active, .datetimepicker table tr td span.active.disabled:hover.active { background-color: #003399; } .datetimepicker table tr td span.old { color: #999999; } .datetimepicker th.switch { width: 145px; } .datetimepicker th span.glyphicon { pointer-events: none; } .datetimepicker thead tr:first-child th, .datetimepicker tfoot th { cursor: pointer; } .datetimepicker thead tr:first-child th:hover, .datetimepicker tfoot th:hover { background: #eeeeee; } .input-append.date .add-on i, .input-prepend.date .add-on i, .input-group.date .input-group-addon span { cursor: pointer; width: 14px; height: 14px; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/datapicker/bootstrap-datetimepicker.js ================================================ /* ========================================================= * bootstrap-datetimepicker.js * ========================================================= * Copyright 2012 Stefan Petre * * Improvements by Andrew Rowls * Improvements by Sébastien Malot * Improvements by Yun Lai * Improvements by Kenneth Henderick * Improvements by CuGBabyBeaR * Improvements by Christian Vaas * * Project URL : http://www.malot.fr/bootstrap-datetimepicker * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ========================================================= */ (function(factory){ if (typeof define === 'function' && define.amd) define(['jquery'], factory); else if (typeof exports === 'object') factory(require('jquery')); else factory(jQuery); }(function($, undefined){ // Add ECMA262-5 Array methods if not supported natively (IE8) if (!('indexOf' in Array.prototype)) { Array.prototype.indexOf = function (find, i) { if (i === undefined) i = 0; if (i < 0) i += this.length; if (i < 0) i = 0; for (var n = this.length; i < n; i++) { if (i in this && this[i] === find) { return i; } } return -1; } } // Add timezone abbreviation support for ie6+, Chrome, Firefox function timeZoneAbbreviation() { var abbreviation, date, formattedStr, i, len, matchedStrings, ref, str; date = (new Date()).toString(); formattedStr = ((ref = date.split('(')[1]) != null ? ref.slice(0, -1) : 0) || date.split(' '); if (formattedStr instanceof Array) { matchedStrings = []; for (var i = 0, len = formattedStr.length; i < len; i++) { str = formattedStr[i]; if ((abbreviation = (ref = str.match(/\b[A-Z]+\b/)) !== null) ? ref[0] : 0) { matchedStrings.push(abbreviation); } } formattedStr = matchedStrings.pop(); } return formattedStr; } function UTCDate() { return new Date(Date.UTC.apply(Date, arguments)); } // Picker object var Datetimepicker = function (element, options) { var that = this; this.element = $(element); // add container for single page application // when page switch the datetimepicker div will be removed also. this.container = options.container || 'body'; this.language = options.language || this.element.data('date-language') || 'zh-cn'; this.language = this.language in dates ? this.language : this.language.split('-')[0]; // fr-CA fallback to fr this.language = this.language in dates ? this.language : 'en'; this.isRTL = dates[this.language].rtl || false; this.formatType = options.formatType || this.element.data('format-type') || 'standard'; this.format = DPGlobal.parseFormat(options.format || this.element.data('date-format') || dates[this.language].format || DPGlobal.getDefaultFormat(this.formatType, 'input'), this.formatType); this.isInline = false; this.isVisible = false; this.isInput = this.element.is('input'); this.fontAwesome = options.fontAwesome || this.element.data('font-awesome') || false; this.bootcssVer = options.bootcssVer || (this.isInput ? (this.element.is('.form-control') ? 3 : 2) : ( this.bootcssVer = this.element.is('.input-group') ? 3 : 2 )); this.component = this.element.is('.date') ? ( this.bootcssVer === 3 ? this.element.find('.input-group-addon .glyphicon-th, .input-group-addon .glyphicon-time, .input-group-addon .glyphicon-remove, .input-group-addon .glyphicon-calendar, .input-group-addon .fa-calendar, .input-group-addon .fa-clock-o').parent() : this.element.find('.add-on .icon-th, .add-on .icon-time, .add-on .icon-calendar, .add-on .fa-calendar, .add-on .fa-clock-o').parent()) : false; this.componentReset = this.element.is('.date') ? ( this.bootcssVer === 3 ? this.element.find('.input-group-addon .glyphicon-remove, .input-group-addon .fa-times').parent():this.element.find('.add-on .icon-remove, .add-on .fa-times').parent()) : false; this.hasInput = this.component && this.element.find('input').length; if (this.component && this.component.length === 0) { this.component = false; } this.linkField = options.linkField || this.element.data('link-field') || false; this.linkFormat = DPGlobal.parseFormat(options.linkFormat || this.element.data('link-format') || DPGlobal.getDefaultFormat(this.formatType, 'link'), this.formatType); this.minuteStep = options.minuteStep || this.element.data('minute-step') || 5; this.pickerPosition = options.pickerPosition || this.element.data('picker-position') || 'bottom-right'; this.showMeridian = options.showMeridian || this.element.data('show-meridian') || false; this.initialDate = options.initialDate || new Date(); this.zIndex = options.zIndex || this.element.data('z-index') || undefined; this.title = typeof options.title === 'undefined' ? false : options.title; this.timezone = options.timezone || timeZoneAbbreviation(); this.icons = { leftArrow: this.fontAwesome ? 'fa-arrow-left' : (this.bootcssVer === 3 ? 'glyphicon-arrow-left' : 'icon-arrow-left'), rightArrow: this.fontAwesome ? 'fa-arrow-right' : (this.bootcssVer === 3 ? 'glyphicon-arrow-right' : 'icon-arrow-right') } this.icontype = this.fontAwesome ? 'fa' : 'glyphicon'; this._attachEvents(); this.clickedOutside = function (e) { // Clicked outside the datetimepicker, hide it if ($(e.target).closest('.datetimepicker').length === 0) { that.hide(); } } this.formatViewType = 'datetime'; if ('formatViewType' in options) { this.formatViewType = options.formatViewType; } else if ('formatViewType' in this.element.data()) { this.formatViewType = this.element.data('formatViewType'); } this.minView = 0; if ('minView' in options) { this.minView = options.minView; } else if ('minView' in this.element.data()) { this.minView = this.element.data('min-view'); } this.minView = DPGlobal.convertViewMode(this.minView); this.maxView = DPGlobal.modes.length - 1; if ('maxView' in options) { this.maxView = options.maxView; } else if ('maxView' in this.element.data()) { this.maxView = this.element.data('max-view'); } this.maxView = DPGlobal.convertViewMode(this.maxView); this.wheelViewModeNavigation = false; if ('wheelViewModeNavigation' in options) { this.wheelViewModeNavigation = options.wheelViewModeNavigation; } else if ('wheelViewModeNavigation' in this.element.data()) { this.wheelViewModeNavigation = this.element.data('view-mode-wheel-navigation'); } this.wheelViewModeNavigationInverseDirection = false; if ('wheelViewModeNavigationInverseDirection' in options) { this.wheelViewModeNavigationInverseDirection = options.wheelViewModeNavigationInverseDirection; } else if ('wheelViewModeNavigationInverseDirection' in this.element.data()) { this.wheelViewModeNavigationInverseDirection = this.element.data('view-mode-wheel-navigation-inverse-dir'); } this.wheelViewModeNavigationDelay = 100; if ('wheelViewModeNavigationDelay' in options) { this.wheelViewModeNavigationDelay = options.wheelViewModeNavigationDelay; } else if ('wheelViewModeNavigationDelay' in this.element.data()) { this.wheelViewModeNavigationDelay = this.element.data('view-mode-wheel-navigation-delay'); } this.startViewMode = 2; if ('startView' in options) { this.startViewMode = options.startView; } else if ('startView' in this.element.data()) { this.startViewMode = this.element.data('start-view'); } this.startViewMode = DPGlobal.convertViewMode(this.startViewMode); this.viewMode = this.startViewMode; this.viewSelect = this.minView; if ('viewSelect' in options) { this.viewSelect = options.viewSelect; } else if ('viewSelect' in this.element.data()) { this.viewSelect = this.element.data('view-select'); } this.viewSelect = DPGlobal.convertViewMode(this.viewSelect); this.forceParse = true; if ('forceParse' in options) { this.forceParse = options.forceParse; } else if ('dateForceParse' in this.element.data()) { this.forceParse = this.element.data('date-force-parse'); } var template = this.bootcssVer === 3 ? DPGlobal.templateV3 : DPGlobal.template; while (template.indexOf('{iconType}') !== -1) { template = template.replace('{iconType}', this.icontype); } while (template.indexOf('{leftArrow}') !== -1) { template = template.replace('{leftArrow}', this.icons.leftArrow); } while (template.indexOf('{rightArrow}') !== -1) { template = template.replace('{rightArrow}', this.icons.rightArrow); } this.picker = $(template) .appendTo(this.isInline ? this.element : this.container) // 'body') .on({ click: $.proxy(this.click, this), mousedown: $.proxy(this.mousedown, this) }); if (this.wheelViewModeNavigation) { if ($.fn.mousewheel) { this.picker.on({mousewheel: $.proxy(this.mousewheel, this)}); } else { console.log('Mouse Wheel event is not supported. Please include the jQuery Mouse Wheel plugin before enabling this option'); } } if (this.isInline) { this.picker.addClass('datetimepicker-inline'); } else { this.picker.addClass('datetimepicker-dropdown-' + this.pickerPosition + ' dropdown-menu'); } if (this.isRTL) { this.picker.addClass('datetimepicker-rtl'); var selector = this.bootcssVer === 3 ? '.prev span, .next span' : '.prev i, .next i'; this.picker.find(selector).toggleClass(this.icons.leftArrow + ' ' + this.icons.rightArrow); } $(document).on('mousedown touchend', this.clickedOutside); this.autoclose = false; if ('autoclose' in options) { this.autoclose = options.autoclose; } else if ('dateAutoclose' in this.element.data()) { this.autoclose = this.element.data('date-autoclose'); } this.keyboardNavigation = true; if ('keyboardNavigation' in options) { this.keyboardNavigation = options.keyboardNavigation; } else if ('dateKeyboardNavigation' in this.element.data()) { this.keyboardNavigation = this.element.data('date-keyboard-navigation'); } this.todayBtn = (options.todayBtn || this.element.data('date-today-btn') || false); this.clearBtn = (options.clearBtn || this.element.data('date-clear-btn') || false); this.todayHighlight = (options.todayHighlight || this.element.data('date-today-highlight') || false); this.weekStart = 0; if (typeof options.weekStart !== 'undefined') { this.weekStart = options.weekStart; } else if (typeof this.element.data('date-weekstart') !== 'undefined') { this.weekStart = this.element.data('date-weekstart'); } else if (typeof dates[this.language].weekStart !== 'undefined') { this.weekStart = dates[this.language].weekStart; } this.weekStart = this.weekStart % 7; this.weekEnd = ((this.weekStart + 6) % 7); this.onRenderDay = function (date) { var render = (options.onRenderDay || function () { return []; })(date); if (typeof render === 'string') { render = [render]; } var res = ['day']; return res.concat((render ? render : [])); }; this.onRenderHour = function (date) { var render = (options.onRenderHour || function () { return []; })(date); var res = ['hour']; if (typeof render === 'string') { render = [render]; } return res.concat((render ? render : [])); }; this.onRenderMinute = function (date) { var render = (options.onRenderMinute || function () { return []; })(date); var res = ['minute']; if (typeof render === 'string') { render = [render]; } if (date < this.startDate || date > this.endDate) { res.push('disabled'); } else if (Math.floor(this.date.getUTCMinutes() / this.minuteStep) === Math.floor(date.getUTCMinutes() / this.minuteStep)) { res.push('active'); } return res.concat((render ? render : [])); }; this.onRenderYear = function (date) { var render = (options.onRenderYear || function () { return []; })(date); var res = ['year']; if (typeof render === 'string') { render = [render]; } if (this.date.getUTCFullYear() === date.getUTCFullYear()) { res.push('active'); } var currentYear = date.getUTCFullYear(); var endYear = this.endDate.getUTCFullYear(); if (date < this.startDate || currentYear > endYear) { res.push('disabled'); } return res.concat((render ? render : [])); } this.onRenderMonth = function (date) { var render = (options.onRenderMonth || function () { return []; })(date); var res = ['month']; if (typeof render === 'string') { render = [render]; } return res.concat((render ? render : [])); } this.startDate = new Date(-8639968443048000); this.endDate = new Date(8639968443048000); this.datesDisabled = []; this.daysOfWeekDisabled = []; this.setStartDate(options.startDate || this.element.data('date-startdate')); this.setEndDate(options.endDate || this.element.data('date-enddate')); this.setDatesDisabled(options.datesDisabled || this.element.data('date-dates-disabled')); this.setDaysOfWeekDisabled(options.daysOfWeekDisabled || this.element.data('date-days-of-week-disabled')); this.setMinutesDisabled(options.minutesDisabled || this.element.data('date-minute-disabled')); this.setHoursDisabled(options.hoursDisabled || this.element.data('date-hour-disabled')); this.fillDow(); this.fillMonths(); this.update(); this.showMode(); if (this.isInline) { this.show(); } }; Datetimepicker.prototype = { constructor: Datetimepicker, _events: [], _attachEvents: function () { this._detachEvents(); if (this.isInput) { // single input this._events = [ [this.element, { focus: $.proxy(this.show, this), keyup: $.proxy(this.update, this), keydown: $.proxy(this.keydown, this) }] ]; } else if (this.component && this.hasInput) { // component: input + button this._events = [ // For components that are not readonly, allow keyboard nav [this.element.find('input'), { focus: $.proxy(this.show, this), keyup: $.proxy(this.update, this), keydown: $.proxy(this.keydown, this) }], [this.component, { click: $.proxy(this.show, this) }] ]; if (this.componentReset) { this._events.push([ this.componentReset, {click: $.proxy(this.reset, this)} ]); } } else if (this.element.is('div')) { // inline datetimepicker this.isInline = true; } else { this._events = [ [this.element, { click: $.proxy(this.show, this) }] ]; } for (var i = 0, el, ev; i < this._events.length; i++) { el = this._events[i][0]; ev = this._events[i][1]; el.on(ev); } }, _detachEvents: function () { for (var i = 0, el, ev; i < this._events.length; i++) { el = this._events[i][0]; ev = this._events[i][1]; el.off(ev); } this._events = []; }, show: function (e) { this.picker.show(); this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); if (this.forceParse) { this.update(); } this.place(); $(window).on('resize', $.proxy(this.place, this)); if (e) { e.stopPropagation(); e.preventDefault(); } this.isVisible = true; this.element.trigger({ type: 'show', date: this.date }); }, hide: function () { if (!this.isVisible) return; if (this.isInline) return; this.picker.hide(); $(window).off('resize', this.place); this.viewMode = this.startViewMode; this.showMode(); if (!this.isInput) { $(document).off('mousedown', this.hide); } if ( this.forceParse && ( this.isInput && this.element.val() || this.hasInput && this.element.find('input').val() ) ) this.setValue(); this.isVisible = false; this.element.trigger({ type: 'hide', date: this.date }); }, remove: function () { this._detachEvents(); $(document).off('mousedown', this.clickedOutside); this.picker.remove(); delete this.picker; delete this.element.data().datetimepicker; }, getDate: function () { var d = this.getUTCDate(); if (d === null) { return null; } return new Date(d.getTime() + (d.getTimezoneOffset() * 60000)); }, getUTCDate: function () { return this.date; }, getInitialDate: function () { return this.initialDate }, setInitialDate: function (initialDate) { this.initialDate = initialDate; }, setDate: function (d) { this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset() * 60000))); }, setUTCDate: function (d) { if (d >= this.startDate && d <= this.endDate) { this.date = d; this.setValue(); this.viewDate = this.date; this.fill(); } else { this.element.trigger({ type: 'outOfRange', date: d, startDate: this.startDate, endDate: this.endDate }); } }, setFormat: function (format) { this.format = DPGlobal.parseFormat(format, this.formatType); var element; if (this.isInput) { element = this.element; } else if (this.component) { element = this.element.find('input'); } if (element && element.val()) { this.setValue(); } }, setValue: function () { var formatted = this.getFormattedDate(); if (!this.isInput) { if (this.component) { this.element.find('input').val(formatted); } this.element.data('date', formatted); } else { this.element.val(formatted); } if (this.linkField) { $('#' + this.linkField).val(this.getFormattedDate(this.linkFormat)); } }, getFormattedDate: function (format) { format = format || this.format; return DPGlobal.formatDate(this.date, format, this.language, this.formatType, this.timezone); }, setStartDate: function (startDate) { this.startDate = startDate || this.startDate; if (this.startDate.valueOf() !== 8639968443048000) { this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language, this.formatType, this.timezone); } this.update(); this.updateNavArrows(); }, setEndDate: function (endDate) { this.endDate = endDate || this.endDate; if (this.endDate.valueOf() !== 8639968443048000) { this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language, this.formatType, this.timezone); } this.update(); this.updateNavArrows(); }, setDatesDisabled: function (datesDisabled) { this.datesDisabled = datesDisabled || []; if (!$.isArray(this.datesDisabled)) { this.datesDisabled = this.datesDisabled.split(/,\s*/); } var mThis = this; this.datesDisabled = $.map(this.datesDisabled, function (d) { return DPGlobal.parseDate(d, mThis.format, mThis.language, mThis.formatType, mThis.timezone).toDateString(); }); this.update(); this.updateNavArrows(); }, setTitle: function (selector, value) { return this.picker.find(selector) .find('th:eq(1)') .text(this.title === false ? value : this.title); }, setDaysOfWeekDisabled: function (daysOfWeekDisabled) { this.daysOfWeekDisabled = daysOfWeekDisabled || []; if (!$.isArray(this.daysOfWeekDisabled)) { this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/); } this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) { return parseInt(d, 10); }); this.update(); this.updateNavArrows(); }, setMinutesDisabled: function (minutesDisabled) { this.minutesDisabled = minutesDisabled || []; if (!$.isArray(this.minutesDisabled)) { this.minutesDisabled = this.minutesDisabled.split(/,\s*/); } this.minutesDisabled = $.map(this.minutesDisabled, function (d) { return parseInt(d, 10); }); this.update(); this.updateNavArrows(); }, setHoursDisabled: function (hoursDisabled) { this.hoursDisabled = hoursDisabled || []; if (!$.isArray(this.hoursDisabled)) { this.hoursDisabled = this.hoursDisabled.split(/,\s*/); } this.hoursDisabled = $.map(this.hoursDisabled, function (d) { return parseInt(d, 10); }); this.update(); this.updateNavArrows(); }, place: function () { if (this.isInline) return; if (!this.zIndex) { var index_highest = 0; $('div').each(function () { var index_current = parseInt($(this).css('zIndex'), 10); if (index_current > index_highest) { index_highest = index_current; } }); this.zIndex = index_highest + 10; } var offset, top, left, containerOffset; if (this.container instanceof $) { containerOffset = this.container.offset(); } else { containerOffset = $(this.container).offset(); } if (this.component) { offset = this.component.offset(); left = offset.left; if (this.pickerPosition === 'bottom-left' || this.pickerPosition === 'top-left') { left += this.component.outerWidth() - this.picker.outerWidth(); } } else { offset = this.element.offset(); left = offset.left; if (this.pickerPosition === 'bottom-left' || this.pickerPosition === 'top-left') { left += this.element.outerWidth() - this.picker.outerWidth(); } } var bodyWidth = document.body.clientWidth || window.innerWidth; if (left + 220 > bodyWidth) { left = bodyWidth - 220; } if (this.pickerPosition === 'top-left' || this.pickerPosition === 'top-right') { top = offset.top - this.picker.outerHeight(); } else { top = offset.top + this.height; } top = top - containerOffset.top; left = left - containerOffset.left; this.picker.css({ top: top, left: left, zIndex: this.zIndex }); }, hour_minute: "^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]", update: function () { var date, fromArgs = false; if (arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) { date = arguments[0]; fromArgs = true; } else { date = (this.isInput ? this.element.val() : this.element.find('input').val()) || this.element.data('date') || this.initialDate; if (typeof date === 'string') { date = date.replace(/^\s+|\s+$/g,''); } } if (!date) { date = new Date(); fromArgs = false; } if (typeof date === "string") { if (new RegExp(this.hour_minute).test(date) || new RegExp(this.hour_minute + ":[0-5][0-9]").test(date)) { date = this.getDate() } } this.date = DPGlobal.parseDate(date, this.format, this.language, this.formatType, this.timezone); if (fromArgs) this.setValue(); if (this.date < this.startDate) { this.viewDate = new Date(this.startDate); } else if (this.date > this.endDate) { this.viewDate = new Date(this.endDate); } else { this.viewDate = new Date(this.date); } this.fill(); }, fillDow: function () { var dowCnt = this.weekStart, html = ''; while (dowCnt < this.weekStart + 7) { html += '' + dates[this.language].daysMin[(dowCnt++) % 7] + ''; } html += ''; this.picker.find('.datetimepicker-days thead').append(html); }, fillMonths: function () { var html = ''; var d = new Date(this.viewDate); for (var i = 0; i < 12; i++) { d.setUTCMonth(i); var classes = this.onRenderMonth(d); html += '' + dates[this.language].monthsShort[i] + ''; } this.picker.find('.datetimepicker-months td').html(html); }, fill: function () { if (!this.date || !this.viewDate) { return; } var d = new Date(this.viewDate), year = d.getUTCFullYear(), month = d.getUTCMonth(), dayMonth = d.getUTCDate(), hours = d.getUTCHours(), startYear = this.startDate.getUTCFullYear(), startMonth = this.startDate.getUTCMonth(), endYear = this.endDate.getUTCFullYear(), endMonth = this.endDate.getUTCMonth() + 1, currentDate = (new UTCDate(this.date.getUTCFullYear(), this.date.getUTCMonth(), this.date.getUTCDate())).valueOf(), today = new Date(); this.setTitle('.datetimepicker-days', dates[this.language].months[month] + ' ' + year) if (this.formatViewType === 'time') { var formatted = this.getFormattedDate(); this.setTitle('.datetimepicker-hours', formatted); this.setTitle('.datetimepicker-minutes', formatted); } else { this.setTitle('.datetimepicker-hours', dayMonth + ' ' + dates[this.language].months[month] + ' ' + year); this.setTitle('.datetimepicker-minutes', dayMonth + ' ' + dates[this.language].months[month] + ' ' + year); } this.picker.find('tfoot th.today') .text(dates[this.language].today || dates['en'].today) .toggle(this.todayBtn !== false); this.picker.find('tfoot th.clear') .text(dates[this.language].clear || dates['en'].clear) .toggle(this.clearBtn !== false); this.updateNavArrows(); this.fillMonths(); var prevMonth = UTCDate(year, month - 1, 28, 0, 0, 0, 0), day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); prevMonth.setUTCDate(day); prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7) % 7); var nextMonth = new Date(prevMonth); nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); nextMonth = nextMonth.valueOf(); var html = []; var classes; while (prevMonth.valueOf() < nextMonth) { if (prevMonth.getUTCDay() === this.weekStart) { html.push(''); } classes = this.onRenderDay(prevMonth); if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() === year && prevMonth.getUTCMonth() < month)) { classes.push('old'); } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() === year && prevMonth.getUTCMonth() > month)) { classes.push('new'); } // Compare internal UTC date with local today, not UTC today if (this.todayHighlight && prevMonth.getUTCFullYear() === today.getFullYear() && prevMonth.getUTCMonth() === today.getMonth() && prevMonth.getUTCDate() === today.getDate()) { classes.push('today'); } if (prevMonth.valueOf() === currentDate) { classes.push('active'); } if ((prevMonth.valueOf() + 86400000) <= this.startDate || prevMonth.valueOf() > this.endDate || $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1 || $.inArray(prevMonth.toDateString(), this.datesDisabled) !== -1) { classes.push('disabled'); } html.push('' + prevMonth.getUTCDate() + ''); if (prevMonth.getUTCDay() === this.weekEnd) { html.push(''); } prevMonth.setUTCDate(prevMonth.getUTCDate() + 1); } this.picker.find('.datetimepicker-days tbody').empty().append(html.join('')); html = []; var txt = '', meridian = '', meridianOld = ''; var hoursDisabled = this.hoursDisabled || []; d = new Date(this.viewDate) for (var i = 0; i < 24; i++) { d.setUTCHours(i); classes = this.onRenderHour(d); if (hoursDisabled.indexOf(i) !== -1) { classes.push('disabled'); } var actual = UTCDate(year, month, dayMonth, i); // We want the previous hour for the startDate if ((actual.valueOf() + 3600000) <= this.startDate || actual.valueOf() > this.endDate) { classes.push('disabled'); } else if (hours === i) { classes.push('active'); } if (this.showMeridian && dates[this.language].meridiem.length === 2) { meridian = (i < 12 ? dates[this.language].meridiem[0] : dates[this.language].meridiem[1]); if (meridian !== meridianOld) { if (meridianOld !== '') { html.push(''); } html.push('
    ' + meridian.toUpperCase() + ''); } meridianOld = meridian; txt = (i % 12 ? i % 12 : 12); if (i < 12) { classes.push('hour_am'); } else { classes.push('hour_pm'); } html.push('' + txt + ''); if (i === 23) { html.push('
    '); } } else { txt = i + ':00'; html.push('' + txt + ''); } } this.picker.find('.datetimepicker-hours td').html(html.join('')); html = []; txt = ''; meridian = ''; meridianOld = ''; var minutesDisabled = this.minutesDisabled || []; d = new Date(this.viewDate); for (var i = 0; i < 60; i += this.minuteStep) { if (minutesDisabled.indexOf(i) !== -1) continue; d.setUTCMinutes(i); d.setUTCSeconds(0); classes = this.onRenderMinute(d); if (this.showMeridian && dates[this.language].meridiem.length === 2) { meridian = (hours < 12 ? dates[this.language].meridiem[0] : dates[this.language].meridiem[1]); if (meridian !== meridianOld) { if (meridianOld !== '') { html.push(''); } html.push('
    ' + meridian.toUpperCase() + ''); } meridianOld = meridian; txt = (hours % 12 ? hours % 12 : 12); html.push('' + txt + ':' + (i < 10 ? '0' + i : i) + ''); if (i === 59) { html.push('
    '); } } else { txt = i + ':00'; html.push('' + hours + ':' + (i < 10 ? '0' + i : i) + ''); } } this.picker.find('.datetimepicker-minutes td').html(html.join('')); var currentYear = this.date.getUTCFullYear(); var months = this.setTitle('.datetimepicker-months', year) .end() .find('.month').removeClass('active'); if (currentYear === year) { // getUTCMonths() returns 0 based, and we need to select the next one // To cater bootstrap 2 we don't need to select the next one months.eq(this.date.getUTCMonth()).addClass('active'); } if (year < startYear || year > endYear) { months.addClass('disabled'); } if (year === startYear) { months.slice(0, startMonth).addClass('disabled'); } if (year === endYear) { months.slice(endMonth).addClass('disabled'); } html = ''; year = parseInt(year / 10, 10) * 10; var yearCont = this.setTitle('.datetimepicker-years', year + '-' + (year + 9)) .end() .find('td'); year -= 1; d = new Date(this.viewDate); for (var i = -1; i < 11; i++) { d.setUTCFullYear(year); classes = this.onRenderYear(d); if (i === -1 || i === 10) { classes.push(old); } html += '' + year + ''; year += 1; } yearCont.html(html); this.place(); }, updateNavArrows: function () { var d = new Date(this.viewDate), year = d.getUTCFullYear(), month = d.getUTCMonth(), day = d.getUTCDate(), hour = d.getUTCHours(); switch (this.viewMode) { case 0: if (year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth() && day <= this.startDate.getUTCDate() && hour <= this.startDate.getUTCHours()) { this.picker.find('.prev').css({visibility: 'hidden'}); } else { this.picker.find('.prev').css({visibility: 'visible'}); } if (year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth() && day >= this.endDate.getUTCDate() && hour >= this.endDate.getUTCHours()) { this.picker.find('.next').css({visibility: 'hidden'}); } else { this.picker.find('.next').css({visibility: 'visible'}); } break; case 1: if (year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth() && day <= this.startDate.getUTCDate()) { this.picker.find('.prev').css({visibility: 'hidden'}); } else { this.picker.find('.prev').css({visibility: 'visible'}); } if (year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth() && day >= this.endDate.getUTCDate()) { this.picker.find('.next').css({visibility: 'hidden'}); } else { this.picker.find('.next').css({visibility: 'visible'}); } break; case 2: if (year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) { this.picker.find('.prev').css({visibility: 'hidden'}); } else { this.picker.find('.prev').css({visibility: 'visible'}); } if (year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) { this.picker.find('.next').css({visibility: 'hidden'}); } else { this.picker.find('.next').css({visibility: 'visible'}); } break; case 3: case 4: if (year <= this.startDate.getUTCFullYear()) { this.picker.find('.prev').css({visibility: 'hidden'}); } else { this.picker.find('.prev').css({visibility: 'visible'}); } if (year >= this.endDate.getUTCFullYear()) { this.picker.find('.next').css({visibility: 'hidden'}); } else { this.picker.find('.next').css({visibility: 'visible'}); } break; } }, mousewheel: function (e) { e.preventDefault(); e.stopPropagation(); if (this.wheelPause) { return; } this.wheelPause = true; var originalEvent = e.originalEvent; var delta = originalEvent.wheelDelta; var mode = delta > 0 ? 1 : (delta === 0) ? 0 : -1; if (this.wheelViewModeNavigationInverseDirection) { mode = -mode; } this.showMode(mode); setTimeout($.proxy(function () { this.wheelPause = false }, this), this.wheelViewModeNavigationDelay); }, click: function (e) { e.stopPropagation(); e.preventDefault(); var target = $(e.target).closest('span, td, th, legend'); if (target.is('.' + this.icontype)) { target = $(target).parent().closest('span, td, th, legend'); } if (target.length === 1) { if (target.is('.disabled')) { this.element.trigger({ type: 'outOfRange', date: this.viewDate, startDate: this.startDate, endDate: this.endDate }); return; } switch (target[0].nodeName.toLowerCase()) { case 'th': switch (target[0].className) { case 'switch': this.showMode(1); break; case 'prev': case 'next': var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1); switch (this.viewMode) { case 0: this.viewDate = this.moveHour(this.viewDate, dir); break; case 1: this.viewDate = this.moveDate(this.viewDate, dir); break; case 2: this.viewDate = this.moveMonth(this.viewDate, dir); break; case 3: case 4: this.viewDate = this.moveYear(this.viewDate, dir); break; } this.fill(); this.element.trigger({ type: target[0].className + ':' + this.convertViewModeText(this.viewMode), date: this.viewDate, startDate: this.startDate, endDate: this.endDate }); break; case 'clear': this.reset(); if (this.autoclose) { this.hide(); } break; case 'today': var date = new Date(); date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0); // Respect startDate and endDate. if (date < this.startDate) date = this.startDate; else if (date > this.endDate) date = this.endDate; this.viewMode = this.startViewMode; this.showMode(0); this._setDate(date); this.fill(); if (this.autoclose) { this.hide(); } break; } break; case 'span': if (!target.is('.disabled')) { var year = this.viewDate.getUTCFullYear(), month = this.viewDate.getUTCMonth(), day = this.viewDate.getUTCDate(), hours = this.viewDate.getUTCHours(), minutes = this.viewDate.getUTCMinutes(), seconds = this.viewDate.getUTCSeconds(); if (target.is('.month')) { this.viewDate.setUTCDate(1); month = target.parent().find('span').index(target); day = this.viewDate.getUTCDate(); this.viewDate.setUTCMonth(month); this.element.trigger({ type: 'changeMonth', date: this.viewDate }); if (this.viewSelect >= 3) { this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); } } else if (target.is('.year')) { this.viewDate.setUTCDate(1); year = parseInt(target.text(), 10) || 0; this.viewDate.setUTCFullYear(year); this.element.trigger({ type: 'changeYear', date: this.viewDate }); if (this.viewSelect >= 4) { this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); } } else if (target.is('.hour')) { hours = parseInt(target.text(), 10) || 0; if (target.hasClass('hour_am') || target.hasClass('hour_pm')) { if (hours === 12 && target.hasClass('hour_am')) { hours = 0; } else if (hours !== 12 && target.hasClass('hour_pm')) { hours += 12; } } this.viewDate.setUTCHours(hours); this.element.trigger({ type: 'changeHour', date: this.viewDate }); if (this.viewSelect >= 1) { this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); } } else if (target.is('.minute')) { minutes = parseInt(target.text().substr(target.text().indexOf(':') + 1), 10) || 0; this.viewDate.setUTCMinutes(minutes); this.element.trigger({ type: 'changeMinute', date: this.viewDate }); if (this.viewSelect >= 0) { this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); } } if (this.viewMode !== 0) { var oldViewMode = this.viewMode; this.showMode(-1); this.fill(); if (oldViewMode === this.viewMode && this.autoclose) { this.hide(); } } else { this.fill(); if (this.autoclose) { this.hide(); } } } break; case 'td': if (target.is('.day') && !target.is('.disabled')) { var day = parseInt(target.text(), 10) || 1; var year = this.viewDate.getUTCFullYear(), month = this.viewDate.getUTCMonth(), hours = this.viewDate.getUTCHours(), minutes = this.viewDate.getUTCMinutes(), seconds = this.viewDate.getUTCSeconds(); if (target.is('.old')) { if (month === 0) { month = 11; year -= 1; } else { month -= 1; } } else if (target.is('.new')) { if (month === 11) { month = 0; year += 1; } else { month += 1; } } this.viewDate.setUTCFullYear(year); this.viewDate.setUTCMonth(month, day); this.element.trigger({ type: 'changeDay', date: this.viewDate }); if (this.viewSelect >= 2) { this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0)); } } var oldViewMode = this.viewMode; this.showMode(-1); this.fill(); if (oldViewMode === this.viewMode && this.autoclose) { this.hide(); } break; } } }, _setDate: function (date, which) { if (!which || which === 'date') this.date = date; if (!which || which === 'view') this.viewDate = date; this.fill(); this.setValue(); var element; if (this.isInput) { element = this.element; } else if (this.component) { element = this.element.find('input'); } if (element) { element.change(); } this.element.trigger({ type: 'changeDate', date: this.getDate() }); if(date === null) this.date = this.viewDate; }, moveMinute: function (date, dir) { if (!dir) return date; var new_date = new Date(date.valueOf()); //dir = dir > 0 ? 1 : -1; new_date.setUTCMinutes(new_date.getUTCMinutes() + (dir * this.minuteStep)); return new_date; }, moveHour: function (date, dir) { if (!dir) return date; var new_date = new Date(date.valueOf()); //dir = dir > 0 ? 1 : -1; new_date.setUTCHours(new_date.getUTCHours() + dir); return new_date; }, moveDate: function (date, dir) { if (!dir) return date; var new_date = new Date(date.valueOf()); //dir = dir > 0 ? 1 : -1; new_date.setUTCDate(new_date.getUTCDate() + dir); return new_date; }, moveMonth: function (date, dir) { if (!dir) return date; var new_date = new Date(date.valueOf()), day = new_date.getUTCDate(), month = new_date.getUTCMonth(), mag = Math.abs(dir), new_month, test; dir = dir > 0 ? 1 : -1; if (mag === 1) { test = dir === -1 // If going back one month, make sure month is not current month // (eg, Mar 31 -> Feb 31 === Feb 28, not Mar 02) ? function () { return new_date.getUTCMonth() === month; } // If going forward one month, make sure month is as expected // (eg, Jan 31 -> Feb 31 === Feb 28, not Mar 02) : function () { return new_date.getUTCMonth() !== new_month; }; new_month = month + dir; new_date.setUTCMonth(new_month); // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 if (new_month < 0 || new_month > 11) new_month = (new_month + 12) % 12; } else { // For magnitudes >1, move one month at a time... for (var i = 0; i < mag; i++) // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... new_date = this.moveMonth(new_date, dir); // ...then reset the day, keeping it in the new month new_month = new_date.getUTCMonth(); new_date.setUTCDate(day); test = function () { return new_month !== new_date.getUTCMonth(); }; } // Common date-resetting loop -- if date is beyond end of month, make it // end of month while (test()) { new_date.setUTCDate(--day); new_date.setUTCMonth(new_month); } return new_date; }, moveYear: function (date, dir) { return this.moveMonth(date, dir * 12); }, dateWithinRange: function (date) { return date >= this.startDate && date <= this.endDate; }, keydown: function (e) { if (this.picker.is(':not(:visible)')) { if (e.keyCode === 27) // allow escape to hide and re-show picker this.show(); return; } var dateChanged = false, dir, newDate, newViewDate; switch (e.keyCode) { case 27: // escape this.hide(); e.preventDefault(); break; case 37: // left case 39: // right if (!this.keyboardNavigation) break; dir = e.keyCode === 37 ? -1 : 1; var viewMode = this.viewMode; if (e.ctrlKey) { viewMode += 2; } else if (e.shiftKey) { viewMode += 1; } if (viewMode === 4) { newDate = this.moveYear(this.date, dir); newViewDate = this.moveYear(this.viewDate, dir); } else if (viewMode === 3) { newDate = this.moveMonth(this.date, dir); newViewDate = this.moveMonth(this.viewDate, dir); } else if (viewMode === 2) { newDate = this.moveDate(this.date, dir); newViewDate = this.moveDate(this.viewDate, dir); } else if (viewMode === 1) { newDate = this.moveHour(this.date, dir); newViewDate = this.moveHour(this.viewDate, dir); } else if (viewMode === 0) { newDate = this.moveMinute(this.date, dir); newViewDate = this.moveMinute(this.viewDate, dir); } if (this.dateWithinRange(newDate)) { this.date = newDate; this.viewDate = newViewDate; this.setValue(); this.update(); e.preventDefault(); dateChanged = true; } break; case 38: // up case 40: // down if (!this.keyboardNavigation) break; dir = e.keyCode === 38 ? -1 : 1; viewMode = this.viewMode; if (e.ctrlKey) { viewMode += 2; } else if (e.shiftKey) { viewMode += 1; } if (viewMode === 4) { newDate = this.moveYear(this.date, dir); newViewDate = this.moveYear(this.viewDate, dir); } else if (viewMode === 3) { newDate = this.moveMonth(this.date, dir); newViewDate = this.moveMonth(this.viewDate, dir); } else if (viewMode === 2) { newDate = this.moveDate(this.date, dir * 7); newViewDate = this.moveDate(this.viewDate, dir * 7); } else if (viewMode === 1) { if (this.showMeridian) { newDate = this.moveHour(this.date, dir * 6); newViewDate = this.moveHour(this.viewDate, dir * 6); } else { newDate = this.moveHour(this.date, dir * 4); newViewDate = this.moveHour(this.viewDate, dir * 4); } } else if (viewMode === 0) { newDate = this.moveMinute(this.date, dir * 4); newViewDate = this.moveMinute(this.viewDate, dir * 4); } if (this.dateWithinRange(newDate)) { this.date = newDate; this.viewDate = newViewDate; this.setValue(); this.update(); e.preventDefault(); dateChanged = true; } break; case 13: // enter if (this.viewMode !== 0) { var oldViewMode = this.viewMode; this.showMode(-1); this.fill(); if (oldViewMode === this.viewMode && this.autoclose) { this.hide(); } } else { this.fill(); if (this.autoclose) { this.hide(); } } e.preventDefault(); break; case 9: // tab this.hide(); break; } if (dateChanged) { var element; if (this.isInput) { element = this.element; } else if (this.component) { element = this.element.find('input'); } if (element) { element.change(); } this.element.trigger({ type: 'changeDate', date: this.getDate() }); } }, showMode: function (dir) { if (dir) { var newViewMode = Math.max(0, Math.min(DPGlobal.modes.length - 1, this.viewMode + dir)); if (newViewMode >= this.minView && newViewMode <= this.maxView) { this.element.trigger({ type: 'changeMode', date: this.viewDate, oldViewMode: this.viewMode, newViewMode: newViewMode }); this.viewMode = newViewMode; } } /* vitalets: fixing bug of very special conditions: jquery 1.7.1 + webkit + show inline datetimepicker in bootstrap popover. Method show() does not set display css correctly and datetimepicker is not shown. Changed to .css('display', 'block') solve the problem. See https://github.com/vitalets/x-editable/issues/37 In jquery 1.7.2+ everything works fine. */ //this.picker.find('>div').hide().filter('.datetimepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); this.picker.find('>div').hide().filter('.datetimepicker-' + DPGlobal.modes[this.viewMode].clsName).css('display', 'block'); this.updateNavArrows(); }, reset: function () { this._setDate(null, 'date'); }, convertViewModeText: function (viewMode) { switch (viewMode) { case 4: return 'decade'; case 3: return 'year'; case 2: return 'month'; case 1: return 'day'; case 0: return 'hour'; } } }; var old = $.fn.datetimepicker; $.fn.datetimepicker = function (option) { var args = Array.apply(null, arguments); args.shift(); var internal_return; this.each(function () { var $this = $(this), data = $this.data('datetimepicker'), options = typeof option === 'object' && option; if (!data) { $this.data('datetimepicker', (data = new Datetimepicker(this, $.extend({}, $.fn.datetimepicker.defaults, options)))); } if (typeof option === 'string' && typeof data[option] === 'function') { internal_return = data[option].apply(data, args); if (internal_return !== undefined) { return false; } } }); if (internal_return !== undefined) return internal_return; else return this; }; $.fn.datetimepicker.defaults = { }; $.fn.datetimepicker.Constructor = Datetimepicker; var dates = $.fn.datetimepicker.dates = { 'zh-cn': { days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"], daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"], daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"], months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], meridiem: ["上午", "下午"], suffix: ["st", "nd", "rd", "th"], today: "今天", clear: "清除" }, en: { days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'], months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], meridiem: ['am', 'pm'], suffix: ['st', 'nd', 'rd', 'th'], today: 'Today', clear: 'Clear' } }; var DPGlobal = { modes: [ { clsName: 'minutes', navFnc: 'Hours', navStep: 1 }, { clsName: 'hours', navFnc: 'Date', navStep: 1 }, { clsName: 'days', navFnc: 'Month', navStep: 1 }, { clsName: 'months', navFnc: 'FullYear', navStep: 1 }, { clsName: 'years', navFnc: 'FullYear', navStep: 10 } ], isLeapYear: function (year) { return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) }, getDaysInMonth: function (year, month) { return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] }, getDefaultFormat: function (type, field) { if (type === 'standard') { if (field === 'input') return 'yyyy-mm-dd hh:ii'; else return 'yyyy-mm-dd hh:ii:ss'; } else if (type === 'php') { if (field === 'input') return 'Y-m-d H:i'; else return 'Y-m-d H:i:s'; } else { throw new Error('Invalid format type.'); } }, validParts: function (type) { if (type === 'standard') { return /t|hh?|HH?|p|P|z|Z|ii?|ss?|dd?|DD?|mm?|MM?|yy(?:yy)?/g; } else if (type === 'php') { return /[dDjlNwzFmMnStyYaABgGhHis]/g; } else { throw new Error('Invalid format type.'); } }, nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\rTZ]+/g, parseFormat: function (format, type) { // IE treats \0 as a string end in inputs (truncating the value), // so it's a bad format delimiter, anyway var separators = format.replace(this.validParts(type), '\0').split('\0'), parts = format.match(this.validParts(type)); if (!separators || !separators.length || !parts || parts.length === 0) { throw new Error('Invalid date format.'); } return {separators: separators, parts: parts}; }, parseDate: function (date, format, language, type, timezone) { if (date instanceof Date) { var dateUTC = new Date(date.valueOf() - date.getTimezoneOffset() * 60000); dateUTC.setMilliseconds(0); return dateUTC; } if (/^\d{4}\-\d{1,2}\-\d{1,2}$/.test(date)) { format = this.parseFormat('yyyy-mm-dd', type); } if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}$/.test(date)) { format = this.parseFormat('yyyy-mm-dd hh:ii', type); } if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}\:\d{1,2}[Z]{0,1}$/.test(date)) { format = this.parseFormat('yyyy-mm-dd hh:ii:ss', type); } if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) { var part_re = /([-+]\d+)([dmwy])/, parts = date.match(/([-+]\d+)([dmwy])/g), part, dir; date = new Date(); for (var i = 0; i < parts.length; i++) { part = part_re.exec(parts[i]); dir = parseInt(part[1]); switch (part[2]) { case 'd': date.setUTCDate(date.getUTCDate() + dir); break; case 'm': date = Datetimepicker.prototype.moveMonth.call(Datetimepicker.prototype, date, dir); break; case 'w': date.setUTCDate(date.getUTCDate() + dir * 7); break; case 'y': date = Datetimepicker.prototype.moveYear.call(Datetimepicker.prototype, date, dir); break; } } return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), 0); } var parts = date && date.toString().match(this.nonpunctuation) || [], date = new Date(0, 0, 0, 0, 0, 0, 0), parsed = {}, setters_order = ['hh', 'h', 'ii', 'i', 'ss', 's', 'yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'D', 'DD', 'd', 'dd', 'H', 'HH', 'p', 'P', 'z', 'Z'], setters_map = { hh: function (d, v) { return d.setUTCHours(v); }, h: function (d, v) { return d.setUTCHours(v); }, HH: function (d, v) { return d.setUTCHours(v === 12 ? 0 : v); }, H: function (d, v) { return d.setUTCHours(v === 12 ? 0 : v); }, ii: function (d, v) { return d.setUTCMinutes(v); }, i: function (d, v) { return d.setUTCMinutes(v); }, ss: function (d, v) { return d.setUTCSeconds(v); }, s: function (d, v) { return d.setUTCSeconds(v); }, yyyy: function (d, v) { return d.setUTCFullYear(v); }, yy: function (d, v) { return d.setUTCFullYear(2000 + v); }, m: function (d, v) { v -= 1; while (v < 0) v += 12; v %= 12; d.setUTCMonth(v); while (d.getUTCMonth() !== v) if (isNaN(d.getUTCMonth())) return d; else d.setUTCDate(d.getUTCDate() - 1); return d; }, d: function (d, v) { return d.setUTCDate(v); }, p: function (d, v) { return d.setUTCHours(v === 1 ? d.getUTCHours() + 12 : d.getUTCHours()); }, z: function () { return timezone } }, val, filtered, part; setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; setters_map['dd'] = setters_map['d']; setters_map['P'] = setters_map['p']; setters_map['Z'] = setters_map['z']; date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()); if (parts.length === format.parts.length) { for (var i = 0, cnt = format.parts.length; i < cnt; i++) { val = parseInt(parts[i], 10); part = format.parts[i]; if (isNaN(val)) { switch (part) { case 'MM': filtered = $(dates[language].months).filter(function () { var m = this.slice(0, parts[i].length), p = parts[i].slice(0, m.length); return m === p; }); val = $.inArray(filtered[0], dates[language].months) + 1; break; case 'M': filtered = $(dates[language].monthsShort).filter(function () { var m = this.slice(0, parts[i].length), p = parts[i].slice(0, m.length); return m.toLowerCase() === p.toLowerCase(); }); val = $.inArray(filtered[0], dates[language].monthsShort) + 1; break; case 'p': case 'P': val = $.inArray(parts[i].toLowerCase(), dates[language].meridiem); break; case 'z': case 'Z': timezone; break; } } parsed[part] = val; } for (var i = 0, s; i < setters_order.length; i++) { s = setters_order[i]; if (s in parsed && !isNaN(parsed[s])) setters_map[s](date, parsed[s]) } } return date; }, formatDate: function (date, format, language, type, timezone) { if (date === null) { return ''; } var val; if (type === 'standard') { val = { t: date.getTime(), // year yy: date.getUTCFullYear().toString().substring(2), yyyy: date.getUTCFullYear(), // month m: date.getUTCMonth() + 1, M: dates[language].monthsShort[date.getUTCMonth()], MM: dates[language].months[date.getUTCMonth()], // day d: date.getUTCDate(), D: dates[language].daysShort[date.getUTCDay()], DD: dates[language].days[date.getUTCDay()], p: (dates[language].meridiem.length === 2 ? dates[language].meridiem[date.getUTCHours() < 12 ? 0 : 1] : ''), // hour h: date.getUTCHours(), // minute i: date.getUTCMinutes(), // second s: date.getUTCSeconds(), // timezone z: timezone }; if (dates[language].meridiem.length === 2) { val.H = (val.h % 12 === 0 ? 12 : val.h % 12); } else { val.H = val.h; } val.HH = (val.H < 10 ? '0' : '') + val.H; val.P = val.p.toUpperCase(); val.Z = val.z; val.hh = (val.h < 10 ? '0' : '') + val.h; val.ii = (val.i < 10 ? '0' : '') + val.i; val.ss = (val.s < 10 ? '0' : '') + val.s; val.dd = (val.d < 10 ? '0' : '') + val.d; val.mm = (val.m < 10 ? '0' : '') + val.m; } else if (type === 'php') { // php format val = { // year y: date.getUTCFullYear().toString().substring(2), Y: date.getUTCFullYear(), // month F: dates[language].months[date.getUTCMonth()], M: dates[language].monthsShort[date.getUTCMonth()], n: date.getUTCMonth() + 1, t: DPGlobal.getDaysInMonth(date.getUTCFullYear(), date.getUTCMonth()), // day j: date.getUTCDate(), l: dates[language].days[date.getUTCDay()], D: dates[language].daysShort[date.getUTCDay()], w: date.getUTCDay(), // 0 -> 6 N: (date.getUTCDay() === 0 ? 7 : date.getUTCDay()), // 1 -> 7 S: (date.getUTCDate() % 10 <= dates[language].suffix.length ? dates[language].suffix[date.getUTCDate() % 10 - 1] : ''), // hour a: (dates[language].meridiem.length === 2 ? dates[language].meridiem[date.getUTCHours() < 12 ? 0 : 1] : ''), g: (date.getUTCHours() % 12 === 0 ? 12 : date.getUTCHours() % 12), G: date.getUTCHours(), // minute i: date.getUTCMinutes(), // second s: date.getUTCSeconds() }; val.m = (val.n < 10 ? '0' : '') + val.n; val.d = (val.j < 10 ? '0' : '') + val.j; val.A = val.a.toString().toUpperCase(); val.h = (val.g < 10 ? '0' : '') + val.g; val.H = (val.G < 10 ? '0' : '') + val.G; val.i = (val.i < 10 ? '0' : '') + val.i; val.s = (val.s < 10 ? '0' : '') + val.s; } else { throw new Error('Invalid format type.'); } var date = [], seps = $.extend([], format.separators); for (var i = 0, cnt = format.parts.length; i < cnt; i++) { if (seps.length) { date.push(seps.shift()); } date.push(val[format.parts[i]]); } if (seps.length) { date.push(seps.shift()); } return date.join(''); }, convertViewMode: function (viewMode) { switch (viewMode) { case 4: case 'decade': viewMode = 4; break; case 3: case 'year': viewMode = 3; break; case 2: case 'month': viewMode = 2; break; case 1: case 'day': viewMode = 1; break; case 0: case 'hour': viewMode = 0; break; } return viewMode; }, headTemplate: '' + '' + '' + '' + '' + '' + '', headTemplateV3: '' + '' + ' ' + '' + ' ' + '' + '', contTemplate: '', footTemplate: '' + '' + '' + '' }; DPGlobal.template = '
    ' + '
    ' + '' + DPGlobal.headTemplate + DPGlobal.contTemplate + DPGlobal.footTemplate + '
    ' + '
    ' + '
    ' + '' + DPGlobal.headTemplate + DPGlobal.contTemplate + DPGlobal.footTemplate + '
    ' + '
    ' + '
    ' + '' + DPGlobal.headTemplate + '' + DPGlobal.footTemplate + '
    ' + '
    ' + '
    ' + '' + DPGlobal.headTemplate + DPGlobal.contTemplate + DPGlobal.footTemplate + '
    ' + '
    ' + '
    ' + '' + DPGlobal.headTemplate + DPGlobal.contTemplate + DPGlobal.footTemplate + '
    ' + '
    ' + '
    '; DPGlobal.templateV3 = '
    ' + '
    ' + '' + DPGlobal.headTemplateV3 + DPGlobal.contTemplate + DPGlobal.footTemplate + '
    ' + '
    ' + '
    ' + '' + DPGlobal.headTemplateV3 + DPGlobal.contTemplate + DPGlobal.footTemplate + '
    ' + '
    ' + '
    ' + '' + DPGlobal.headTemplateV3 + '' + DPGlobal.footTemplate + '
    ' + '
    ' + '
    ' + '' + DPGlobal.headTemplateV3 + DPGlobal.contTemplate + DPGlobal.footTemplate + '
    ' + '
    ' + '
    ' + '' + DPGlobal.headTemplateV3 + DPGlobal.contTemplate + DPGlobal.footTemplate + '
    ' + '
    ' + '
    '; $.fn.datetimepicker.DPGlobal = DPGlobal; /* DATETIMEPICKER NO CONFLICT * =================== */ $.fn.datetimepicker.noConflict = function () { $.fn.datetimepicker = old; return this; }; /* DATETIMEPICKER DATA-API * ================== */ $(document).on( 'focus.datetimepicker.data-api click.datetimepicker.data-api', '[data-provide="datetimepicker"]', function (e) { var $this = $(this); if ($this.data('datetimepicker')) return; e.preventDefault(); // component click requires us to explicitly show it $this.datetimepicker('show'); } ); $(function () { $('[data-provide="datetimepicker-inline"]').datetimepicker(); }); })); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.css ================================================ /* * Bootstrap Duallistbox - v3.0.9 * A responsive dual listbox widget optimized for Twitter Bootstrap. It works on all modern browsers and on touch devices. * https://www.virtuosoft.eu/code/bootstrap-duallistbox/ * * Made by István Ujj-Mészáros * Under Apache License v2.0 License */ .bootstrap-duallistbox-container .buttons { width: 100%; margin-bottom: -1px; } .bootstrap-duallistbox-container label { display: block; } .bootstrap-duallistbox-container .info { display: inline-block; margin-bottom: 5px; font-size: 11px; } .bootstrap-duallistbox-container .clear1, .bootstrap-duallistbox-container .clear2 { display: none; font-size: 10px; } .bootstrap-duallistbox-container .box1.filtered .clear1, .bootstrap-duallistbox-container .box2.filtered .clear2 { display: inline-block; } .bootstrap-duallistbox-container .move, .bootstrap-duallistbox-container .remove { width: 60%; } .bootstrap-duallistbox-container .btn-group .btn { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .bootstrap-duallistbox-container select { border-top-left-radius: 0; border-top-right-radius: 0; } .bootstrap-duallistbox-container .moveall, .bootstrap-duallistbox-container .removeall { width: 40%; } .bootstrap-duallistbox-container.bs2compatible .btn-group > .btn + .btn { margin-left: 0; } .bootstrap-duallistbox-container select { width: 100%; height: 300px; padding: 0; } .bootstrap-duallistbox-container .filter { display: inline-block; width: 100%; height: 31px; margin: 0 0 5px 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .bootstrap-duallistbox-container .filter.placeholder { color: #aaa; } .bootstrap-duallistbox-container.moveonselect .move, .bootstrap-duallistbox-container.moveonselect .remove { display:none; } .bootstrap-duallistbox-container.moveonselect .moveall, .bootstrap-duallistbox-container.moveonselect .removeall { width: 100%; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/duallistbox/bootstrap-duallistbox.js ================================================ /* * Bootstrap Duallistbox - v3.0.9 * A responsive dual listbox widget optimized for Twitter Bootstrap. It works on all modern browsers and on touch devices. * https://www.virtuosoft.eu/code/bootstrap-duallistbox/ * * Made by István Ujj-Mészáros * Under Apache License v2.0 License */ ;(function ($, window, document, undefined) { // Create the defaults once var pluginName = 'bootstrapDualListbox', defaults = { bootstrap2Compatible: false, filterTextClear: 'show all', filterPlaceHolder: 'Filter', moveSelectedLabel: 'Move selected', moveAllLabel: 'Move all', removeSelectedLabel: 'Remove selected', removeAllLabel: 'Remove all', moveOnSelect: true, // true/false (forced true on androids, see the comment later) moveOnDoubleClick: true, // true/false (forced false on androids, cause moveOnSelect is forced to true) preserveSelectionOnMove: false, // 'all' / 'moved' / false selectedListLabel: false, // 'string', false nonSelectedListLabel: false, // 'string', false helperSelectNamePostfix: '_helper', // 'string_of_postfix' / false selectorMinimalHeight: 100, showFilterInputs: true, // whether to show filter inputs nonSelectedFilter: '', // string, filter the non selected options selectedFilter: '', // string, filter the selected options infoText: 'Showing all {0}', // text when all options are visible / false for no info text infoTextFiltered: 'Filtered {0} from {1}', // when not all of the options are visible due to the filter infoTextEmpty: 'Empty list', // when there are no options present in the list filterOnValues: false, // filter by selector's values, boolean sortByInputOrder: false, eventMoveOverride: false, // boolean, allows user to unbind default event behaviour and run their own instead eventMoveAllOverride: false, // boolean, allows user to unbind default event behaviour and run their own instead eventRemoveOverride: false, // boolean, allows user to unbind default event behaviour and run their own instead eventRemoveAllOverride: false // boolean, allows user to unbind default event behaviour and run their own instead }, // Selections are invisible on android if the containing select is styled with CSS // http://code.google.com/p/android/issues/detail?id=16922 isBuggyAndroid = /android/i.test(navigator.userAgent.toLowerCase()); // The actual plugin constructor function BootstrapDualListbox(element, options) { this.element = $(element); // jQuery has an extend method which merges the contents of two or // more objects, storing the result in the first object. The first object // is generally empty as we don't want to alter the default options for // future instances of the plugin this.settings = $.extend({}, defaults, options); this._defaults = defaults; this._name = pluginName; this.init(); } function triggerChangeEvent(dualListbox) { dualListbox.element.trigger('change'); } function updateSelectionStates(dualListbox) { dualListbox.element.find('option').each(function(index, item) { var $item = $(item); if (typeof($item.data('original-index')) === 'undefined') { $item.data('original-index', dualListbox.elementCount++); } if (typeof($item.data('_selected')) === 'undefined') { $item.data('_selected', false); } }); } function changeSelectionState(dualListbox, original_index, selected) { dualListbox.element.find('option').each(function(index, item) { var $item = $(item); if ($item.data('original-index') === original_index) { $item.prop('selected', selected); if(selected){ $item.attr('data-sortindex', dualListbox.sortIndex); dualListbox.sortIndex++; } else { $item.removeAttr('data-sortindex'); } } }); } function formatString(s, args) { return s.replace(/\{(\d+)\}/g, function(match, number) { return typeof args[number] !== 'undefined' ? args[number] : match; }); } function refreshInfo(dualListbox) { if (!dualListbox.settings.infoText) { return; } var visible1 = dualListbox.elements.select1.find('option').length, visible2 = dualListbox.elements.select2.find('option').length, all1 = dualListbox.element.find('option').length - dualListbox.selectedElements, all2 = dualListbox.selectedElements, content = ''; if (all1 === 0) { content = dualListbox.settings.infoTextEmpty; } else if (visible1 === all1) { content = formatString(dualListbox.settings.infoText, [visible1, all1]); } else { content = formatString(dualListbox.settings.infoTextFiltered, [visible1, all1]); } dualListbox.elements.info1.html(content); dualListbox.elements.box1.toggleClass('filtered', !(visible1 === all1 || all1 === 0)); if (all2 === 0) { content = dualListbox.settings.infoTextEmpty; } else if (visible2 === all2) { content = formatString(dualListbox.settings.infoText, [visible2, all2]); } else { content = formatString(dualListbox.settings.infoTextFiltered, [visible2, all2]); } dualListbox.elements.info2.html(content); dualListbox.elements.box2.toggleClass('filtered', !(visible2 === all2 || all2 === 0)); } function refreshSelects(dualListbox) { dualListbox.selectedElements = 0; dualListbox.elements.select1.empty(); dualListbox.elements.select2.empty(); dualListbox.element.find('option').each(function(index, item) { var $item = $(item); if ($item.prop('selected')) { dualListbox.selectedElements++; dualListbox.elements.select2.append($item.clone(true).prop('selected', $item.data('_selected'))); } else { dualListbox.elements.select1.append($item.clone(true).prop('selected', $item.data('_selected'))); } }); if (dualListbox.settings.showFilterInputs) { filter(dualListbox, 1); filter(dualListbox, 2); } refreshInfo(dualListbox); } function filter(dualListbox, selectIndex) { if (!dualListbox.settings.showFilterInputs) { return; } saveSelections(dualListbox, selectIndex); dualListbox.elements['select'+selectIndex].empty().scrollTop(0); var regex = new RegExp($.trim(dualListbox.elements['filterInput'+selectIndex].val()), 'gi'), allOptions = dualListbox.element.find('option'), options = dualListbox.element; if (selectIndex === 1) { options = allOptions.not(':selected'); } else { options = options.find('option:selected'); } options.each(function(index, item) { var $item = $(item), isFiltered = true; if (item.text.match(regex) || (dualListbox.settings.filterOnValues && $item.attr('value').match(regex) ) ) { isFiltered = false; dualListbox.elements['select'+selectIndex].append($item.clone(true).prop('selected', $item.data('_selected'))); } allOptions.eq($item.data('original-index')).data('filtered'+selectIndex, isFiltered); }); refreshInfo(dualListbox); } function saveSelections(dualListbox, selectIndex) { var options = dualListbox.element.find('option'); dualListbox.elements['select'+selectIndex].find('option').each(function(index, item) { var $item = $(item); options.eq($item.data('original-index')).data('_selected', $item.prop('selected')); }); } function sortOptionsByInputOrder(select){ var selectopt = select.children('option'); selectopt.sort(function(a,b){ var an = parseInt(a.getAttribute('data-sortindex')), bn = parseInt(b.getAttribute('data-sortindex')); if(an > bn) { return 1; } if(an < bn) { return -1; } return 0; }); selectopt.detach().appendTo(select); } function sortOptions(select, dualListbox) { select.find('option').sort(function(a, b) { return ($(a).data('original-index') > $(b).data('original-index')) ? 1 : -1; }).appendTo(select); // workaround for chromium bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1072475 refreshSelects(dualListbox); } function clearSelections(dualListbox) { dualListbox.elements.select1.find('option').each(function() { dualListbox.element.find('option').data('_selected', false); }); } function move(dualListbox) { if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) { saveSelections(dualListbox, 1); saveSelections(dualListbox, 2); } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) { saveSelections(dualListbox, 1); } dualListbox.elements.select1.find('option:selected').each(function(index, item) { var $item = $(item); if (!$item.data('filtered1')) { changeSelectionState(dualListbox, $item.data('original-index'), true); } }); refreshSelects(dualListbox); triggerChangeEvent(dualListbox); if(dualListbox.settings.sortByInputOrder){ sortOptionsByInputOrder(dualListbox.elements.select2); } else { sortOptions(dualListbox.elements.select2, dualListbox); } } function remove(dualListbox) { if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) { saveSelections(dualListbox, 1); saveSelections(dualListbox, 2); } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) { saveSelections(dualListbox, 2); } dualListbox.elements.select2.find('option:selected').each(function(index, item) { var $item = $(item); if (!$item.data('filtered2')) { changeSelectionState(dualListbox, $item.data('original-index'), false); } }); refreshSelects(dualListbox); triggerChangeEvent(dualListbox); sortOptions(dualListbox.elements.select1, dualListbox); if(dualListbox.settings.sortByInputOrder){ sortOptionsByInputOrder(dualListbox.elements.select2); } } function moveAll(dualListbox) { if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) { saveSelections(dualListbox, 1); saveSelections(dualListbox, 2); } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) { saveSelections(dualListbox, 1); } dualListbox.element.find('option').each(function(index, item) { var $item = $(item); if (!$item.data('filtered1')) { $item.prop('selected', true); $item.attr('data-sortindex', dualListbox.sortIndex); dualListbox.sortIndex++; } }); refreshSelects(dualListbox); triggerChangeEvent(dualListbox); } function removeAll(dualListbox) { if (dualListbox.settings.preserveSelectionOnMove === 'all' && !dualListbox.settings.moveOnSelect) { saveSelections(dualListbox, 1); saveSelections(dualListbox, 2); } else if (dualListbox.settings.preserveSelectionOnMove === 'moved' && !dualListbox.settings.moveOnSelect) { saveSelections(dualListbox, 2); } dualListbox.element.find('option').each(function(index, item) { var $item = $(item); if (!$item.data('filtered2')) { $item.prop('selected', false); $item.removeAttr('data-sortindex'); } }); refreshSelects(dualListbox); triggerChangeEvent(dualListbox); } function bindEvents(dualListbox) { dualListbox.elements.form.submit(function(e) { if (dualListbox.elements.filterInput1.is(':focus')) { e.preventDefault(); dualListbox.elements.filterInput1.focusout(); } else if (dualListbox.elements.filterInput2.is(':focus')) { e.preventDefault(); dualListbox.elements.filterInput2.focusout(); } }); dualListbox.element.on('bootstrapDualListbox.refresh', function(e, mustClearSelections){ dualListbox.refresh(mustClearSelections); }); dualListbox.elements.filterClear1.on('click', function() { dualListbox.setNonSelectedFilter('', true); }); dualListbox.elements.filterClear2.on('click', function() { dualListbox.setSelectedFilter('', true); }); if (dualListbox.settings.eventMoveOverride === false) { dualListbox.elements.moveButton.on('click', function() { move(dualListbox); }); } if (dualListbox.settings.eventMoveAllOverride === false) { dualListbox.elements.moveAllButton.on('click', function() { moveAll(dualListbox); }); } if (dualListbox.settings.eventRemoveOverride === false) { dualListbox.elements.removeButton.on('click', function() { remove(dualListbox); }); } if (dualListbox.settings.eventRemoveAllOverride === false) { dualListbox.elements.removeAllButton.on('click', function() { removeAll(dualListbox); }); } dualListbox.elements.filterInput1.on('change keyup', function() { filter(dualListbox, 1); }); dualListbox.elements.filterInput2.on('change keyup', function() { filter(dualListbox, 2); }); } BootstrapDualListbox.prototype = { init: function () { // Add the custom HTML template this.container = $('' + '
    ' + '
    ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '
    ' + ' ' + ' ' + '
    ' + ' ' + '
    ' + '
    ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '
    ' + ' ' + ' ' + '
    ' + ' ' + '
    ' + '
    ') .insertBefore(this.element); // Cache the inner elements this.elements = { originalSelect: this.element, box1: $('.box1', this.container), box2: $('.box2', this.container), filterInput1: $('.box1 .filter', this.container), filterInput2: $('.box2 .filter', this.container), filterClear1: $('.box1 .clear1', this.container), filterClear2: $('.box2 .clear2', this.container), label1: $('.box1 > label', this.container), label2: $('.box2 > label', this.container), info1: $('.box1 .info', this.container), info2: $('.box2 .info', this.container), select1: $('.box1 select', this.container), select2: $('.box2 select', this.container), moveButton: $('.box1 .move', this.container), removeButton: $('.box2 .remove', this.container), moveAllButton: $('.box1 .moveall', this.container), removeAllButton: $('.box2 .removeall', this.container), form: $($('.box1 .filter', this.container)[0].form) }; // Set select IDs this.originalSelectName = this.element.attr('name') || ''; var select1Id = 'bootstrap-duallistbox-nonselected-list_' + this.originalSelectName, select2Id = 'bootstrap-duallistbox-selected-list_' + this.originalSelectName; this.elements.select1.attr('id', select1Id); this.elements.select2.attr('id', select2Id); this.elements.label1.attr('for', select1Id); this.elements.label2.attr('for', select2Id); // Apply all settings this.selectedElements = 0; this.sortIndex = 0; this.elementCount = 0; this.setBootstrap2Compatible(this.settings.bootstrap2Compatible); this.setFilterTextClear(this.settings.filterTextClear); this.setFilterPlaceHolder(this.settings.filterPlaceHolder); this.setMoveSelectedLabel(this.settings.moveSelectedLabel); this.setMoveAllLabel(this.settings.moveAllLabel); this.setRemoveSelectedLabel(this.settings.removeSelectedLabel); this.setRemoveAllLabel(this.settings.removeAllLabel); this.setMoveOnSelect(this.settings.moveOnSelect); this.setMoveOnDoubleClick(this.settings.moveOnDoubleClick); this.setPreserveSelectionOnMove(this.settings.preserveSelectionOnMove); this.setSelectedListLabel(this.settings.selectedListLabel); this.setNonSelectedListLabel(this.settings.nonSelectedListLabel); this.setHelperSelectNamePostfix(this.settings.helperSelectNamePostfix); this.setSelectOrMinimalHeight(this.settings.selectorMinimalHeight); updateSelectionStates(this); this.setShowFilterInputs(this.settings.showFilterInputs); this.setNonSelectedFilter(this.settings.nonSelectedFilter); this.setSelectedFilter(this.settings.selectedFilter); this.setInfoText(this.settings.infoText); this.setInfoTextFiltered(this.settings.infoTextFiltered); this.setInfoTextEmpty(this.settings.infoTextEmpty); this.setFilterOnValues(this.settings.filterOnValues); this.setSortByInputOrder(this.settings.sortByInputOrder); this.setEventMoveOverride(this.settings.eventMoveOverride); this.setEventMoveAllOverride(this.settings.eventMoveAllOverride); this.setEventRemoveOverride(this.settings.eventRemoveOverride); this.setEventRemoveAllOverride(this.settings.eventRemoveAllOverride); // Hide the original select this.element.hide(); bindEvents(this); refreshSelects(this); return this.element; }, setBootstrap2Compatible: function(value, refresh) { this.settings.bootstrap2Compatible = value; if (value) { this.container.removeClass('row').addClass('row-fluid bs2compatible'); this.container.find('.box1, .box2').removeClass('col-md-6').addClass('span6'); this.container.find('.clear1, .clear2').removeClass('btn-default btn-xs').addClass('btn-mini'); this.container.find('input, select').removeClass('form-control'); this.container.find('.btn').removeClass('btn-default'); this.container.find('.moveall > i, .move > i').removeClass('glyphicon glyphicon-arrow-right').addClass('icon-arrow-right'); this.container.find('.removeall > i, .remove > i').removeClass('glyphicon glyphicon-arrow-left').addClass('icon-arrow-left'); } else { this.container.removeClass('row-fluid bs2compatible').addClass('row'); this.container.find('.box1, .box2').removeClass('span6').addClass('col-md-6'); this.container.find('.clear1, .clear2').removeClass('btn-mini').addClass('btn-default btn-xs'); this.container.find('input, select').addClass('form-control'); this.container.find('.btn').addClass('btn-default'); this.container.find('.moveall > i, .move > i').removeClass('icon-arrow-right').addClass('glyphicon glyphicon-arrow-right'); this.container.find('.removeall > i, .remove > i').removeClass('icon-arrow-left').addClass('glyphicon glyphicon-arrow-left'); } if (refresh) { refreshSelects(this); } return this.element; }, setFilterTextClear: function(value, refresh) { this.settings.filterTextClear = value; this.elements.filterClear1.html(value); this.elements.filterClear2.html(value); if (refresh) { refreshSelects(this); } return this.element; }, setFilterPlaceHolder: function(value, refresh) { this.settings.filterPlaceHolder = value; this.elements.filterInput1.attr('placeholder', value); this.elements.filterInput2.attr('placeholder', value); if (refresh) { refreshSelects(this); } return this.element; }, setMoveSelectedLabel: function(value, refresh) { this.settings.moveSelectedLabel = value; this.elements.moveButton.attr('title', value); if (refresh) { refreshSelects(this); } return this.element; }, setMoveAllLabel: function(value, refresh) { this.settings.moveAllLabel = value; this.elements.moveAllButton.attr('title', value); if (refresh) { refreshSelects(this); } return this.element; }, setRemoveSelectedLabel: function(value, refresh) { this.settings.removeSelectedLabel = value; this.elements.removeButton.attr('title', value); if (refresh) { refreshSelects(this); } return this.element; }, setRemoveAllLabel: function(value, refresh) { this.settings.removeAllLabel = value; this.elements.removeAllButton.attr('title', value); if (refresh) { refreshSelects(this); } return this.element; }, setMoveOnSelect: function(value, refresh) { if (isBuggyAndroid) { value = true; } this.settings.moveOnSelect = value; if (this.settings.moveOnSelect) { this.container.addClass('moveonselect'); var self = this; this.elements.select1.on('change', function() { move(self); }); this.elements.select2.on('change', function() { remove(self); }); } else { this.container.removeClass('moveonselect'); this.elements.select1.off('change'); this.elements.select2.off('change'); } if (refresh) { refreshSelects(this); } return this.element; }, setMoveOnDoubleClick: function(value, refresh) { if (isBuggyAndroid) { value = false; } this.settings.moveOnDoubleClick = value; if (this.settings.moveOnDoubleClick) { this.container.addClass('moveondoubleclick'); var self = this; this.elements.select1.on('dblclick', function() { move(self); }); this.elements.select2.on('dblclick', function() { remove(self); }); } else { this.container.removeClass('moveondoubleclick'); this.elements.select1.off('dblclick'); this.elements.select2.off('dblclick'); } if (refresh) { refreshSelects(this); } return this.element; }, setPreserveSelectionOnMove: function(value, refresh) { // We are forcing to move on select and disabling preserveSelectionOnMove on Android if (isBuggyAndroid) { value = false; } this.settings.preserveSelectionOnMove = value; if (refresh) { refreshSelects(this); } return this.element; }, setSelectedListLabel: function(value, refresh) { this.settings.selectedListLabel = value; if (value) { this.elements.label2.show().html(value); } else { this.elements.label2.hide().html(value); } if (refresh) { refreshSelects(this); } return this.element; }, setNonSelectedListLabel: function(value, refresh) { this.settings.nonSelectedListLabel = value; if (value) { this.elements.label1.show().html(value); } else { this.elements.label1.hide().html(value); } if (refresh) { refreshSelects(this); } return this.element; }, setHelperSelectNamePostfix: function(value, refresh) { this.settings.helperSelectNamePostfix = value; if (value) { this.elements.select1.attr('name', this.originalSelectName + value + '1'); this.elements.select2.attr('name', this.originalSelectName + value + '2'); } else { this.elements.select1.removeAttr('name'); this.elements.select2.removeAttr('name'); } if (refresh) { refreshSelects(this); } return this.element; }, setSelectOrMinimalHeight: function(value, refresh) { this.settings.selectorMinimalHeight = value; var height = this.element.height(); if (this.element.height() < value) { height = value; } this.elements.select1.height(height); this.elements.select2.height(height); if (refresh) { refreshSelects(this); } return this.element; }, setShowFilterInputs: function(value, refresh) { if (!value) { this.setNonSelectedFilter(''); this.setSelectedFilter(''); refreshSelects(this); this.elements.filterInput1.hide(); this.elements.filterInput2.hide(); } else { this.elements.filterInput1.show(); this.elements.filterInput2.show(); } this.settings.showFilterInputs = value; if (refresh) { refreshSelects(this); } return this.element; }, setNonSelectedFilter: function(value, refresh) { if (this.settings.showFilterInputs) { this.settings.nonSelectedFilter = value; this.elements.filterInput1.val(value); if (refresh) { refreshSelects(this); } return this.element; } }, setSelectedFilter: function(value, refresh) { if (this.settings.showFilterInputs) { this.settings.selectedFilter = value; this.elements.filterInput2.val(value); if (refresh) { refreshSelects(this); } return this.element; } }, setInfoText: function(value, refresh) { this.settings.infoText = value; if (value) { this.elements.info1.show(); this.elements.info2.show(); } else { this.elements.info1.hide(); this.elements.info2.hide(); } if (refresh) { refreshSelects(this); } return this.element; }, setInfoTextFiltered: function(value, refresh) { this.settings.infoTextFiltered = value; if (refresh) { refreshSelects(this); } return this.element; }, setInfoTextEmpty: function(value, refresh) { this.settings.infoTextEmpty = value; if (refresh) { refreshSelects(this); } return this.element; }, setFilterOnValues: function(value, refresh) { this.settings.filterOnValues = value; if (refresh) { refreshSelects(this); } return this.element; }, setSortByInputOrder: function(value, refresh){ this.settings.sortByInputOrder = value; if (refresh) { refreshSelects(this); } return this.element; }, setEventMoveOverride: function(value, refresh) { this.settings.eventMoveOverride = value; if (refresh) { refreshSelects(this); } return this.element; }, setEventMoveAllOverride: function(value, refresh) { this.settings.eventMoveAllOverride = value; if (refresh) { refreshSelects(this); } return this.element; }, setEventRemoveOverride: function(value, refresh) { this.settings.eventRemoveOverride = value; if (refresh) { refreshSelects(this); } return this.element; }, setEventRemoveAllOverride: function(value, refresh) { this.settings.eventRemoveAllOverride = value; if (refresh) { refreshSelects(this); } return this.element; }, getContainer: function() { return this.container; }, refresh: function(mustClearSelections) { updateSelectionStates(this); if (!mustClearSelections) { saveSelections(this, 1); saveSelections(this, 2); } else { clearSelections(this); } refreshSelects(this); }, destroy: function() { this.container.remove(); this.element.show(); $.data(this, 'plugin_' + pluginName, null); return this.element; } }; // A really lightweight plugin wrapper around the constructor, // preventing against multiple instantiations $.fn[ pluginName ] = function (options) { var args = arguments; // Is the first parameter an object (options), or was omitted, instantiate a new instance of the plugin. if (options === undefined || typeof options === 'object') { return this.each(function () { // If this is not a select if (!$(this).is('select')) { $(this).find('select').each(function(index, item) { // For each nested select, instantiate the Dual List Box $(item).bootstrapDualListbox(options); }); } else if (!$.data(this, 'plugin_' + pluginName)) { // Only allow the plugin to be instantiated once so we check that the element has no plugin instantiation yet // if it has no instance, create a new one, pass options to our plugin constructor, // and store the plugin instance in the elements jQuery data object. $.data(this, 'plugin_' + pluginName, new BootstrapDualListbox(this, options)); } }); // If the first parameter is a string and it doesn't start with an underscore or "contains" the `init`-function, // treat this as a call to a public method. } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { // Cache the method call to make it possible to return a value var returns; this.each(function () { var instance = $.data(this, 'plugin_' + pluginName); // Tests that there's already a plugin-instance and checks that the requested public method exists if (instance instanceof BootstrapDualListbox && typeof instance[options] === 'function') { // Call the method of our plugin instance, and pass it the supplied arguments. returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1)); } }); // If the earlier cached method gives a value back return the value, // otherwise return this to preserve chainability. return returns !== undefined ? returns : this; } }; })(jQuery, window, document); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/flot/curvedLines.js ================================================ /* The MIT License Copyright (c) 2011 by Michael Zinsmaier and nergal.dev Copyright (c) 2012 by Thomas Ritou Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* ____________________________________________________ what it is: ____________________________________________________ curvedLines is a plugin for flot, that tries to display lines in a smoother way. The plugin is based on nergal.dev's work https://code.google.com/p/flot/issues/detail?id=226 and further extended with a mode that forces the min/max points of the curves to be on the points. Both modes are achieved through adding of more data points => 1) with large data sets you may get trouble => 2) if you want to display the points too, you have to plot them as 2nd data series over the lines && 3) consecutive x data points are not allowed to have the same value This is version 0.5 of curvedLines so it will probably not work in every case. However the basic form of use descirbed next works (: Feel free to further improve the code ____________________________________________________ how to use it: ____________________________________________________ var d1 = [[5,5],[7,3],[9,12]]; var options = { series: { curvedLines: { active: true }}}; $.plot($("#placeholder"), [{data = d1, lines: { show: true}, curvedLines: {apply: true}}], options); _____________________________________________________ options: _____________________________________________________ active: bool true => plugin can be used apply: bool true => series will be drawn as curved line fit: bool true => forces the max,mins of the curve to be on the datapoints curvePointFactor int defines how many "virtual" points are used per "real" data point to emulate the curvedLines (points total = real points * curvePointFactor) fitPointDist: int defines the x axis distance of the additional two points that are used to enforce the min max condition. + line options (since v0.5 curved lines use flots line implementation for drawing => line options like fill, show ... are supported out of the box) */ /* * v0.1 initial commit * v0.15 negative values should work now (outcommented a negative -> 0 hook hope it does no harm) * v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi) * v0.3 improved saddle handling and added basic handling of Dates * v0.4 rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug * v0.5 rewritten instead of implementing a own draw function CurvedLines is now based on the processDatapoints flot hook (credits go to thomas ritou). * This change breakes existing code however CurvedLines are now just many tiny straight lines to flot and therefore all flot lines options (like gradient fill, * shadow) are now supported out of the box * v0.6 flot 0.8 compatibility and some bug fixes */ (function($) { var options = { series : { curvedLines : { active : false, apply: false, fit : false, curvePointFactor : 20, fitPointDist : undefined } } }; function init(plot) { plot.hooks.processOptions.push(processOptions); //if the plugin is active register processDatapoints method function processOptions(plot, options) { if (options.series.curvedLines.active) { plot.hooks.processDatapoints.unshift(processDatapoints); } } //only if the plugin is active function processDatapoints(plot, series, datapoints) { var nrPoints = datapoints.points.length / datapoints.pointsize; var EPSILON = 0.5; //pretty large epsilon but save if (series.curvedLines.apply == true && series.originSeries === undefined && nrPoints > (1 + EPSILON)) { if (series.lines.fill) { var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1) ,pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2); //flot makes sure for us that we've got a second y point if fill is true ! //Merge top and bottom curve datapoints.pointsize = 3; datapoints.points = []; var j = 0; var k = 0; var i = 0; var ps = 2; while (i < pointsTop.length || j < pointsBottom.length) { if (pointsTop[i] == pointsBottom[j]) { datapoints.points[k] = pointsTop[i]; datapoints.points[k + 1] = pointsTop[i + 1]; datapoints.points[k + 2] = pointsBottom[j + 1]; j += ps; i += ps; } else if (pointsTop[i] < pointsBottom[j]) { datapoints.points[k] = pointsTop[i]; datapoints.points[k + 1] = pointsTop[i + 1]; datapoints.points[k + 2] = k > 0 ? datapoints.points[k-1] : null; i += ps; } else { datapoints.points[k] = pointsBottom[j]; datapoints.points[k + 1] = k > 1 ? datapoints.points[k-2] : null; datapoints.points[k + 2] = pointsBottom[j + 1]; j += ps; } k += 3; } } else if (series.lines.lineWidth > 0) { datapoints.points = calculateCurvePoints(datapoints, series.curvedLines, 1); datapoints.pointsize = 2; } } } //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226 //if fit option is selected additional datapoints get inserted before the curve calculations in nergal.dev s code. function calculateCurvePoints(datapoints, curvedLinesOptions, yPos) { var points = datapoints.points, ps = datapoints.pointsize; var num = curvedLinesOptions.curvePointFactor * (points.length / ps); var xdata = new Array; var ydata = new Array; var curX = -1; var curY = -1; var j = 0; if (curvedLinesOptions.fit) { //insert a point before and after the "real" data point to force the line //to have a max,min at the data point. var fpDist; if(typeof curvedLinesOptions.fitPointDist == 'undefined') { //estimate it var minX = points[0]; var maxX = points[points.length-ps]; fpDist = (maxX - minX) / (500 * 100); //x range / (estimated pixel length of placeholder * factor) } else { //use user defined value fpDist = curvedLinesOptions.fitPointDist; } for (var i = 0; i < points.length; i += ps) { var frontX; var backX; curX = i; curY = i + yPos; //add point X s frontX = points[curX] - fpDist; backX = points[curX] + fpDist; var factor = 2; while (frontX == points[curX] || backX == points[curX]) { //inside the ulp frontX = points[curX] - (fpDist * factor); backX = points[curX] + (fpDist * factor); factor++; } //add curve points xdata[j] = frontX; ydata[j] = points[curY]; j++; xdata[j] = points[curX]; ydata[j] = points[curY]; j++; xdata[j] = backX; ydata[j] = points[curY]; j++; } } else { //just use the datapoints for (var i = 0; i < points.length; i += ps) { curX = i; curY = i + yPos; xdata[j] = points[curX]; ydata[j] = points[curY]; j++; } } var n = xdata.length; var y2 = new Array(); var delta = new Array(); y2[0] = 0; y2[n - 1] = 0; delta[0] = 0; for (var i = 1; i < n - 1; ++i) { var d = (xdata[i + 1] - xdata[i - 1]); if (d == 0) { //point before current point and after current point need some space in between return []; } var s = (xdata[i] - xdata[i - 1]) / d; var p = s * y2[i - 1] + 2; y2[i] = (s - 1) / p; delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]); delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p; } for (var j = n - 2; j >= 0; --j) { y2[j] = y2[j] * y2[j + 1] + delta[j]; } // xmax - xmin / #points var step = (xdata[n - 1] - xdata[0]) / (num - 1); var xnew = new Array; var ynew = new Array; var result = new Array; xnew[0] = xdata[0]; ynew[0] = ydata[0]; result.push(xnew[0]); result.push(ynew[0]); for ( j = 1; j < num; ++j) { //new x point (sampling point for the created curve) xnew[j] = xnew[0] + j * step; var max = n - 1; var min = 0; while (max - min > 1) { var k = Math.round((max + min) / 2); if (xdata[k] > xnew[j]) { max = k; } else { min = k; } } //found point one to the left and one to the right of generated new point var h = (xdata[max] - xdata[min]); if (h == 0) { //similar to above two points from original x data need some space between them return []; } var a = (xdata[max] - xnew[j]) / h; var b = (xnew[j] - xdata[min]) / h; ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6; result.push(xnew[j]); result.push(ynew[j]); } return result; } }//end init $.plot.plugins.push({ init : init, options : options, name : 'curvedLines', version : '0.5' }); })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.js ================================================ /*! Javascript plotting library for jQuery, v. 0.7. * * Released under the MIT license by IOLA, December 2007. * */ // first an inline dependency, jquery.colorhelpers.js, we inline it here // for convenience /* Plugin for jQuery for working with colors. * * Version 1.1. * * Inspiration from jQuery color animation plugin by John Resig. * * Released under the MIT license by Ole Laursen, October 2009. * * Examples: * * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() * var c = $.color.extract($("#mydiv"), 'background-color'); * console.log(c.r, c.g, c.b, c.a); * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" * * Note that .scale() and .add() return the same modified object * instead of making a new one. * * V. 1.1: Fix error handling so e.g. parsing an empty string does * produce a color rather than just crashing. */ (function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); // the actual Flot code (function($) { function Plot(placeholder, data_, options_, plugins) { // data is on the form: // [ series1, series2 ... ] // where series is either just the data as [ [x1, y1], [x2, y2], ... ] // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } var series = [], options = { // the color theme used for graphs colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], legend: { show: true, noColumns: 1, // number of colums in legend table labelFormatter: null, // fn: string -> string labelBoxBorderColor: "#ccc", // border color for the little label boxes container: null, // container (as jQuery object) to put legend in, null means default on top of graph position: "ne", // position of default legend container within plot margin: 5, // distance from grid edge to default legend container within plot backgroundColor: null, // null means auto-detect backgroundOpacity: 0.85 // set to 0 to avoid background }, xaxis: { show: null, // null = auto-detect, true = always, false = never position: "bottom", // or "top" mode: null, // null or "time" color: null, // base color, labels, ticks tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" transform: null, // null or f: number -> number to transform axis inverseTransform: null, // if transform is set, this should be the inverse function min: null, // min. value to show, null means set automatically max: null, // max. value to show, null means set automatically autoscaleMargin: null, // margin in % to add if auto-setting min/max ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks tickFormatter: null, // fn: number -> string labelWidth: null, // size of tick labels in pixels labelHeight: null, reserveSpace: null, // whether to reserve space even if axis isn't shown tickLength: null, // size in pixels of ticks, or "full" for whole line alignTicksWithAxis: null, // axis number or null for no sync // mode specific options tickDecimals: null, // no. of decimals, null means auto tickSize: null, // number or [number, "unit"] minTickSize: null, // number or [number, "unit"] monthNames: null, // list of names of months timeformat: null, // format string to use twelveHourClock: false // 12 or 24 time in time mode }, yaxis: { autoscaleMargin: 0.02, position: "left" // or "right" }, xaxes: [], yaxes: [], series: { points: { show: false, radius: 3, lineWidth: 2, // in pixels fill: true, fillColor: "#ffffff", symbol: "circle" // or callback }, lines: { // we don't put in show: false so we can see // whether lines were actively disabled lineWidth: 2, // in pixels fill: false, fillColor: null, steps: false }, bars: { show: false, lineWidth: 2, // in pixels barWidth: 1, // in units of the x axis fill: true, fillColor: null, align: "left", // or "center" horizontal: false }, shadowSize: 3 }, grid: { show: true, aboveData: false, color: "#545454", // primary color used for outline and labels backgroundColor: null, // null for transparent, else color borderColor: null, // set if different from the grid color tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" labelMargin: 5, // in pixels axisMargin: 8, // in pixels borderWidth: 2, // in pixels minBorderMargin: null, // in pixels, null means taken from points radius markings: null, // array of ranges or fn: axes -> array of ranges markingsColor: "#f4f4f4", markingsLineWidth: 2, // interactive stuff clickable: false, hoverable: false, autoHighlight: true, // highlight in case mouse is near mouseActiveRadius: 10 // how far the mouse can be away to activate an item }, hooks: {} }, canvas = null, // the canvas for the plot itself overlay = null, // canvas for interactive stuff on top of plot eventHolder = null, // jQuery object that events should be bound to ctx = null, octx = null, xaxes = [], yaxes = [], plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, canvasWidth = 0, canvasHeight = 0, plotWidth = 0, plotHeight = 0, hooks = { processOptions: [], processRawData: [], processDatapoints: [], drawSeries: [], draw: [], bindEvents: [], drawOverlay: [], shutdown: [] }, plot = this; // public functions plot.setData = setData; plot.setupGrid = setupGrid; plot.draw = draw; plot.getPlaceholder = function() { return placeholder; }; plot.getCanvas = function() { return canvas; }; plot.getPlotOffset = function() { return plotOffset; }; plot.width = function () { return plotWidth; }; plot.height = function () { return plotHeight; }; plot.offset = function () { var o = eventHolder.offset(); o.left += plotOffset.left; o.top += plotOffset.top; return o; }; plot.getData = function () { return series; }; plot.getAxes = function () { var res = {}, i; $.each(xaxes.concat(yaxes), function (_, axis) { if (axis) res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; }); return res; }; plot.getXAxes = function () { return xaxes; }; plot.getYAxes = function () { return yaxes; }; plot.c2p = canvasToAxisCoords; plot.p2c = axisToCanvasCoords; plot.getOptions = function () { return options; }; plot.highlight = highlight; plot.unhighlight = unhighlight; plot.triggerRedrawOverlay = triggerRedrawOverlay; plot.pointOffset = function(point) { return { left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left), top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top) }; }; plot.shutdown = shutdown; plot.resize = function () { getCanvasDimensions(); resizeCanvas(canvas); resizeCanvas(overlay); }; // public attributes plot.hooks = hooks; // initialize initPlugins(plot); parseOptions(options_); setupCanvases(); setData(data_); setupGrid(); draw(); bindEvents(); function executeHooks(hook, args) { args = [plot].concat(args); for (var i = 0; i < hook.length; ++i) hook[i].apply(this, args); } function initPlugins() { for (var i = 0; i < plugins.length; ++i) { var p = plugins[i]; p.init(plot); if (p.options) $.extend(true, options, p.options); } } function parseOptions(opts) { var i; $.extend(true, options, opts); if (options.xaxis.color == null) options.xaxis.color = options.grid.color; if (options.yaxis.color == null) options.yaxis.color = options.grid.color; if (options.xaxis.tickColor == null) // backwards-compatibility options.xaxis.tickColor = options.grid.tickColor; if (options.yaxis.tickColor == null) // backwards-compatibility options.yaxis.tickColor = options.grid.tickColor; if (options.grid.borderColor == null) options.grid.borderColor = options.grid.color; if (options.grid.tickColor == null) options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); // fill in defaults in axes, copy at least always the // first as the rest of the code assumes it'll be there for (i = 0; i < Math.max(1, options.xaxes.length); ++i) options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]); for (i = 0; i < Math.max(1, options.yaxes.length); ++i) options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]); // backwards compatibility, to be removed in future if (options.xaxis.noTicks && options.xaxis.ticks == null) options.xaxis.ticks = options.xaxis.noTicks; if (options.yaxis.noTicks && options.yaxis.ticks == null) options.yaxis.ticks = options.yaxis.noTicks; if (options.x2axis) { options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); options.xaxes[1].position = "top"; } if (options.y2axis) { options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); options.yaxes[1].position = "right"; } if (options.grid.coloredAreas) options.grid.markings = options.grid.coloredAreas; if (options.grid.coloredAreasColor) options.grid.markingsColor = options.grid.coloredAreasColor; if (options.lines) $.extend(true, options.series.lines, options.lines); if (options.points) $.extend(true, options.series.points, options.points); if (options.bars) $.extend(true, options.series.bars, options.bars); if (options.shadowSize != null) options.series.shadowSize = options.shadowSize; // save options on axes for future reference for (i = 0; i < options.xaxes.length; ++i) getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; for (i = 0; i < options.yaxes.length; ++i) getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; // add hooks from options for (var n in hooks) if (options.hooks[n] && options.hooks[n].length) hooks[n] = hooks[n].concat(options.hooks[n]); executeHooks(hooks.processOptions, [options]); } function setData(d) { series = parseData(d); fillInSeriesOptions(); processData(); } function parseData(d) { var res = []; for (var i = 0; i < d.length; ++i) { var s = $.extend(true, {}, options.series); if (d[i].data != null) { s.data = d[i].data; // move the data instead of deep-copy delete d[i].data; $.extend(true, s, d[i]); d[i].data = s.data; } else s.data = d[i]; res.push(s); } return res; } function axisNumber(obj, coord) { var a = obj[coord + "axis"]; if (typeof a == "object") // if we got a real axis, extract number a = a.n; if (typeof a != "number") a = 1; // default to first axis return a; } function allAxes() { // return flat array without annoying null entries return $.grep(xaxes.concat(yaxes), function (a) { return a; }); } function canvasToAxisCoords(pos) { // return an object with x/y corresponding to all used axes var res = {}, i, axis; for (i = 0; i < xaxes.length; ++i) { axis = xaxes[i]; if (axis && axis.used) res["x" + axis.n] = axis.c2p(pos.left); } for (i = 0; i < yaxes.length; ++i) { axis = yaxes[i]; if (axis && axis.used) res["y" + axis.n] = axis.c2p(pos.top); } if (res.x1 !== undefined) res.x = res.x1; if (res.y1 !== undefined) res.y = res.y1; return res; } function axisToCanvasCoords(pos) { // get canvas coords from the first pair of x/y found in pos var res = {}, i, axis, key; for (i = 0; i < xaxes.length; ++i) { axis = xaxes[i]; if (axis && axis.used) { key = "x" + axis.n; if (pos[key] == null && axis.n == 1) key = "x"; if (pos[key] != null) { res.left = axis.p2c(pos[key]); break; } } } for (i = 0; i < yaxes.length; ++i) { axis = yaxes[i]; if (axis && axis.used) { key = "y" + axis.n; if (pos[key] == null && axis.n == 1) key = "y"; if (pos[key] != null) { res.top = axis.p2c(pos[key]); break; } } } return res; } function getOrCreateAxis(axes, number) { if (!axes[number - 1]) axes[number - 1] = { n: number, // save the number for future reference direction: axes == xaxes ? "x" : "y", options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) }; return axes[number - 1]; } function fillInSeriesOptions() { var i; // collect what we already got of colors var neededColors = series.length, usedColors = [], assignedColors = []; for (i = 0; i < series.length; ++i) { var sc = series[i].color; if (sc != null) { --neededColors; if (typeof sc == "number") assignedColors.push(sc); else usedColors.push($.color.parse(series[i].color)); } } // we might need to generate more colors if higher indices // are assigned for (i = 0; i < assignedColors.length; ++i) { neededColors = Math.max(neededColors, assignedColors[i] + 1); } // produce colors as needed var colors = [], variation = 0; i = 0; while (colors.length < neededColors) { var c; if (options.colors.length == i) // check degenerate case c = $.color.make(100, 100, 100); else c = $.color.parse(options.colors[i]); // vary color if needed var sign = variation % 2 == 1 ? -1 : 1; c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2) // FIXME: if we're getting to close to something else, // we should probably skip this one colors.push(c); ++i; if (i >= options.colors.length) { i = 0; ++variation; } } // fill in the options var colori = 0, s; for (i = 0; i < series.length; ++i) { s = series[i]; // assign colors if (s.color == null) { s.color = colors[colori].toString(); ++colori; } else if (typeof s.color == "number") s.color = colors[s.color].toString(); // turn on lines automatically in case nothing is set if (s.lines.show == null) { var v, show = true; for (v in s) if (s[v] && s[v].show) { show = false; break; } if (show) s.lines.show = true; } // setup axes s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); } } function processData() { var topSentry = Number.POSITIVE_INFINITY, bottomSentry = Number.NEGATIVE_INFINITY, fakeInfinity = Number.MAX_VALUE, i, j, k, m, length, s, points, ps, x, y, axis, val, f, p; function updateAxis(axis, min, max) { if (min < axis.datamin && min != -fakeInfinity) axis.datamin = min; if (max > axis.datamax && max != fakeInfinity) axis.datamax = max; } $.each(allAxes(), function (_, axis) { // init axis axis.datamin = topSentry; axis.datamax = bottomSentry; axis.used = false; }); for (i = 0; i < series.length; ++i) { s = series[i]; s.datapoints = { points: [] }; executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); } // first pass: clean and copy data for (i = 0; i < series.length; ++i) { s = series[i]; var data = s.data, format = s.datapoints.format; if (!format) { format = []; // find out how to copy format.push({ x: true, number: true, required: true }); format.push({ y: true, number: true, required: true }); if (s.bars.show || (s.lines.show && s.lines.fill)) { format.push({ y: true, number: true, required: false, defaultValue: 0 }); if (s.bars.horizontal) { delete format[format.length - 1].y; format[format.length - 1].x = true; } } s.datapoints.format = format; } if (s.datapoints.pointsize != null) continue; // already filled in s.datapoints.pointsize = format.length; ps = s.datapoints.pointsize; points = s.datapoints.points; insertSteps = s.lines.show && s.lines.steps; s.xaxis.used = s.yaxis.used = true; for (j = k = 0; j < data.length; ++j, k += ps) { p = data[j]; var nullify = p == null; if (!nullify) { for (m = 0; m < ps; ++m) { val = p[m]; f = format[m]; if (f) { if (f.number && val != null) { val = +val; // convert to number if (isNaN(val)) val = null; else if (val == Infinity) val = fakeInfinity; else if (val == -Infinity) val = -fakeInfinity; } if (val == null) { if (f.required) nullify = true; if (f.defaultValue != null) val = f.defaultValue; } } points[k + m] = val; } } if (nullify) { for (m = 0; m < ps; ++m) { val = points[k + m]; if (val != null) { f = format[m]; // extract min/max info if (f.x) updateAxis(s.xaxis, val, val); if (f.y) updateAxis(s.yaxis, val, val); } points[k + m] = null; } } else { // a little bit of line specific stuff that // perhaps shouldn't be here, but lacking // better means... if (insertSteps && k > 0 && points[k - ps] != null && points[k - ps] != points[k] && points[k - ps + 1] != points[k + 1]) { // copy the point to make room for a middle point for (m = 0; m < ps; ++m) points[k + ps + m] = points[k + m]; // middle point has same y points[k + 1] = points[k - ps + 1]; // we've added a point, better reflect that k += ps; } } } } // give the hooks a chance to run for (i = 0; i < series.length; ++i) { s = series[i]; executeHooks(hooks.processDatapoints, [ s, s.datapoints]); } // second pass: find datamax/datamin for auto-scaling for (i = 0; i < series.length; ++i) { s = series[i]; points = s.datapoints.points, ps = s.datapoints.pointsize; var xmin = topSentry, ymin = topSentry, xmax = bottomSentry, ymax = bottomSentry; for (j = 0; j < points.length; j += ps) { if (points[j] == null) continue; for (m = 0; m < ps; ++m) { val = points[j + m]; f = format[m]; if (!f || val == fakeInfinity || val == -fakeInfinity) continue; if (f.x) { if (val < xmin) xmin = val; if (val > xmax) xmax = val; } if (f.y) { if (val < ymin) ymin = val; if (val > ymax) ymax = val; } } } if (s.bars.show) { // make sure we got room for the bar on the dancing floor var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; if (s.bars.horizontal) { ymin += delta; ymax += delta + s.bars.barWidth; } else { xmin += delta; xmax += delta + s.bars.barWidth; } } updateAxis(s.xaxis, xmin, xmax); updateAxis(s.yaxis, ymin, ymax); } $.each(allAxes(), function (_, axis) { if (axis.datamin == topSentry) axis.datamin = null; if (axis.datamax == bottomSentry) axis.datamax = null; }); } function makeCanvas(skipPositioning, cls) { var c = document.createElement('canvas'); c.className = cls; c.width = canvasWidth; c.height = canvasHeight; if (!skipPositioning) $(c).css({ position: 'absolute', left: 0, top: 0 }); $(c).appendTo(placeholder); if (!c.getContext) // excanvas hack c = window.G_vmlCanvasManager.initElement(c); // used for resetting in case we get replotted c.getContext("2d").save(); return c; } function getCanvasDimensions() { canvasWidth = placeholder.width(); canvasHeight = placeholder.height(); if (canvasWidth <= 0 || canvasHeight <= 0) throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; } function resizeCanvas(c) { // resizing should reset the state (excanvas seems to be // buggy though) if (c.width != canvasWidth) c.width = canvasWidth; if (c.height != canvasHeight) c.height = canvasHeight; // so try to get back to the initial state (even if it's // gone now, this should be safe according to the spec) var cctx = c.getContext("2d"); cctx.restore(); // and save again cctx.save(); } function setupCanvases() { var reused, existingCanvas = placeholder.children("canvas.base"), existingOverlay = placeholder.children("canvas.overlay"); if (existingCanvas.length == 0 || existingOverlay == 0) { // init everything placeholder.html(""); // make sure placeholder is clear placeholder.css({ padding: 0 }); // padding messes up the positioning if (placeholder.css("position") == 'static') placeholder.css("position", "relative"); // for positioning labels and overlay getCanvasDimensions(); canvas = makeCanvas(true, "base"); overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features reused = false; } else { // reuse existing elements canvas = existingCanvas.get(0); overlay = existingOverlay.get(0); reused = true; } ctx = canvas.getContext("2d"); octx = overlay.getContext("2d"); // we include the canvas in the event holder too, because IE 7 // sometimes has trouble with the stacking order eventHolder = $([overlay, canvas]); if (reused) { // run shutdown in the old plot object placeholder.data("plot").shutdown(); // reset reused canvases plot.resize(); // make sure overlay pixels are cleared (canvas is cleared when we redraw) octx.clearRect(0, 0, canvasWidth, canvasHeight); // then whack any remaining obvious garbage left eventHolder.unbind(); placeholder.children().not([canvas, overlay]).remove(); } // save in case we get replotted placeholder.data("plot", plot); } function bindEvents() { // bind events if (options.grid.hoverable) { eventHolder.mousemove(onMouseMove); eventHolder.mouseleave(onMouseLeave); } if (options.grid.clickable) eventHolder.click(onClick); executeHooks(hooks.bindEvents, [eventHolder]); } function shutdown() { if (redrawTimeout) clearTimeout(redrawTimeout); eventHolder.unbind("mousemove", onMouseMove); eventHolder.unbind("mouseleave", onMouseLeave); eventHolder.unbind("click", onClick); executeHooks(hooks.shutdown, [eventHolder]); } function setTransformationHelpers(axis) { // set helper functions on the axis, assumes plot area // has been computed already function identity(x) { return x; } var s, m, t = axis.options.transform || identity, it = axis.options.inverseTransform; // precompute how much the axis is scaling a point // in canvas space if (axis.direction == "x") { s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); m = Math.min(t(axis.max), t(axis.min)); } else { s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); s = -s; m = Math.max(t(axis.max), t(axis.min)); } // data point to canvas coordinate if (t == identity) // slight optimization axis.p2c = function (p) { return (p - m) * s; }; else axis.p2c = function (p) { return (t(p) - m) * s; }; // canvas coordinate to data point if (!it) axis.c2p = function (c) { return m + c / s; }; else axis.c2p = function (c) { return it(m + c / s); }; } function measureTickLabels(axis) { var opts = axis.options, i, ticks = axis.ticks || [], labels = [], l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv; function makeDummyDiv(labels, width) { return $('
    ' + '
    ' + labels.join("") + '
    ') .appendTo(placeholder); } if (axis.direction == "x") { // to avoid measuring the widths of the labels (it's slow), we // construct fixed-size boxes and put the labels inside // them, we don't need the exact figures and the // fixed-size box content is easy to center if (w == null) w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1)); // measure x label heights if (h == null) { labels = []; for (i = 0; i < ticks.length; ++i) { l = ticks[i].label; if (l) labels.push('
    ' + l + '
    '); } if (labels.length > 0) { // stick them all in the same div and measure // collective height labels.push('
    '); dummyDiv = makeDummyDiv(labels, "width:10000px;"); h = dummyDiv.height(); dummyDiv.remove(); } } } else if (w == null || h == null) { // calculate y label dimensions for (i = 0; i < ticks.length; ++i) { l = ticks[i].label; if (l) labels.push('
    ' + l + '
    '); } if (labels.length > 0) { dummyDiv = makeDummyDiv(labels, ""); if (w == null) w = dummyDiv.children().width(); if (h == null) h = dummyDiv.find("div.tickLabel").height(); dummyDiv.remove(); } } if (w == null) w = 0; if (h == null) h = 0; axis.labelWidth = w; axis.labelHeight = h; } function allocateAxisBoxFirstPhase(axis) { // find the bounding box of the axis by looking at label // widths/heights and ticks, make room by diminishing the // plotOffset var lw = axis.labelWidth, lh = axis.labelHeight, pos = axis.options.position, tickLength = axis.options.tickLength, axismargin = options.grid.axisMargin, padding = options.grid.labelMargin, all = axis.direction == "x" ? xaxes : yaxes, index; // determine axis margin var samePosition = $.grep(all, function (a) { return a && a.options.position == pos && a.reserveSpace; }); if ($.inArray(axis, samePosition) == samePosition.length - 1) axismargin = 0; // outermost // determine tick length - if we're innermost, we can use "full" if (tickLength == null) tickLength = "full"; var sameDirection = $.grep(all, function (a) { return a && a.reserveSpace; }); var innermost = $.inArray(axis, sameDirection) == 0; if (!innermost && tickLength == "full") tickLength = 5; if (!isNaN(+tickLength)) padding += +tickLength; // compute box if (axis.direction == "x") { lh += padding; if (pos == "bottom") { plotOffset.bottom += lh + axismargin; axis.box = { top: canvasHeight - plotOffset.bottom, height: lh }; } else { axis.box = { top: plotOffset.top + axismargin, height: lh }; plotOffset.top += lh + axismargin; } } else { lw += padding; if (pos == "left") { axis.box = { left: plotOffset.left + axismargin, width: lw }; plotOffset.left += lw + axismargin; } else { plotOffset.right += lw + axismargin; axis.box = { left: canvasWidth - plotOffset.right, width: lw }; } } // save for future reference axis.position = pos; axis.tickLength = tickLength; axis.box.padding = padding; axis.innermost = innermost; } function allocateAxisBoxSecondPhase(axis) { // set remaining bounding box coordinates if (axis.direction == "x") { axis.box.left = plotOffset.left; axis.box.width = plotWidth; } else { axis.box.top = plotOffset.top; axis.box.height = plotHeight; } } function setupGrid() { var i, axes = allAxes(); // first calculate the plot and axis box dimensions $.each(axes, function (_, axis) { axis.show = axis.options.show; if (axis.show == null) axis.show = axis.used; // by default an axis is visible if it's got data axis.reserveSpace = axis.show || axis.options.reserveSpace; setRange(axis); }); allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; if (options.grid.show) { $.each(allocatedAxes, function (_, axis) { // make the ticks setupTickGeneration(axis); setTicks(axis); snapRangeToTicks(axis, axis.ticks); // find labelWidth/Height for axis measureTickLabels(axis); }); // with all dimensions in house, we can compute the // axis boxes, start from the outside (reverse order) for (i = allocatedAxes.length - 1; i >= 0; --i) allocateAxisBoxFirstPhase(allocatedAxes[i]); // make sure we've got enough space for things that // might stick out var minMargin = options.grid.minBorderMargin; if (minMargin == null) { minMargin = 0; for (i = 0; i < series.length; ++i) minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2); } for (var a in plotOffset) { plotOffset[a] += options.grid.borderWidth; plotOffset[a] = Math.max(minMargin, plotOffset[a]); } } plotWidth = canvasWidth - plotOffset.left - plotOffset.right; plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; // now we got the proper plotWidth/Height, we can compute the scaling $.each(axes, function (_, axis) { setTransformationHelpers(axis); }); if (options.grid.show) { $.each(allocatedAxes, function (_, axis) { allocateAxisBoxSecondPhase(axis); }); insertAxisLabels(); } insertLegend(); } function setRange(axis) { var opts = axis.options, min = +(opts.min != null ? opts.min : axis.datamin), max = +(opts.max != null ? opts.max : axis.datamax), delta = max - min; if (delta == 0.0) { // degenerate case var widen = max == 0 ? 1 : 0.01; if (opts.min == null) min -= widen; // always widen max if we couldn't widen min to ensure we // don't fall into min == max which doesn't work if (opts.max == null || opts.min != null) max += widen; } else { // consider autoscaling var margin = opts.autoscaleMargin; if (margin != null) { if (opts.min == null) { min -= delta * margin; // make sure we don't go below zero if all values // are positive if (min < 0 && axis.datamin != null && axis.datamin >= 0) min = 0; } if (opts.max == null) { max += delta * margin; if (max > 0 && axis.datamax != null && axis.datamax <= 0) max = 0; } } } axis.min = min; axis.max = max; } function setupTickGeneration(axis) { var opts = axis.options; // estimate number of ticks var noTicks; if (typeof opts.ticks == "number" && opts.ticks > 0) noTicks = opts.ticks; else // heuristic based on the model a*sqrt(x) fitted to // some data points that seemed reasonable noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight); var delta = (axis.max - axis.min) / noTicks, size, generator, unit, formatter, i, magn, norm; if (opts.mode == "time") { // pretty handling of time // map of app. size of time units in milliseconds var timeUnitSize = { "second": 1000, "minute": 60 * 1000, "hour": 60 * 60 * 1000, "day": 24 * 60 * 60 * 1000, "month": 30 * 24 * 60 * 60 * 1000, "year": 365.2425 * 24 * 60 * 60 * 1000 }; // the allowed tick sizes, after 1 year we use // an integer algorithm var spec = [ [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"], [1, "day"], [2, "day"], [3, "day"], [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"], [3, "month"], [6, "month"], [1, "year"] ]; var minSize = 0; if (opts.minTickSize != null) { if (typeof opts.tickSize == "number") minSize = opts.tickSize; else minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; } for (var i = 0; i < spec.length - 1; ++i) if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) break; size = spec[i][0]; unit = spec[i][1]; // special-case the possibility of several years if (unit == "year") { magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); norm = (delta / timeUnitSize.year) / magn; if (norm < 1.5) size = 1; else if (norm < 3) size = 2; else if (norm < 7.5) size = 5; else size = 10; size *= magn; } axis.tickSize = opts.tickSize || [size, unit]; generator = function(axis) { var ticks = [], tickSize = axis.tickSize[0], unit = axis.tickSize[1], d = new Date(axis.min); var step = tickSize * timeUnitSize[unit]; if (unit == "second") d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); if (unit == "minute") d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); if (unit == "hour") d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); if (unit == "month") d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); if (unit == "year") d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); // reset smaller components d.setUTCMilliseconds(0); if (step >= timeUnitSize.minute) d.setUTCSeconds(0); if (step >= timeUnitSize.hour) d.setUTCMinutes(0); if (step >= timeUnitSize.day) d.setUTCHours(0); if (step >= timeUnitSize.day * 4) d.setUTCDate(1); if (step >= timeUnitSize.year) d.setUTCMonth(0); var carry = 0, v = Number.NaN, prev; do { prev = v; v = d.getTime(); ticks.push(v); if (unit == "month") { if (tickSize < 1) { // a bit complicated - we'll divide the month // up but we need to take care of fractions // so we don't end up in the middle of a day d.setUTCDate(1); var start = d.getTime(); d.setUTCMonth(d.getUTCMonth() + 1); var end = d.getTime(); d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); carry = d.getUTCHours(); d.setUTCHours(0); } else d.setUTCMonth(d.getUTCMonth() + tickSize); } else if (unit == "year") { d.setUTCFullYear(d.getUTCFullYear() + tickSize); } else d.setTime(v + step); } while (v < axis.max && v != prev); return ticks; }; formatter = function (v, axis) { var d = new Date(v); // first check global format if (opts.timeformat != null) return $.plot.formatDate(d, opts.timeformat, opts.monthNames); var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; var span = axis.max - axis.min; var suffix = (opts.twelveHourClock) ? " %p" : ""; if (t < timeUnitSize.minute) fmt = "%h:%M:%S" + suffix; else if (t < timeUnitSize.day) { if (span < 2 * timeUnitSize.day) fmt = "%h:%M" + suffix; else fmt = "%b %d %h:%M" + suffix; } else if (t < timeUnitSize.month) fmt = "%b %d"; else if (t < timeUnitSize.year) { if (span < timeUnitSize.year) fmt = "%b"; else fmt = "%b %y"; } else fmt = "%y"; return $.plot.formatDate(d, fmt, opts.monthNames); }; } else { // pretty rounding of base-10 numbers var maxDec = opts.tickDecimals; var dec = -Math.floor(Math.log(delta) / Math.LN10); if (maxDec != null && dec > maxDec) dec = maxDec; magn = Math.pow(10, -dec); norm = delta / magn; // norm is between 1.0 and 10.0 if (norm < 1.5) size = 1; else if (norm < 3) { size = 2; // special case for 2.5, requires an extra decimal if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { size = 2.5; ++dec; } } else if (norm < 7.5) size = 5; else size = 10; size *= magn; if (opts.minTickSize != null && size < opts.minTickSize) size = opts.minTickSize; axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); axis.tickSize = opts.tickSize || size; generator = function (axis) { var ticks = []; // spew out all possible ticks var start = floorInBase(axis.min, axis.tickSize), i = 0, v = Number.NaN, prev; do { prev = v; v = start + i * axis.tickSize; ticks.push(v); ++i; } while (v < axis.max && v != prev); return ticks; }; formatter = function (v, axis) { return v.toFixed(axis.tickDecimals); }; } if (opts.alignTicksWithAxis != null) { var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; if (otherAxis && otherAxis.used && otherAxis != axis) { // consider snapping min/max to outermost nice ticks var niceTicks = generator(axis); if (niceTicks.length > 0) { if (opts.min == null) axis.min = Math.min(axis.min, niceTicks[0]); if (opts.max == null && niceTicks.length > 1) axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); } generator = function (axis) { // copy ticks, scaled to this axis var ticks = [], v, i; for (i = 0; i < otherAxis.ticks.length; ++i) { v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); v = axis.min + v * (axis.max - axis.min); ticks.push(v); } return ticks; }; // we might need an extra decimal since forced // ticks don't necessarily fit naturally if (axis.mode != "time" && opts.tickDecimals == null) { var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1), ts = generator(axis); // only proceed if the tick interval rounded // with an extra decimal doesn't give us a // zero at end if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) axis.tickDecimals = extraDec; } } } axis.tickGenerator = generator; if ($.isFunction(opts.tickFormatter)) axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; else axis.tickFormatter = formatter; } function setTicks(axis) { var oticks = axis.options.ticks, ticks = []; if (oticks == null || (typeof oticks == "number" && oticks > 0)) ticks = axis.tickGenerator(axis); else if (oticks) { if ($.isFunction(oticks)) // generate the ticks ticks = oticks({ min: axis.min, max: axis.max }); else ticks = oticks; } // clean up/labelify the supplied ticks, copy them over var i, v; axis.ticks = []; for (i = 0; i < ticks.length; ++i) { var label = null; var t = ticks[i]; if (typeof t == "object") { v = +t[0]; if (t.length > 1) label = t[1]; } else v = +t; if (label == null) label = axis.tickFormatter(v, axis); if (!isNaN(v)) axis.ticks.push({ v: v, label: label }); } } function snapRangeToTicks(axis, ticks) { if (axis.options.autoscaleMargin && ticks.length > 0) { // snap to ticks if (axis.options.min == null) axis.min = Math.min(axis.min, ticks[0].v); if (axis.options.max == null && ticks.length > 1) axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); } } function draw() { ctx.clearRect(0, 0, canvasWidth, canvasHeight); var grid = options.grid; // draw background, if any if (grid.show && grid.backgroundColor) drawBackground(); if (grid.show && !grid.aboveData) drawGrid(); for (var i = 0; i < series.length; ++i) { executeHooks(hooks.drawSeries, [ctx, series[i]]); drawSeries(series[i]); } executeHooks(hooks.draw, [ctx]); if (grid.show && grid.aboveData) drawGrid(); } function extractRange(ranges, coord) { var axis, from, to, key, axes = allAxes(); for (i = 0; i < axes.length; ++i) { axis = axes[i]; if (axis.direction == coord) { key = coord + axis.n + "axis"; if (!ranges[key] && axis.n == 1) key = coord + "axis"; // support x1axis as xaxis if (ranges[key]) { from = ranges[key].from; to = ranges[key].to; break; } } } // backwards-compat stuff - to be removed in future if (!ranges[key]) { axis = coord == "x" ? xaxes[0] : yaxes[0]; from = ranges[coord + "1"]; to = ranges[coord + "2"]; } // auto-reverse as an added bonus if (from != null && to != null && from > to) { var tmp = from; from = to; to = tmp; } return { from: from, to: to, axis: axis }; } function drawBackground() { ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); ctx.fillRect(0, 0, plotWidth, plotHeight); ctx.restore(); } function drawGrid() { var i; ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); // draw markings var markings = options.grid.markings; if (markings) { if ($.isFunction(markings)) { var axes = plot.getAxes(); // xmin etc. is backwards compatibility, to be // removed in the future axes.xmin = axes.xaxis.min; axes.xmax = axes.xaxis.max; axes.ymin = axes.yaxis.min; axes.ymax = axes.yaxis.max; markings = markings(axes); } for (i = 0; i < markings.length; ++i) { var m = markings[i], xrange = extractRange(m, "x"), yrange = extractRange(m, "y"); // fill in missing if (xrange.from == null) xrange.from = xrange.axis.min; if (xrange.to == null) xrange.to = xrange.axis.max; if (yrange.from == null) yrange.from = yrange.axis.min; if (yrange.to == null) yrange.to = yrange.axis.max; // clip if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) continue; xrange.from = Math.max(xrange.from, xrange.axis.min); xrange.to = Math.min(xrange.to, xrange.axis.max); yrange.from = Math.max(yrange.from, yrange.axis.min); yrange.to = Math.min(yrange.to, yrange.axis.max); if (xrange.from == xrange.to && yrange.from == yrange.to) continue; // then draw xrange.from = xrange.axis.p2c(xrange.from); xrange.to = xrange.axis.p2c(xrange.to); yrange.from = yrange.axis.p2c(yrange.from); yrange.to = yrange.axis.p2c(yrange.to); if (xrange.from == xrange.to || yrange.from == yrange.to) { // draw line ctx.beginPath(); ctx.strokeStyle = m.color || options.grid.markingsColor; ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; ctx.moveTo(xrange.from, yrange.from); ctx.lineTo(xrange.to, yrange.to); ctx.stroke(); } else { // fill area ctx.fillStyle = m.color || options.grid.markingsColor; ctx.fillRect(xrange.from, yrange.to, xrange.to - xrange.from, yrange.from - yrange.to); } } } // draw the ticks var axes = allAxes(), bw = options.grid.borderWidth; for (var j = 0; j < axes.length; ++j) { var axis = axes[j], box = axis.box, t = axis.tickLength, x, y, xoff, yoff; if (!axis.show || axis.ticks.length == 0) continue ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString(); ctx.lineWidth = 1; // find the edges if (axis.direction == "x") { x = 0; if (t == "full") y = (axis.position == "top" ? 0 : plotHeight); else y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); } else { y = 0; if (t == "full") x = (axis.position == "left" ? 0 : plotWidth); else x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); } // draw tick bar if (!axis.innermost) { ctx.beginPath(); xoff = yoff = 0; if (axis.direction == "x") xoff = plotWidth; else yoff = plotHeight; if (ctx.lineWidth == 1) { x = Math.floor(x) + 0.5; y = Math.floor(y) + 0.5; } ctx.moveTo(x, y); ctx.lineTo(x + xoff, y + yoff); ctx.stroke(); } // draw ticks ctx.beginPath(); for (i = 0; i < axis.ticks.length; ++i) { var v = axis.ticks[i].v; xoff = yoff = 0; if (v < axis.min || v > axis.max // skip those lying on the axes if we got a border || (t == "full" && bw > 0 && (v == axis.min || v == axis.max))) continue; if (axis.direction == "x") { x = axis.p2c(v); yoff = t == "full" ? -plotHeight : t; if (axis.position == "top") yoff = -yoff; } else { y = axis.p2c(v); xoff = t == "full" ? -plotWidth : t; if (axis.position == "left") xoff = -xoff; } if (ctx.lineWidth == 1) { if (axis.direction == "x") x = Math.floor(x) + 0.5; else y = Math.floor(y) + 0.5; } ctx.moveTo(x, y); ctx.lineTo(x + xoff, y + yoff); } ctx.stroke(); } // draw border if (bw) { ctx.lineWidth = bw; ctx.strokeStyle = options.grid.borderColor; ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); } ctx.restore(); } function insertAxisLabels() { placeholder.find(".tickLabels").remove(); var html = ['
    ']; var axes = allAxes(); for (var j = 0; j < axes.length; ++j) { var axis = axes[j], box = axis.box; if (!axis.show) continue; //debug: html.push('
    ') html.push('
    '); for (var i = 0; i < axis.ticks.length; ++i) { var tick = axis.ticks[i]; if (!tick.label || tick.v < axis.min || tick.v > axis.max) continue; var pos = {}, align; if (axis.direction == "x") { align = "center"; pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2); if (axis.position == "bottom") pos.top = box.top + box.padding; else pos.bottom = canvasHeight - (box.top + box.height - box.padding); } else { pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2); if (axis.position == "left") { pos.right = canvasWidth - (box.left + box.width - box.padding) align = "right"; } else { pos.left = box.left + box.padding; align = "left"; } } pos.width = axis.labelWidth; var style = ["position:absolute", "text-align:" + align ]; for (var a in pos) style.push(a + ":" + pos[a] + "px") html.push('
    ' + tick.label + '
    '); } html.push('
    '); } html.push('
    '); placeholder.append(html.join("")); } function drawSeries(series) { if (series.lines.show) drawSeriesLines(series); if (series.bars.show) drawSeriesBars(series); if (series.points.show) drawSeriesPoints(series); } function drawSeriesLines(series) { function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize, prevx = null, prevy = null; ctx.beginPath(); for (var i = ps; i < points.length; i += ps) { var x1 = points[i - ps], y1 = points[i - ps + 1], x2 = points[i], y2 = points[i + 1]; if (x1 == null || x2 == null) continue; // clip with ymin if (y1 <= y2 && y1 < axisy.min) { if (y2 < axisy.min) continue; // line segment is outside // compute new intersection point x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.min; } else if (y2 <= y1 && y2 < axisy.min) { if (y1 < axisy.min) continue; x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.min; } // clip with ymax if (y1 >= y2 && y1 > axisy.max) { if (y2 > axisy.max) continue; x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.max; } else if (y2 >= y1 && y2 > axisy.max) { if (y1 > axisy.max) continue; x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.max; } // clip with xmin if (x1 <= x2 && x1 < axisx.min) { if (x2 < axisx.min) continue; y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.min; } else if (x2 <= x1 && x2 < axisx.min) { if (x1 < axisx.min) continue; y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.min; } // clip with xmax if (x1 >= x2 && x1 > axisx.max) { if (x2 > axisx.max) continue; y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.max; } else if (x2 >= x1 && x2 > axisx.max) { if (x1 > axisx.max) continue; y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.max; } if (x1 != prevx || y1 != prevy) ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); prevx = x2; prevy = y2; ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); } ctx.stroke(); } function plotLineArea(datapoints, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize, bottom = Math.min(Math.max(0, axisy.min), axisy.max), i = 0, top, areaOpen = false, ypos = 1, segmentStart = 0, segmentEnd = 0; // we process each segment in two turns, first forward // direction to sketch out top, then once we hit the // end we go backwards to sketch the bottom while (true) { if (ps > 0 && i > points.length + ps) break; i += ps; // ps is negative if going backwards var x1 = points[i - ps], y1 = points[i - ps + ypos], x2 = points[i], y2 = points[i + ypos]; if (areaOpen) { if (ps > 0 && x1 != null && x2 == null) { // at turning point segmentEnd = i; ps = -ps; ypos = 2; continue; } if (ps < 0 && i == segmentStart + ps) { // done with the reverse sweep ctx.fill(); areaOpen = false; ps = -ps; ypos = 1; i = segmentStart = segmentEnd + ps; continue; } } if (x1 == null || x2 == null) continue; // clip x values // clip with xmin if (x1 <= x2 && x1 < axisx.min) { if (x2 < axisx.min) continue; y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.min; } else if (x2 <= x1 && x2 < axisx.min) { if (x1 < axisx.min) continue; y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.min; } // clip with xmax if (x1 >= x2 && x1 > axisx.max) { if (x2 > axisx.max) continue; y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.max; } else if (x2 >= x1 && x2 > axisx.max) { if (x1 > axisx.max) continue; y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.max; } if (!areaOpen) { // open area ctx.beginPath(); ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); areaOpen = true; } // now first check the case where both is outside if (y1 >= axisy.max && y2 >= axisy.max) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); continue; } else if (y1 <= axisy.min && y2 <= axisy.min) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); continue; } // else it's a bit more complicated, there might // be a flat maxed out rectangle first, then a // triangular cutout or reverse; to find these // keep track of the current x values var x1old = x1, x2old = x2; // clip the y values, without shortcutting, we // go through all cases in turn // clip with ymin if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.min; } else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.min; } // clip with ymax if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.max; } else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.max; } // if the x value was changed we got a rectangle // to fill if (x1 != x1old) { ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); // it goes to (x1, y1), but we fill that below } // fill triangular section, this sometimes result // in redundant points if (x1, y1) hasn't changed // from previous line to, but we just ignore that ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); // fill the other rectangle if it's there if (x2 != x2old) { ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); } } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); ctx.lineJoin = "round"; var lw = series.lines.lineWidth, sw = series.shadowSize; // FIXME: consider another form of shadow when filling is turned on if (lw > 0 && sw > 0) { // draw shadow as a thick and thin line with transparency ctx.lineWidth = sw; ctx.strokeStyle = "rgba(0,0,0,0.1)"; // position shadow at angle from the mid of line var angle = Math.PI/18; plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); ctx.lineWidth = sw/2; plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); } ctx.lineWidth = lw; ctx.strokeStyle = series.color; var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); if (fillStyle) { ctx.fillStyle = fillStyle; plotLineArea(series.datapoints, series.xaxis, series.yaxis); } if (lw > 0) plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); ctx.restore(); } function drawSeriesPoints(series) { function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { var points = datapoints.points, ps = datapoints.pointsize; for (var i = 0; i < points.length; i += ps) { var x = points[i], y = points[i + 1]; if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) continue; ctx.beginPath(); x = axisx.p2c(x); y = axisy.p2c(y) + offset; if (symbol == "circle") ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); else symbol(ctx, x, y, radius, shadow); ctx.closePath(); if (fillStyle) { ctx.fillStyle = fillStyle; ctx.fill(); } ctx.stroke(); } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); var lw = series.points.lineWidth, sw = series.shadowSize, radius = series.points.radius, symbol = series.points.symbol; if (lw > 0 && sw > 0) { // draw shadow in two steps var w = sw / 2; ctx.lineWidth = w; ctx.strokeStyle = "rgba(0,0,0,0.1)"; plotPoints(series.datapoints, radius, null, w + w/2, true, series.xaxis, series.yaxis, symbol); ctx.strokeStyle = "rgba(0,0,0,0.2)"; plotPoints(series.datapoints, radius, null, w/2, true, series.xaxis, series.yaxis, symbol); } ctx.lineWidth = lw; ctx.strokeStyle = series.color; plotPoints(series.datapoints, radius, getFillStyle(series.points, series.color), 0, false, series.xaxis, series.yaxis, symbol); ctx.restore(); } function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { var left, right, bottom, top, drawLeft, drawRight, drawTop, drawBottom, tmp; // in horizontal mode, we start the bar from the left // instead of from the bottom so it appears to be // horizontal rather than vertical if (horizontal) { drawBottom = drawRight = drawTop = true; drawLeft = false; left = b; right = x; top = y + barLeft; bottom = y + barRight; // account for negative bars if (right < left) { tmp = right; right = left; left = tmp; drawLeft = true; drawRight = false; } } else { drawLeft = drawRight = drawTop = true; drawBottom = false; left = x + barLeft; right = x + barRight; bottom = b; top = y; // account for negative bars if (top < bottom) { tmp = top; top = bottom; bottom = tmp; drawBottom = true; drawTop = false; } } // clip if (right < axisx.min || left > axisx.max || top < axisy.min || bottom > axisy.max) return; if (left < axisx.min) { left = axisx.min; drawLeft = false; } if (right > axisx.max) { right = axisx.max; drawRight = false; } if (bottom < axisy.min) { bottom = axisy.min; drawBottom = false; } if (top > axisy.max) { top = axisy.max; drawTop = false; } left = axisx.p2c(left); bottom = axisy.p2c(bottom); right = axisx.p2c(right); top = axisy.p2c(top); // fill the bar if (fillStyleCallback) { c.beginPath(); c.moveTo(left, bottom); c.lineTo(left, top); c.lineTo(right, top); c.lineTo(right, bottom); c.fillStyle = fillStyleCallback(bottom, top); c.fill(); } // draw outline if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { c.beginPath(); // FIXME: inline moveTo is buggy with excanvas c.moveTo(left, bottom + offset); if (drawLeft) c.lineTo(left, top + offset); else c.moveTo(left, top + offset); if (drawTop) c.lineTo(right, top + offset); else c.moveTo(right, top + offset); if (drawRight) c.lineTo(right, bottom + offset); else c.moveTo(right, bottom + offset); if (drawBottom) c.lineTo(left, bottom + offset); else c.moveTo(left, bottom + offset); c.stroke(); } } function drawSeriesBars(series) { function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize; for (var i = 0; i < points.length; i += ps) { if (points[i] == null) continue; drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); // FIXME: figure out a way to add shadows (for instance along the right edge) ctx.lineWidth = series.bars.lineWidth; ctx.strokeStyle = series.color; var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); ctx.restore(); } function getFillStyle(filloptions, seriesColor, bottom, top) { var fill = filloptions.fill; if (!fill) return null; if (filloptions.fillColor) return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); var c = $.color.parse(seriesColor); c.a = typeof fill == "number" ? fill : 0.4; c.normalize(); return c.toString(); } function insertLegend() { placeholder.find(".legend").remove(); if (!options.legend.show) return; var fragments = [], rowStarted = false, lf = options.legend.labelFormatter, s, label; for (var i = 0; i < series.length; ++i) { s = series[i]; label = s.label; if (!label) continue; if (i % options.legend.noColumns == 0) { if (rowStarted) fragments.push(''); fragments.push(''); rowStarted = true; } if (lf) label = lf(label, s); fragments.push( '
    ' + '' + label + ''); } if (rowStarted) fragments.push(''); if (fragments.length == 0) return; var table = '' + fragments.join("") + '
    '; if (options.legend.container != null) $(options.legend.container).html(table); else { var pos = "", p = options.legend.position, m = options.legend.margin; if (m[0] == null) m = [m, m]; if (p.charAt(0) == "n") pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; else if (p.charAt(0) == "s") pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; if (p.charAt(1) == "e") pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; else if (p.charAt(1) == "w") pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; var legend = $('
    ' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
    ').appendTo(placeholder); if (options.legend.backgroundOpacity != 0.0) { // put in the transparent background // separately to avoid blended labels and // label boxes var c = options.legend.backgroundColor; if (c == null) { c = options.grid.backgroundColor; if (c && typeof c == "string") c = $.color.parse(c); else c = $.color.extract(legend, 'background-color'); c.a = 1; c = c.toString(); } var div = legend.children(); $('
    ').prependTo(legend).css('opacity', options.legend.backgroundOpacity); } } } // interactive features var highlights = [], redrawTimeout = null; // returns the data item the mouse is over, or null if none is found function findNearbyItem(mouseX, mouseY, seriesFilter) { var maxDistance = options.grid.mouseActiveRadius, smallestDistance = maxDistance * maxDistance + 1, item = null, foundPoint = false, i, j; for (i = series.length - 1; i >= 0; --i) { if (!seriesFilter(series[i])) continue; var s = series[i], axisx = s.xaxis, axisy = s.yaxis, points = s.datapoints.points, ps = s.datapoints.pointsize, mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster my = axisy.c2p(mouseY), maxx = maxDistance / axisx.scale, maxy = maxDistance / axisy.scale; // with inverse transforms, we can't use the maxx/maxy // optimization, sadly if (axisx.options.inverseTransform) maxx = Number.MAX_VALUE; if (axisy.options.inverseTransform) maxy = Number.MAX_VALUE; if (s.lines.show || s.points.show) { for (j = 0; j < points.length; j += ps) { var x = points[j], y = points[j + 1]; if (x == null) continue; // For points and lines, the cursor must be within a // certain distance to the data point if (x - mx > maxx || x - mx < -maxx || y - my > maxy || y - my < -maxy) continue; // We have to calculate distances in pixels, not in // data units, because the scales of the axes may be different var dx = Math.abs(axisx.p2c(x) - mouseX), dy = Math.abs(axisy.p2c(y) - mouseY), dist = dx * dx + dy * dy; // we save the sqrt // use <= to ensure last point takes precedence // (last generally means on top of) if (dist < smallestDistance) { smallestDistance = dist; item = [i, j / ps]; } } } if (s.bars.show && !item) { // no other point can be nearby var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, barRight = barLeft + s.bars.barWidth; for (j = 0; j < points.length; j += ps) { var x = points[j], y = points[j + 1], b = points[j + 2]; if (x == null) continue; // for a bar graph, the cursor must be inside the bar if (series[i].bars.horizontal ? (mx <= Math.max(b, x) && mx >= Math.min(b, x) && my >= y + barLeft && my <= y + barRight) : (mx >= x + barLeft && mx <= x + barRight && my >= Math.min(b, y) && my <= Math.max(b, y))) item = [i, j / ps]; } } } if (item) { i = item[0]; j = item[1]; ps = series[i].datapoints.pointsize; return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), dataIndex: j, series: series[i], seriesIndex: i }; } return null; } function onMouseMove(e) { if (options.grid.hoverable) triggerClickHoverEvent("plothover", e, function (s) { return s["hoverable"] != false; }); } function onMouseLeave(e) { if (options.grid.hoverable) triggerClickHoverEvent("plothover", e, function (s) { return false; }); } function onClick(e) { triggerClickHoverEvent("plotclick", e, function (s) { return s["clickable"] != false; }); } // trigger click or hover event (they send the same parameters // so we share their code) function triggerClickHoverEvent(eventname, event, seriesFilter) { var offset = eventHolder.offset(), canvasX = event.pageX - offset.left - plotOffset.left, canvasY = event.pageY - offset.top - plotOffset.top, pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); pos.pageX = event.pageX; pos.pageY = event.pageY; var item = findNearbyItem(canvasX, canvasY, seriesFilter); if (item) { // fill in mouse pos for any listeners out there item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); } if (options.grid.autoHighlight) { // clear auto-highlights for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; if (h.auto == eventname && !(item && h.series == item.series && h.point[0] == item.datapoint[0] && h.point[1] == item.datapoint[1])) unhighlight(h.series, h.point); } if (item) highlight(item.series, item.datapoint, eventname); } placeholder.trigger(eventname, [ pos, item ]); } function triggerRedrawOverlay() { if (!redrawTimeout) redrawTimeout = setTimeout(drawOverlay, 30); } function drawOverlay() { redrawTimeout = null; // draw highlights octx.save(); octx.clearRect(0, 0, canvasWidth, canvasHeight); octx.translate(plotOffset.left, plotOffset.top); var i, hi; for (i = 0; i < highlights.length; ++i) { hi = highlights[i]; if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point); else drawPointHighlight(hi.series, hi.point); } octx.restore(); executeHooks(hooks.drawOverlay, [octx]); } function highlight(s, point, auto) { if (typeof s == "number") s = series[s]; if (typeof point == "number") { var ps = s.datapoints.pointsize; point = s.datapoints.points.slice(ps * point, ps * (point + 1)); } var i = indexOfHighlight(s, point); if (i == -1) { highlights.push({ series: s, point: point, auto: auto }); triggerRedrawOverlay(); } else if (!auto) highlights[i].auto = false; } function unhighlight(s, point) { if (s == null && point == null) { highlights = []; triggerRedrawOverlay(); } if (typeof s == "number") s = series[s]; if (typeof point == "number") point = s.data[point]; var i = indexOfHighlight(s, point); if (i != -1) { highlights.splice(i, 1); triggerRedrawOverlay(); } } function indexOfHighlight(s, p) { for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; if (h.series == s && h.point[0] == p[0] && h.point[1] == p[1]) return i; } return -1; } function drawPointHighlight(series, point) { var x = point[0], y = point[1], axisx = series.xaxis, axisy = series.yaxis; if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) return; var pointRadius = series.points.radius + series.points.lineWidth / 2; octx.lineWidth = pointRadius; octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); var radius = 1.5 * pointRadius, x = axisx.p2c(x), y = axisy.p2c(y); octx.beginPath(); if (series.points.symbol == "circle") octx.arc(x, y, radius, 0, 2 * Math.PI, false); else series.points.symbol(octx, x, y, radius, false); octx.closePath(); octx.stroke(); } function drawBarHighlight(series, point) { octx.lineWidth = series.bars.lineWidth; octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString(); var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); } function getColorOrGradient(spec, bottom, top, defaultColor) { if (typeof spec == "string") return spec; else { // assume this is a gradient spec; IE currently only // supports a simple vertical gradient properly, so that's // what we support too var gradient = ctx.createLinearGradient(0, top, 0, bottom); for (var i = 0, l = spec.colors.length; i < l; ++i) { var c = spec.colors[i]; if (typeof c != "string") { var co = $.color.parse(defaultColor); if (c.brightness != null) co = co.scale('rgb', c.brightness) if (c.opacity != null) co.a *= c.opacity; c = co.toString(); } gradient.addColorStop(i / (l - 1), c); } return gradient; } } } $.plot = function(placeholder, data, options) { //var t0 = new Date(); var plot = new Plot($(placeholder), data, options, $.plot.plugins); //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); return plot; }; $.plot.version = "0.7"; $.plot.plugins = []; // returns a string with the date d formatted according to fmt $.plot.formatDate = function(d, fmt, monthNames) { var leftPad = function(n) { n = "" + n; return n.length == 1 ? "0" + n : n; }; var r = []; var escape = false, padNext = false; var hours = d.getUTCHours(); var isAM = hours < 12; if (monthNames == null) monthNames = ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]; if (fmt.search(/%p|%P/) != -1) { if (hours > 12) { hours = hours - 12; } else if (hours == 0) { hours = 12; } } for (var i = 0; i < fmt.length; ++i) { var c = fmt.charAt(i); if (escape) { switch (c) { case 'h': c = "" + hours; break; case 'H': c = leftPad(hours); break; case 'M': c = leftPad(d.getUTCMinutes()); break; case 'S': c = leftPad(d.getUTCSeconds()); break; case 'd': c = "" + d.getUTCDate(); break; case 'm': c = "" + (d.getUTCMonth() + 1); break; case 'y': c = "" + d.getUTCFullYear(); break; case 'b': c = "" + monthNames[d.getUTCMonth()]; break; case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; case '0': c = ""; padNext = true; break; } if (c && padNext) { c = leftPad(c); padNext = false; } r.push(c); if (!padNext) escape = false; } else { if (c == "%") escape = true; else r.push(c); } } return r.join(""); }; // round to nearby lower multiple of base function floorInBase(n, base) { return base * Math.floor(n / base); } })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.pie.js ================================================ /* Flot plugin for rendering pie charts. The plugin assumes the data is coming is as a single data value for each series, and each of those values is a positive value or zero (negative numbers don't make any sense and will cause strange effects). The data values do NOT need to be passed in as percentage values because it internally calculates the total and percentages. * Created by Brian Medendorp, June 2009 * Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars * Changes: 2009-10-22: lineJoin set to round 2009-10-23: IE full circle fix, donut 2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera 2009-11-17: Added IE hover capability submitted by Anthony Aragues 2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well) Available options are: series: { pie: { show: true/false radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) offset: { top: integer value to move the pie up or down left: integer value to move the pie left or right, or 'auto' }, stroke: { color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') width: integer pixel width of the stroke }, label: { show: true/false, or 'auto' formatter: a user-defined function that modifies the text/style of the label text radius: 0-1 for percentage of fullsize, or a specified pixel length background: { color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') opacity: 0-1 }, threshold: 0-1 for the percentage value at which to hide labels (if they're too small) }, combine: { threshold: 0-1 for the percentage value at which to combine slices (if they're too small) color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined label: any text value of what the combined slice should be labeled } highlight: { opacity: 0-1 } } } More detail and specific examples can be found in the included HTML file. */ (function ($) { function init(plot) // this is the "body" of the plugin { var canvas = null; var target = null; var maxRadius = null; var centerLeft = null; var centerTop = null; var total = 0; var redraw = true; var redrawAttempts = 10; var shrink = 0.95; var legendWidth = 0; var processed = false; var raw = false; // interactive variables var highlights = []; // add hook to determine if pie plugin in enabled, and then perform necessary operations plot.hooks.processOptions.push(checkPieEnabled); plot.hooks.bindEvents.push(bindEvents); // check to see if the pie plugin is enabled function checkPieEnabled(plot, options) { if (options.series.pie.show) { //disable grid options.grid.show = false; // set labels.show if (options.series.pie.label.show=='auto') if (options.legend.show) options.series.pie.label.show = false; else options.series.pie.label.show = true; // set radius if (options.series.pie.radius=='auto') if (options.series.pie.label.show) options.series.pie.radius = 3/4; else options.series.pie.radius = 1; // ensure sane tilt if (options.series.pie.tilt>1) options.series.pie.tilt=1; if (options.series.pie.tilt<0) options.series.pie.tilt=0; // add processData hook to do transformations on the data plot.hooks.processDatapoints.push(processDatapoints); plot.hooks.drawOverlay.push(drawOverlay); // add draw hook plot.hooks.draw.push(draw); } } // bind hoverable events function bindEvents(plot, eventHolder) { var options = plot.getOptions(); if (options.series.pie.show && options.grid.hoverable) eventHolder.unbind('mousemove').mousemove(onMouseMove); if (options.series.pie.show && options.grid.clickable) eventHolder.unbind('click').click(onClick); } // debugging function that prints out an object function alertObject(obj) { var msg = ''; function traverse(obj, depth) { if (!depth) depth = 0; for (var i = 0; i < obj.length; ++i) { for (var j=0; jcanvas.width-maxRadius) centerLeft = canvas.width-maxRadius; } function fixData(data) { for (var i = 0; i < data.length; ++i) { if (typeof(data[i].data)=='number') data[i].data = [[1,data[i].data]]; else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined') { if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined') data[i].label = data[i].data.label; // fix weirdness coming from flot data[i].data = [[1,0]]; } } return data; } function combine(data) { data = fixData(data); calcTotal(data); var combined = 0; var numCombined = 0; var color = options.series.pie.combine.color; var newdata = []; for (var i = 0; i < data.length; ++i) { // make sure its a number data[i].data[0][1] = parseFloat(data[i].data[0][1]); if (!data[i].data[0][1]) data[i].data[0][1] = 0; if (data[i].data[0][1]/total<=options.series.pie.combine.threshold) { combined += data[i].data[0][1]; numCombined++; if (!color) color = data[i].color; } else { newdata.push({ data: [[1,data[i].data[0][1]]], color: data[i].color, label: data[i].label, angle: (data[i].data[0][1]*(Math.PI*2))/total, percent: (data[i].data[0][1]/total*100) }); } } if (numCombined>0) newdata.push({ data: [[1,combined]], color: color, label: options.series.pie.combine.label, angle: (combined*(Math.PI*2))/total, percent: (combined/total*100) }); return newdata; } function draw(plot, newCtx) { if (!target) return; // if no series were passed ctx = newCtx; setupPie(); var slices = plot.getData(); var attempts = 0; while (redraw && attempts0) maxRadius *= shrink; attempts += 1; clear(); if (options.series.pie.tilt<=0.8) drawShadow(); drawPie(); } if (attempts >= redrawAttempts) { clear(); target.prepend('
    Could not draw pie with labels contained inside canvas
    '); } if ( plot.setSeries && plot.insertLegend ) { plot.setSeries(slices); plot.insertLegend(); } // we're actually done at this point, just defining internal functions at this point function clear() { ctx.clearRect(0,0,canvas.width,canvas.height); target.children().filter('.pieLabel, .pieLabelBackground').remove(); } function drawShadow() { var shadowLeft = 5; var shadowTop = 15; var edge = 10; var alpha = 0.02; // set radius if (options.series.pie.radius>1) var radius = options.series.pie.radius; else var radius = maxRadius * options.series.pie.radius; if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge) return; // shadow would be outside canvas, so don't draw it ctx.save(); ctx.translate(shadowLeft,shadowTop); ctx.globalAlpha = alpha; ctx.fillStyle = '#000'; // center and rotate to starting position ctx.translate(centerLeft,centerTop); ctx.scale(1, options.series.pie.tilt); //radius -= edge; for (var i=1; i<=edge; i++) { ctx.beginPath(); ctx.arc(0,0,radius,0,Math.PI*2,false); ctx.fill(); radius -= i; } ctx.restore(); } function drawPie() { startAngle = Math.PI*options.series.pie.startAngle; // set radius if (options.series.pie.radius>1) var radius = options.series.pie.radius; else var radius = maxRadius * options.series.pie.radius; // center and rotate to starting position ctx.save(); ctx.translate(centerLeft,centerTop); ctx.scale(1, options.series.pie.tilt); //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera // draw slices ctx.save(); var currentAngle = startAngle; for (var i = 0; i < slices.length; ++i) { slices[i].startAngle = currentAngle; drawSlice(slices[i].angle, slices[i].color, true); } ctx.restore(); // draw slice outlines ctx.save(); ctx.lineWidth = options.series.pie.stroke.width; currentAngle = startAngle; for (var i = 0; i < slices.length; ++i) drawSlice(slices[i].angle, options.series.pie.stroke.color, false); ctx.restore(); // draw donut hole drawDonutHole(ctx); // draw labels if (options.series.pie.label.show) drawLabels(); // restore to original state ctx.restore(); function drawSlice(angle, color, fill) { if (angle<=0) return; if (fill) ctx.fillStyle = color; else { ctx.strokeStyle = color; ctx.lineJoin = 'round'; } ctx.beginPath(); if (Math.abs(angle - Math.PI*2) > 0.000000001) ctx.moveTo(0,0); // Center of the pie else if ($.browser.msie) angle -= 0.0001; //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false); ctx.closePath(); //ctx.rotate(angle); // This doesn't work properly in Opera currentAngle += angle; if (fill) ctx.fill(); else ctx.stroke(); } function drawLabels() { var currentAngle = startAngle; // set radius if (options.series.pie.label.radius>1) var radius = options.series.pie.label.radius; else var radius = maxRadius * options.series.pie.label.radius; for (var i = 0; i < slices.length; ++i) { if (slices[i].percent >= options.series.pie.label.threshold*100) drawLabel(slices[i], currentAngle, i); currentAngle += slices[i].angle; } function drawLabel(slice, startAngle, index) { if (slice.data[0][1]==0) return; // format label text var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; if (lf) text = lf(slice.label, slice); else text = slice.label; if (plf) text = plf(text, slice); var halfAngle = ((startAngle+slice.angle) + startAngle)/2; var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; var html = '' + text + ""; target.append(html); var label = target.children('#pieLabel'+index); var labelTop = (y - label.height()/2); var labelLeft = (x - label.width()/2); label.css('top', labelTop); label.css('left', labelLeft); // check to make sure that the label is not outside the canvas if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0) redraw = true; if (options.series.pie.label.background.opacity != 0) { // put in the transparent background separately to avoid blended labels and label boxes var c = options.series.pie.label.background.color; if (c == null) { c = slice.color; } var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;'; $('
    ').insertBefore(label).css('opacity', options.series.pie.label.background.opacity); } } // end individual label function } // end drawLabels function } // end drawPie function } // end draw function // Placed here because it needs to be accessed from multiple locations function drawDonutHole(layer) { // draw donut hole if(options.series.pie.innerRadius > 0) { // subtract the center layer.save(); innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color layer.beginPath(); layer.fillStyle = options.series.pie.stroke.color; layer.arc(0,0,innerRadius,0,Math.PI*2,false); layer.fill(); layer.closePath(); layer.restore(); // add inner stroke layer.save(); layer.beginPath(); layer.strokeStyle = options.series.pie.stroke.color; layer.arc(0,0,innerRadius,0,Math.PI*2,false); layer.stroke(); layer.closePath(); layer.restore(); // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. } } //-- Additional Interactive related functions -- function isPointInPoly(poly, pt) { for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) && (c = !c); return c; } function findNearbySlice(mouseX, mouseY) { var slices = plot.getData(), options = plot.getOptions(), radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; for (var i = 0; i < slices.length; ++i) { var s = slices[i]; if(s.pie.show) { ctx.save(); ctx.beginPath(); ctx.moveTo(0,0); // Center of the pie //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false); ctx.closePath(); x = mouseX-centerLeft; y = mouseY-centerTop; if(ctx.isPointInPath) { if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop)) { //alert('found slice!'); ctx.restore(); return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; } } else { // excanvas for IE doesn;t support isPointInPath, this is a workaround. p1X = (radius * Math.cos(s.startAngle)); p1Y = (radius * Math.sin(s.startAngle)); p2X = (radius * Math.cos(s.startAngle+(s.angle/4))); p2Y = (radius * Math.sin(s.startAngle+(s.angle/4))); p3X = (radius * Math.cos(s.startAngle+(s.angle/2))); p3Y = (radius * Math.sin(s.startAngle+(s.angle/2))); p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5))); p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5))); p5X = (radius * Math.cos(s.startAngle+s.angle)); p5Y = (radius * Math.sin(s.startAngle+s.angle)); arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]]; arrPoint = [x,y]; // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? if(isPointInPoly(arrPoly, arrPoint)) { ctx.restore(); return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; } } ctx.restore(); } } return null; } function onMouseMove(e) { triggerClickHoverEvent('plothover', e); } function onClick(e) { triggerClickHoverEvent('plotclick', e); } // trigger click or hover event (they send the same parameters so we share their code) function triggerClickHoverEvent(eventname, e) { var offset = plot.offset(), canvasX = parseInt(e.pageX - offset.left), canvasY = parseInt(e.pageY - offset.top), item = findNearbySlice(canvasX, canvasY); if (options.grid.autoHighlight) { // clear auto-highlights for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; if (h.auto == eventname && !(item && h.series == item.series)) unhighlight(h.series); } } // highlight the slice if (item) highlight(item.series, eventname); // trigger any hover bind events var pos = { pageX: e.pageX, pageY: e.pageY }; target.trigger(eventname, [ pos, item ]); } function highlight(s, auto) { if (typeof s == "number") s = series[s]; var i = indexOfHighlight(s); if (i == -1) { highlights.push({ series: s, auto: auto }); plot.triggerRedrawOverlay(); } else if (!auto) highlights[i].auto = false; } function unhighlight(s) { if (s == null) { highlights = []; plot.triggerRedrawOverlay(); } if (typeof s == "number") s = series[s]; var i = indexOfHighlight(s); if (i != -1) { highlights.splice(i, 1); plot.triggerRedrawOverlay(); } } function indexOfHighlight(s) { for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; if (h.series == s) return i; } return -1; } function drawOverlay(plot, octx) { //alert(options.series.pie.radius); var options = plot.getOptions(); //alert(options.series.pie.radius); var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; octx.save(); octx.translate(centerLeft, centerTop); octx.scale(1, options.series.pie.tilt); for (i = 0; i < highlights.length; ++i) drawHighlight(highlights[i].series); drawDonutHole(octx); octx.restore(); function drawHighlight(series) { if (series.angle < 0) return; //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor octx.beginPath(); if (Math.abs(series.angle - Math.PI*2) > 0.000000001) octx.moveTo(0,0); // Center of the pie octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false); octx.closePath(); octx.fill(); } } } // end init (plugin body) // define pie specific options and their default values var options = { series: { pie: { show: false, radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) innerRadius:0, /* for donut */ startAngle: 3/2, tilt: 1, offset: { top: 0, left: 'auto' }, stroke: { color: '#FFF', width: 1 }, label: { show: 'auto', formatter: function(label, slice){ return '
    '+label+'
    '+Math.round(slice.percent)+'%
    '; }, // formatter function radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) background: { color: null, opacity: 0 }, threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow) }, combine: { threshold: -1, // percentage at which to combine little slices into one larger slice color: null, // color to give the new slice (auto-generated if null) label: 'Other' // label to give the new slice }, highlight: { //color: '#FFF', // will add this functionality once parseColor is available opacity: 0.5 } } } }; $.plot.plugins.push({ init: init, options: options, name: "pie", version: "1.0" }); })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.resize.js ================================================ /* Flot plugin for automatically redrawing plots as the placeholder resizes. Copyright (c) 2007-2013 IOLA and Ole Laursen. Licensed under the MIT license. It works by listening for changes on the placeholder div (through the jQuery resize event plugin) - if the size changes, it will redraw the plot. There are no options. If you need to disable the plugin for some plots, you can just fix the size of their placeholders. */ /* Inline dependency: * jQuery resize event - v1.1 - 3/14/2010 * http://benalman.com/projects/jquery-resize-plugin/ * * Copyright (c) 2010 "Cowboy" Ben Alman * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ */ (function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); (function ($) { var options = { }; // no options function init(plot) { function onResize() { var placeholder = plot.getPlaceholder(); // somebody might have hidden us and we can't plot // when we don't have the dimensions if (placeholder.width() == 0 || placeholder.height() == 0) return; plot.resize(); plot.setupGrid(); plot.draw(); } function bindEvents(plot, eventHolder) { plot.getPlaceholder().resize(onResize); } function shutdown(plot, eventHolder) { plot.getPlaceholder().unbind("resize", onResize); } plot.hooks.bindEvents.push(bindEvents); plot.hooks.shutdown.push(shutdown); } $.plot.plugins.push({ init: init, options: options, name: 'resize', version: '1.0' }); })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.spline.js ================================================ /** * Flot plugin that provides spline interpolation for line graphs * author: Alex Bardas < alex.bardas@gmail.com > * modified by: Avi Kohn https://github.com/AMKohn * based on the spline interpolation described at: * http://scaledinnovation.com/analytics/splines/aboutSplines.html * * Example usage: (add in plot options series object) * for linespline: * series: { * ... * lines: { * show: false * }, * splines: { * show: true, * tension: x, (float between 0 and 1, defaults to 0.5), * lineWidth: y (number, defaults to 2), * fill: z (float between 0 .. 1 or false, as in flot documentation) * }, * ... * } * areaspline: * series: { * ... * lines: { * show: true, * lineWidth: 0, (line drawing will not execute) * fill: x, (float between 0 .. 1, as in flot documentation) * ... * }, * splines: { * show: true, * tension: 0.5 (float between 0 and 1) * }, * ... * } * */ (function($) { 'use strict' /** * @param {Number} x0, y0, x1, y1: coordinates of the end (knot) points of the segment * @param {Number} x2, y2: the next knot (not connected, but needed to calculate p2) * @param {Number} tension: control how far the control points spread * @return {Array}: p1 -> control point, from x1 back toward x0 * p2 -> the next control point, returned to become the next segment's p1 * * @api private */ function getControlPoints(x0, y0, x1, y1, x2, y2, tension) { var pow = Math.pow, sqrt = Math.sqrt, d01, d12, fa, fb, p1x, p1y, p2x, p2y; // Scaling factors: distances from this knot to the previous and following knots. d01 = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); d12 = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); fa = tension * d01 / (d01 + d12); fb = tension - fa; p1x = x1 + fa * (x0 - x2); p1y = y1 + fa * (y0 - y2); p2x = x1 - fb * (x0 - x2); p2y = y1 - fb * (y0 - y2); return [p1x, p1y, p2x, p2y]; } var line = []; function drawLine(points, ctx, height, fill, seriesColor) { var c = $.color.parse(seriesColor); c.a = typeof fill == "number" ? fill : .3; c.normalize(); c = c.toString(); ctx.beginPath(); ctx.moveTo(points[0][0], points[0][1]); var plength = points.length; for (var i = 0; i < plength; i++) { ctx[points[i][3]].apply(ctx, points[i][2]); } ctx.stroke(); ctx.lineWidth = 0; ctx.lineTo(points[plength - 1][0], height); ctx.lineTo(points[0][0], height); ctx.closePath(); if (fill !== false) { ctx.fillStyle = c; ctx.fill(); } } /** * @param {Object} ctx: canvas context * @param {String} type: accepted strings: 'bezier' or 'quadratic' (defaults to quadratic) * @param {Array} points: 2 points for which to draw the interpolation * @param {Array} cpoints: control points for those segment points * * @api private */ function queue(ctx, type, points, cpoints) { if (type === void 0 || (type !== 'bezier' && type !== 'quadratic')) { type = 'quadratic'; } type = type + 'CurveTo'; if (line.length == 0) line.push([points[0], points[1], cpoints.concat(points.slice(2)), type]); else if (type == "quadraticCurveTo" && points.length == 2) { cpoints = cpoints.slice(0, 2).concat(points); line.push([points[0], points[1], cpoints, type]); } else line.push([points[2], points[3], cpoints.concat(points.slice(2)), type]); } /** * @param {Object} plot * @param {Object} ctx: canvas context * @param {Object} series * * @api private */ function drawSpline(plot, ctx, series) { // Not interested if spline is not requested if (series.splines.show !== true) { return; } var cp = [], // array of control points tension = series.splines.tension || 0.5, idx, x, y, points = series.datapoints.points, ps = series.datapoints.pointsize, plotOffset = plot.getPlotOffset(), len = points.length, pts = []; line = []; // Cannot display a linespline/areaspline if there are less than 3 points if (len / ps < 4) { $.extend(series.lines, series.splines); return; } for (idx = 0; idx < len; idx += ps) { x = points[idx]; y = points[idx + 1]; if (x == null || x < series.xaxis.min || x > series.xaxis.max || y < series.yaxis.min || y > series.yaxis.max) { continue; } pts.push(series.xaxis.p2c(x) + plotOffset.left, series.yaxis.p2c(y) + plotOffset.top); } len = pts.length; // Draw an open curve, not connected at the ends for (idx = 0; idx < len - 2; idx += 2) { cp = cp.concat(getControlPoints.apply(this, pts.slice(idx, idx + 6).concat([tension]))); } ctx.save(); ctx.strokeStyle = series.color; ctx.lineWidth = series.splines.lineWidth; queue(ctx, 'quadratic', pts.slice(0, 4), cp.slice(0, 2)); for (idx = 2; idx < len - 3; idx += 2) { queue(ctx, 'bezier', pts.slice(idx, idx + 4), cp.slice(2 * idx - 2, 2 * idx + 2)); } queue(ctx, 'quadratic', pts.slice(len - 2, len), [cp[2 * len - 10], cp[2 * len - 9], pts[len - 4], pts[len - 3]]); drawLine(line, ctx, plot.height() + 10, series.splines.fill, series.color); ctx.restore(); } $.plot.plugins.push({ init: function(plot) { plot.hooks.drawSeries.push(drawSpline); }, options: { series: { splines: { show: false, lineWidth: 2, tension: 0.5, fill: false } } }, name: 'spline', version: '0.8.2' }); })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/flot/jquery.flot.symbol.js ================================================ /* Flot plugin that adds some extra symbols for plotting points. Copyright (c) 2007-2014 IOLA and Ole Laursen. Licensed under the MIT license. The symbols are accessed as strings through the standard symbol options: series: { points: { symbol: "square" // or "diamond", "triangle", "cross" } } */ (function ($) { function processRawData(plot, series, datapoints) { // we normalize the area of each symbol so it is approximately the // same as a circle of the given radius var handlers = { square: function (ctx, x, y, radius, shadow) { // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 var size = radius * Math.sqrt(Math.PI) / 2; ctx.rect(x - size, y - size, size + size, size + size); }, diamond: function (ctx, x, y, radius, shadow) { // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) var size = radius * Math.sqrt(Math.PI / 2); ctx.moveTo(x - size, y); ctx.lineTo(x, y - size); ctx.lineTo(x + size, y); ctx.lineTo(x, y + size); ctx.lineTo(x - size, y); }, triangle: function (ctx, x, y, radius, shadow) { // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); var height = size * Math.sin(Math.PI / 3); ctx.moveTo(x - size/2, y + height/2); ctx.lineTo(x + size/2, y + height/2); if (!shadow) { ctx.lineTo(x, y - height/2); ctx.lineTo(x - size/2, y + height/2); } }, cross: function (ctx, x, y, radius, shadow) { // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 var size = radius * Math.sqrt(Math.PI) / 2; ctx.moveTo(x - size, y - size); ctx.lineTo(x + size, y + size); ctx.moveTo(x - size, y + size); ctx.lineTo(x + size, y - size); } }; var s = series.points.symbol; if (handlers[s]) series.points.symbol = handlers[s]; } function init(plot) { plot.hooks.processDatapoints.push(processRawData); } $.plot.plugins.push({ init: init, name: 'symbols', version: '1.0' }); })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/fullscreen/jquery.fullscreen.js ================================================ /** * 基于jQuery FullScreen修改 * 新增支持IE全屏显示 * Copyright (c) 2019 ruoyi */ (function(jQuery) { /** * Sets or gets the fullscreen state. * * @param {boolean=} state * True to enable fullscreen mode, false to disable it. If not * specified then the current fullscreen state is returned. * @return {boolean|Element|jQuery|null} * When querying the fullscreen state then the current fullscreen * element (or true if browser doesn't support it) is returned * when browser is currently in full screen mode. False is returned * if browser is not in full screen mode. Null is returned if * browser doesn't support fullscreen mode at all. When setting * the fullscreen state then the current jQuery selection is * returned for chaining. * @this {jQuery} */ function fullScreen(state) { var e, func, doc; // Do nothing when nothing was selected if (!this.length) return this; // We only use the first selected element because it doesn't make sense // to fullscreen multiple elements. e = (/** @type {Element} */ this[0]); // Find the real element and the document (Depends on whether the // document itself or a HTML element was selected) if (e.ownerDocument) { doc = e.ownerDocument; } else { doc = e; e = doc.documentElement; } // When no state was specified then return the current state. if (state == null) { // When fullscreen mode is not supported then return null if (!((/** @type {?Function} */ doc["exitFullscreen"]) || (/** @type {?Function} */ doc["webkitExitFullscreen"]) || (/** @type {?Function} */ doc["webkitCancelFullScreen"]) || (/** @type {?Function} */ doc["msExitFullscreen"]) || (/** @type {?Function} */ doc["mozCancelFullScreen"]))) { return null; } // Check fullscreen state state = !!doc["fullscreenElement"] || !!doc["msFullscreenElement"] || !!doc["webkitIsFullScreen"] || !!doc["mozFullScreen"]; if (!state) return state; // Return current fullscreen element or "true" if browser doesn't // support this return (/** @type {?Element} */ doc["fullscreenElement"]) || (/** @type {?Element} */ doc["webkitFullscreenElement"]) || (/** @type {?Element} */ doc["webkitCurrentFullScreenElement"]) || (/** @type {?Element} */ doc["msFullscreenElement"]) || (/** @type {?Element} */ doc["mozFullScreenElement"]) || state; } // When state was specified then enter or exit fullscreen mode. if (state) { // Enter fullscreen func = (/** @type {?Function} */ e["requestFullscreen"]) || (/** @type {?Function} */ e["webkitRequestFullscreen"]) || (/** @type {?Function} */ e["webkitRequestFullScreen"]) || (/** @type {?Function} */ e["msRequestFullscreen"]) || (/** @type {?Function} */ e["mozRequestFullScreen"]); if (func) { func.call(e); } return this; } else { // Exit fullscreen func = (/** @type {?Function} */ doc["exitFullscreen"]) || (/** @type {?Function} */ doc["webkitExitFullscreen"]) || (/** @type {?Function} */ doc["webkitCancelFullScreen"]) || (/** @type {?Function} */ doc["msExitFullscreen"]) || (/** @type {?Function} */ doc["mozCancelFullScreen"]); if (func) func.call(doc); return this; } } /** * Toggles the fullscreen mode. * * @return {!jQuery} * The jQuery selection for chaining. * @this {jQuery} */ function toggleFullScreen() { return (/** @type {!jQuery} */ fullScreen.call(this, !fullScreen.call(this))); } /** * Handles the browser-specific fullscreenchange event and triggers * a jquery event for it. * * @param {?Event} event * The fullscreenchange event. */ function fullScreenChangeHandler(event) { jQuery(document).trigger(new jQuery.Event("fullscreenchange")); } /** * Handles the browser-specific fullscreenerror event and triggers * a jquery event for it. * * @param {?Event} event * The fullscreenerror event. */ function fullScreenErrorHandler(event) { jQuery(document).trigger(new jQuery.Event("fullscreenerror")); } /** * Installs the fullscreenchange event handler. */ function installFullScreenHandlers() { var e, change, error; // Determine event name e = document; if (e["webkitCancelFullScreen"]) { change = "webkitfullscreenchange"; error = "webkitfullscreenerror"; } else if (e["msExitFullscreen"]) { change = "MSFullscreenChange"; error = "MSFullscreenError"; } else if (e["mozCancelFullScreen"]) { change = "mozfullscreenchange"; error = "mozfullscreenerror"; } else { change = "fullscreenchange"; error = "fullscreenerror"; } // Install the event handlers jQuery(document).bind(change, fullScreenChangeHandler); jQuery(document).bind(error, fullScreenErrorHandler); } jQuery.fn["fullScreen"] = fullScreen; jQuery.fn["toggleFullScreen"] = toggleFullScreen; installFullScreenHandlers(); })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/iCheck/custom.css ================================================ /* iCheck plugin Square skin, green ----------------------------------- */ .icheckbox_square-green, .iradio_square-green { display: inline-block; *display: inline; vertical-align: middle; margin: 0; padding: 0; width: 22px; height: 22px; background: url(green.png) no-repeat; border: none; cursor: pointer; } .icheckbox_square-green-login{ display: inline-block; *display: inline; vertical-align: middle; margin: 0; padding: 0; width: 22px; height: 22px; background: url(green-login.png) no-repeat; border: none; cursor: pointer; } .icheckbox_square-green,.icheckbox_square-green-login { background-position: 0 0; } .icheckbox_square-green.hover,.icheckbox_square-green-login.hover { background-position: -24px 0; } .icheckbox_square-green.checked,.icheckbox_square-green-login.checked { background-position: -48px 0; } .icheckbox_square-green.disabled,.icheckbox_square-green.disabled-login { background-position: -72px 0; cursor: default; } .icheckbox_square-green.checked.disabled,.icheckbox_square-green-login.checked.disabled { background-position: -96px 0; } .iradio_square-green { background-position: -120px 0; } .iradio_square-green.hover { background-position: -144px 0; } .iradio_square-green.checked { background-position: -168px 0; } .iradio_square-green.disabled { background-position: -192px 0; cursor: default; } .iradio_square-green.checked.disabled { background-position: -216px 0; } /* HiDPI support */ @media (-o-min-device-pixel-ratio: 5/4), (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) { .icheckbox_square-green,.icheckbox_square-green-login, .iradio_square-green { background-image: url(green%402x.png); -webkit-background-size: 240px 24px; background-size: 240px 24px; } } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.css ================================================ /*! * Jasny Bootstrap v3.1.3 (http://jasny.github.io/bootstrap) * Copyright 2012-2014 Arnold Daniels * Licensed under Apache-2.0 (https://github.com/jasny/bootstrap/blob/master/LICENSE) */ .container-smooth { max-width: 1170px; } @media (min-width: 1px) { .container-smooth { width: auto; } } .btn-labeled { padding-top: 0; padding-bottom: 0; } .btn-label { position: relative; left: -12px; display: inline-block; padding: 6px 12px; background: transparent; background: rgba(0, 0, 0, .15); border-radius: 3px 0 0 3px; } .btn-label.btn-label-right { right: -12px; left: auto; border-radius: 0 3px 3px 0; } .btn-lg .btn-label { left: -16px; padding: 10px 16px; border-radius: 5px 0 0 5px; } .btn-lg .btn-label.btn-label-right { right: -16px; left: auto; border-radius: 0 5px 5px 0; } .btn-sm .btn-label { left: -10px; padding: 5px 10px; border-radius: 2px 0 0 2px; } .btn-sm .btn-label.btn-label-right { right: -10px; left: auto; border-radius: 0 2px 2px 0; } .btn-xs .btn-label { left: -5px; padding: 1px 5px; border-radius: 2px 0 0 2px; } .btn-xs .btn-label.btn-label-right { right: -5px; left: auto; border-radius: 0 2px 2px 0; } .nav-tabs-bottom { border-top: 1px solid #ddd; border-bottom: 0; } .nav-tabs-bottom > li { margin-top: -1px; margin-bottom: 0; } .nav-tabs-bottom > li > a { border-radius: 0 0 4px 4px; } .nav-tabs-bottom > li > a:hover, .nav-tabs-bottom > li > a:focus, .nav-tabs-bottom > li.active > a, .nav-tabs-bottom > li.active > a:hover, .nav-tabs-bottom > li.active > a:focus { border: 1px solid #ddd; border-top-color: transparent; } .nav-tabs-left { border-right: 1px solid #ddd; border-bottom: 0; } .nav-tabs-left > li { float: none; margin-right: -1px; margin-bottom: 0; } .nav-tabs-left > li > a { margin-right: 0; margin-bottom: 2px; border-radius: 4px 0 0 4px; } .nav-tabs-left > li > a:hover, .nav-tabs-left > li > a:focus, .nav-tabs-left > li.active > a, .nav-tabs-left > li.active > a:hover, .nav-tabs-left > li.active > a:focus { border: 1px solid #ddd; border-right-color: transparent; } .row > .nav-tabs-left { position: relative; z-index: 1; padding-right: 0; padding-left: 15px; margin-right: -1px; } .row > .nav-tabs-left + .tab-content { border-left: 1px solid #ddd; } .nav-tabs-right { border-bottom: 0; border-left: 1px solid #ddd; } .nav-tabs-right > li { float: none; margin-bottom: 0; margin-left: -1px; } .nav-tabs-right > li > a { margin-bottom: 2px; margin-left: 0; border-radius: 0 4px 4px 0; } .nav-tabs-right > li > a:hover, .nav-tabs-right > li > a:focus, .nav-tabs-right > li.active > a, .nav-tabs-right > li.active > a:hover, .nav-tabs-right > li.active > a:focus { border: 1px solid #ddd; border-left-color: transparent; } .row > .nav-tabs-right { padding-right: 15px; padding-left: 0; } .navmenu, .navbar-offcanvas { width: 300px; height: auto; border-style: solid; border-width: 1px; border-radius: 4px; } .navmenu-fixed-left, .navmenu-fixed-right, .navbar-offcanvas { position: fixed; top: 0; bottom: 0; z-index: 1030; overflow-y: auto; border-radius: 0; } .navmenu-fixed-left, .navbar-offcanvas.navmenu-fixed-left { right: auto; left: 0; border-width: 0 1px 0 0; } .navmenu-fixed-right, .navbar-offcanvas { right: 0; left: auto; border-width: 0 0 0 1px; } .navmenu-nav { margin-bottom: 10px; } .navmenu-nav.dropdown-menu { position: static; float: none; padding-top: 0; margin: 0; border: none; border-radius: 0; -webkit-box-shadow: none; box-shadow: none; } .navbar-offcanvas .navbar-nav { margin: 0; } @media (min-width: 768px) { .navbar-offcanvas { width: auto; border-top: 0; box-shadow: none; } .navbar-offcanvas.offcanvas { position: static; display: block !important; height: auto !important; padding-bottom: 0; overflow: visible !important; } .navbar-offcanvas .navbar-nav.navbar-left:first-child { margin-left: -15px; } .navbar-offcanvas .navbar-nav.navbar-right:last-child { margin-right: -15px; } .navbar-offcanvas .navmenu-brand { display: none; } } .navmenu-brand { display: block; padding: 10px 15px; margin: 10px 0; font-size: 18px; line-height: 20px; } .navmenu-brand:hover, .navmenu-brand:focus { text-decoration: none; } .navmenu-default, .navbar-default .navbar-offcanvas { background-color: #f8f8f8; border-color: #e7e7e7; } .navmenu-default .navmenu-brand, .navbar-default .navbar-offcanvas .navmenu-brand { color: #777; } .navmenu-default .navmenu-brand:hover, .navbar-default .navbar-offcanvas .navmenu-brand:hover, .navmenu-default .navmenu-brand:focus, .navbar-default .navbar-offcanvas .navmenu-brand:focus { color: #5e5e5e; background-color: transparent; } .navmenu-default .navmenu-text, .navbar-default .navbar-offcanvas .navmenu-text { color: #777; } .navmenu-default .navmenu-nav > .dropdown > a:hover .caret, .navbar-default .navbar-offcanvas .navmenu-nav > .dropdown > a:hover .caret, .navmenu-default .navmenu-nav > .dropdown > a:focus .caret, .navbar-default .navbar-offcanvas .navmenu-nav > .dropdown > a:focus .caret { border-top-color: #333; border-bottom-color: #333; } .navmenu-default .navmenu-nav > .open > a, .navbar-default .navbar-offcanvas .navmenu-nav > .open > a, .navmenu-default .navmenu-nav > .open > a:hover, .navbar-default .navbar-offcanvas .navmenu-nav > .open > a:hover, .navmenu-default .navmenu-nav > .open > a:focus, .navbar-default .navbar-offcanvas .navmenu-nav > .open > a:focus { color: #555; background-color: #e7e7e7; } .navmenu-default .navmenu-nav > .open > a .caret, .navbar-default .navbar-offcanvas .navmenu-nav > .open > a .caret, .navmenu-default .navmenu-nav > .open > a:hover .caret, .navbar-default .navbar-offcanvas .navmenu-nav > .open > a:hover .caret, .navmenu-default .navmenu-nav > .open > a:focus .caret, .navbar-default .navbar-offcanvas .navmenu-nav > .open > a:focus .caret { border-top-color: #555; border-bottom-color: #555; } .navmenu-default .navmenu-nav > .dropdown > a .caret, .navbar-default .navbar-offcanvas .navmenu-nav > .dropdown > a .caret { border-top-color: #777; border-bottom-color: #777; } .navmenu-default .navmenu-nav.dropdown-menu, .navbar-default .navbar-offcanvas .navmenu-nav.dropdown-menu { background-color: #e7e7e7; } .navmenu-default .navmenu-nav.dropdown-menu > .divider, .navbar-default .navbar-offcanvas .navmenu-nav.dropdown-menu > .divider { background-color: #f8f8f8; } .navmenu-default .navmenu-nav.dropdown-menu > .active > a, .navbar-default .navbar-offcanvas .navmenu-nav.dropdown-menu > .active > a, .navmenu-default .navmenu-nav.dropdown-menu > .active > a:hover, .navbar-default .navbar-offcanvas .navmenu-nav.dropdown-menu > .active > a:hover, .navmenu-default .navmenu-nav.dropdown-menu > .active > a:focus, .navbar-default .navbar-offcanvas .navmenu-nav.dropdown-menu > .active > a:focus { background-color: #d7d7d7; } .navmenu-default .navmenu-nav > li > a, .navbar-default .navbar-offcanvas .navmenu-nav > li > a { color: #777; } .navmenu-default .navmenu-nav > li > a:hover, .navbar-default .navbar-offcanvas .navmenu-nav > li > a:hover, .navmenu-default .navmenu-nav > li > a:focus, .navbar-default .navbar-offcanvas .navmenu-nav > li > a:focus { color: #333; background-color: transparent; } .navmenu-default .navmenu-nav > .active > a, .navbar-default .navbar-offcanvas .navmenu-nav > .active > a, .navmenu-default .navmenu-nav > .active > a:hover, .navbar-default .navbar-offcanvas .navmenu-nav > .active > a:hover, .navmenu-default .navmenu-nav > .active > a:focus, .navbar-default .navbar-offcanvas .navmenu-nav > .active > a:focus { color: #555; background-color: #e7e7e7; } .navmenu-default .navmenu-nav > .disabled > a, .navbar-default .navbar-offcanvas .navmenu-nav > .disabled > a, .navmenu-default .navmenu-nav > .disabled > a:hover, .navbar-default .navbar-offcanvas .navmenu-nav > .disabled > a:hover, .navmenu-default .navmenu-nav > .disabled > a:focus, .navbar-default .navbar-offcanvas .navmenu-nav > .disabled > a:focus { color: #ccc; background-color: transparent; } .navmenu-inverse, .navbar-inverse .navbar-offcanvas { background-color: #222; border-color: #080808; } .navmenu-inverse .navmenu-brand, .navbar-inverse .navbar-offcanvas .navmenu-brand { color: #999; } .navmenu-inverse .navmenu-brand:hover, .navbar-inverse .navbar-offcanvas .navmenu-brand:hover, .navmenu-inverse .navmenu-brand:focus, .navbar-inverse .navbar-offcanvas .navmenu-brand:focus { color: #fff; background-color: transparent; } .navmenu-inverse .navmenu-text, .navbar-inverse .navbar-offcanvas .navmenu-text { color: #999; } .navmenu-inverse .navmenu-nav > .dropdown > a:hover .caret, .navbar-inverse .navbar-offcanvas .navmenu-nav > .dropdown > a:hover .caret, .navmenu-inverse .navmenu-nav > .dropdown > a:focus .caret, .navbar-inverse .navbar-offcanvas .navmenu-nav > .dropdown > a:focus .caret { border-top-color: #fff; border-bottom-color: #fff; } .navmenu-inverse .navmenu-nav > .open > a, .navbar-inverse .navbar-offcanvas .navmenu-nav > .open > a, .navmenu-inverse .navmenu-nav > .open > a:hover, .navbar-inverse .navbar-offcanvas .navmenu-nav > .open > a:hover, .navmenu-inverse .navmenu-nav > .open > a:focus, .navbar-inverse .navbar-offcanvas .navmenu-nav > .open > a:focus { color: #fff; background-color: #080808; } .navmenu-inverse .navmenu-nav > .open > a .caret, .navbar-inverse .navbar-offcanvas .navmenu-nav > .open > a .caret, .navmenu-inverse .navmenu-nav > .open > a:hover .caret, .navbar-inverse .navbar-offcanvas .navmenu-nav > .open > a:hover .caret, .navmenu-inverse .navmenu-nav > .open > a:focus .caret, .navbar-inverse .navbar-offcanvas .navmenu-nav > .open > a:focus .caret { border-top-color: #fff; border-bottom-color: #fff; } .navmenu-inverse .navmenu-nav > .dropdown > a .caret, .navbar-inverse .navbar-offcanvas .navmenu-nav > .dropdown > a .caret { border-top-color: #999; border-bottom-color: #999; } .navmenu-inverse .navmenu-nav.dropdown-menu, .navbar-inverse .navbar-offcanvas .navmenu-nav.dropdown-menu { background-color: #080808; } .navmenu-inverse .navmenu-nav.dropdown-menu > .divider, .navbar-inverse .navbar-offcanvas .navmenu-nav.dropdown-menu > .divider { background-color: #222; } .navmenu-inverse .navmenu-nav.dropdown-menu > .active > a, .navbar-inverse .navbar-offcanvas .navmenu-nav.dropdown-menu > .active > a, .navmenu-inverse .navmenu-nav.dropdown-menu > .active > a:hover, .navbar-inverse .navbar-offcanvas .navmenu-nav.dropdown-menu > .active > a:hover, .navmenu-inverse .navmenu-nav.dropdown-menu > .active > a:focus, .navbar-inverse .navbar-offcanvas .navmenu-nav.dropdown-menu > .active > a:focus { background-color: #000; } .navmenu-inverse .navmenu-nav > li > a, .navbar-inverse .navbar-offcanvas .navmenu-nav > li > a { color: #999; } .navmenu-inverse .navmenu-nav > li > a:hover, .navbar-inverse .navbar-offcanvas .navmenu-nav > li > a:hover, .navmenu-inverse .navmenu-nav > li > a:focus, .navbar-inverse .navbar-offcanvas .navmenu-nav > li > a:focus { color: #fff; background-color: transparent; } .navmenu-inverse .navmenu-nav > .active > a, .navbar-inverse .navbar-offcanvas .navmenu-nav > .active > a, .navmenu-inverse .navmenu-nav > .active > a:hover, .navbar-inverse .navbar-offcanvas .navmenu-nav > .active > a:hover, .navmenu-inverse .navmenu-nav > .active > a:focus, .navbar-inverse .navbar-offcanvas .navmenu-nav > .active > a:focus { color: #fff; background-color: #080808; } .navmenu-inverse .navmenu-nav > .disabled > a, .navbar-inverse .navbar-offcanvas .navmenu-nav > .disabled > a, .navmenu-inverse .navmenu-nav > .disabled > a:hover, .navbar-inverse .navbar-offcanvas .navmenu-nav > .disabled > a:hover, .navmenu-inverse .navmenu-nav > .disabled > a:focus, .navbar-inverse .navbar-offcanvas .navmenu-nav > .disabled > a:focus { color: #444; background-color: transparent; } .alert-fixed-top, .alert-fixed-bottom { position: fixed; left: 0; z-index: 1035; width: 100%; margin: 0; border-radius: 0; } @media (min-width: 992px) { .alert-fixed-top, .alert-fixed-bottom { left: 50%; width: 992px; margin-left: -496px; } } .alert-fixed-top { top: 0; border-width: 0 0 1px 0; } @media (min-width: 992px) { .alert-fixed-top { border-width: 0 1px 1px 1px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } } .alert-fixed-bottom { bottom: 0; border-width: 1px 0 0 0; } @media (min-width: 992px) { .alert-fixed-bottom { border-width: 1px 1px 0 1px; border-top-left-radius: 4px; border-top-right-radius: 4px; } } .offcanvas { display: none; } .offcanvas.in { display: block; } @media (max-width: 767px) { .offcanvas-xs { display: none; } .offcanvas-xs.in { display: block; } } @media (max-width: 991px) { .offcanvas-sm { display: none; } .offcanvas-sm.in { display: block; } } @media (max-width: 1199px) { .offcanvas-md { display: none; } .offcanvas-md.in { display: block; } } .offcanvas-lg { display: none; } .offcanvas-lg.in { display: block; } .canvas-sliding { -webkit-transition: top .35s, left .35s, bottom .35s, right .35s; transition: top .35s, left .35s, bottom .35s, right .35s; } .offcanvas-clone { position: absolute !important; top: auto !important; right: 0 !important; bottom: 0 !important; left: auto !important; width: 0 !important; height: 0 !important; padding: 0 !important; margin: 0 !important; overflow: hidden !important; border: none !important; opacity: 0 !important; } .table.rowlink td:not(.rowlink-skip), .table .rowlink td:not(.rowlink-skip) { cursor: pointer; } .table.rowlink td:not(.rowlink-skip) a, .table .rowlink td:not(.rowlink-skip) a { font: inherit; color: inherit; text-decoration: inherit; } .table-hover.rowlink tr:hover td, .table-hover .rowlink tr:hover td { background-color: #cfcfcf; } .btn-file { position: relative; overflow: hidden; vertical-align: middle; } .btn-file > input { position: absolute; top: 0; right: 0; width: 100%; height: 100%; margin: 0; font-size: 23px; cursor: pointer; filter: alpha(opacity=0); opacity: 0; direction: ltr; } .fileinput { display: inline-block; margin-bottom: 9px; } .fileinput .form-control { display: inline-block; padding-top: 7px; padding-bottom: 5px; margin-bottom: 0; vertical-align: middle; cursor: text; } .fileinput .thumbnail { display: inline-block; margin-bottom: 5px; overflow: hidden; text-align: center; vertical-align: middle; } .fileinput .thumbnail > img { max-height: 100%; } .fileinput .btn { vertical-align: middle; } .fileinput-exists .fileinput-new, .fileinput-new .fileinput-exists { display: none; } .fileinput-inline .fileinput-controls { display: inline; } .fileinput-filename { display: inline-block; overflow: hidden; vertical-align: middle; } .form-control .fileinput-filename { vertical-align: bottom; } .fileinput.input-group { display: table; } .fileinput.input-group > * { position: relative; z-index: 2; } .fileinput.input-group > .btn-file { z-index: 1; } .fileinput-new.input-group .btn-file, .fileinput-new .input-group .btn-file { border-radius: 0 4px 4px 0; } .fileinput-new.input-group .btn-file.btn-xs, .fileinput-new .input-group .btn-file.btn-xs, .fileinput-new.input-group .btn-file.btn-sm, .fileinput-new .input-group .btn-file.btn-sm { border-radius: 0 3px 3px 0; } .fileinput-new.input-group .btn-file.btn-lg, .fileinput-new .input-group .btn-file.btn-lg { border-radius: 0 6px 6px 0; } .form-group.has-warning .fileinput .fileinput-preview { color: #8a6d3b; } .form-group.has-warning .fileinput .thumbnail { border-color: #faebcc; } .form-group.has-error .fileinput .fileinput-preview { color: #a94442; } .form-group.has-error .fileinput .thumbnail { border-color: #ebccd1; } .form-group.has-success .fileinput .fileinput-preview { color: #3c763d; } .form-group.has-success .fileinput .thumbnail { border-color: #d6e9c6; } .input-group-addon:not(:first-child) { border-left: 0; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jasny/jasny-bootstrap.js ================================================ /*! * Jasny Bootstrap v4.0.0 (http://jasny.github.io/bootstrap) * Copyright 2012-2014 Arnold Daniels * Licensed under Apache-2.0 (https://github.com/jasny/bootstrap/blob/master/LICENSE) */ +function ($) { "use strict"; var isIE = window.navigator.appName == 'Microsoft Internet Explorer' // FILEUPLOAD PUBLIC CLASS DEFINITION // ================================= var Fileinput = function (element, options) { this.$element = $(element) this.options = $.extend({}, Fileinput.DEFAULTS, options) this.$input = this.$element.find(':file') if (this.$input.length === 0) return this.name = this.$input.attr('name') || options.name this.$hidden = this.$element.find('input[type=hidden][name="' + this.name + '"]') if (this.$hidden.length === 0) { this.$hidden = $('').insertBefore(this.$input) } this.$preview = this.$element.find('.fileinput-preview') var height = this.$preview.css('height') if (this.$preview.css('display') !== 'inline' && height !== '0px' && height !== 'none') { this.$preview.css('line-height', height) } this.original = { exists: this.$element.hasClass('fileinput-exists'), preview: this.$preview.html(), hiddenVal: this.$hidden.val() } this.listen() this.reset() } Fileinput.DEFAULTS = { clearName: true } Fileinput.prototype.listen = function() { this.$input.on('change.bs.fileinput', $.proxy(this.change, this)) $(this.$input[0].form).on('reset.bs.fileinput', $.proxy(this.reset, this)) this.$element.find('[data-trigger="fileinput"]').on('click.bs.fileinput', $.proxy(this.trigger, this)) this.$element.find('[data-dismiss="fileinput"]').on('click.bs.fileinput', $.proxy(this.clear, this)) }, Fileinput.prototype.verifySizes = function(files) { if (typeof this.options.maxSize === 'undefined') return true var max = parseFloat(this.options.maxSize) if (max !== this.options.maxSize) return true for (var i = 0; i < files.length; i++) { var size = typeof files[i].size !== 'undefined' ? files[i].size : null if (size === null) continue size = size / 1000 / 1000 /* convert from bytes to MB */ if (size > max) return false } return true } Fileinput.prototype.change = function(e) { var files = e.target.files === undefined ? (e.target && e.target.value ? [{ name: e.target.value.replace(/^.+\\/, '')}] : []) : e.target.files e.stopPropagation() if (files.length === 0) { this.clear() this.$element.trigger('clear.bs.fileinput') return } if (!this.verifySizes(files)) { this.$element.trigger('max_size.bs.fileinput') this.clear() this.$element.trigger('clear.bs.fileinput') return } this.$hidden.val('') this.$hidden.attr('name', '') this.$input.attr('name', this.name) var file = files[0] if (this.$preview.length > 0 && (typeof file.type !== "undefined" ? file.type.match(/^image\/(gif|png|jpeg|svg\+xml)$/) : file.name.match(/\.(gif|png|jpe?g|svg)$/i)) && typeof FileReader !== "undefined") { var Fileinput = this var reader = new FileReader() var preview = this.$preview var element = this.$element reader.onload = function(re) { var $img = $('') $img[0].src = re.target.result files[0].result = re.target.result element.find('.fileinput-filename').text(file.name) // if parent has max-height, using `(max-)height: 100%` on child doesn't take padding and border into account if (preview.css('max-height') != 'none') { var mh = parseInt(preview.css('max-height'), 10) || 0 var pt = parseInt(preview.css('padding-top'), 10) || 0 var pb = parseInt(preview.css('padding-bottom'), 10) || 0 var bt = parseInt(preview.css('border-top'), 10) || 0 var bb = parseInt(preview.css('border-bottom'), 10) || 0 $img.css('max-height', mh - pt - pb - bt - bb) } preview.html($img) if (Fileinput.options.exif) { //Fix image tranformation if this is possible Fileinput.setImageTransform($img, file); } element.addClass('fileinput-exists').removeClass('fileinput-new') element.trigger('change.bs.fileinput', files) } reader.readAsDataURL(file) } else { var text = file.name var $nameView = this.$element.find('.fileinput-filename') if (files.length > 1) { text = $.map(files, function(file) { return file.name; }).join(', ') } $nameView.text(text) this.$preview.text(file.name) this.$element.addClass('fileinput-exists').removeClass('fileinput-new') this.$element.trigger('change.bs.fileinput') } }, Fileinput.prototype.setImageTransform = function($img, file) { var Fileinput = this; var reader = new FileReader(); reader.onload = function(me) { var transform = false; var view = new DataView(reader.result); var exif = Fileinput.getImageExif(view); if (exif) { Fileinput.resetOrientation($img, exif); } } reader.readAsArrayBuffer(file); } Fileinput.prototype.getImageExif = function(view) { if (view.getUint16(0, false) != 0xFFD8) { return -2; } var length = view.byteLength, offset = 2; while (offset < length) { var marker = view.getUint16(offset, false); offset += 2; if (marker == 0xFFE1) { if (view.getUint32(offset += 2, false) != 0x45786966) { return -1; } var little = view.getUint16(offset += 6, false) == 0x4949; offset += view.getUint32(offset + 4, little); var tags = view.getUint16(offset, little); offset += 2; for (var i = 0; i < tags; i++) { if (view.getUint16(offset + (i * 12), little) == 0x0112) { return view.getUint16(offset + (i * 12) + 8, little); } } } else if ((marker & 0xFF00) != 0xFF00){ break; } else { offset += view.getUint16(offset, false); } } return -1; } Fileinput.prototype.resetOrientation = function($img, transform) { var img = new Image(); img.onload = function() { var width = img.width, height = img.height, canvas = document.createElement('canvas'), ctx = canvas.getContext("2d"); // set proper canvas dimensions before transform & export if ([5,6,7,8].indexOf(transform) > -1) { canvas.width = height; canvas.height = width; } else { canvas.width = width; canvas.height = height; } // transform context before drawing image switch (transform) { case 2: ctx.transform(-1, 0, 0, 1, width, 0); break; case 3: ctx.transform(-1, 0, 0, -1, width, height ); break; case 4: ctx.transform(1, 0, 0, -1, 0, height ); break; case 5: ctx.transform(0, 1, 1, 0, 0, 0); break; case 6: ctx.transform(0, 1, -1, 0, height , 0); break; case 7: ctx.transform(0, -1, -1, 0, height , width); break; case 8: ctx.transform(0, -1, 1, 0, 0, width); break; default: ctx.transform(1, 0, 0, 1, 0, 0); } // draw image ctx.drawImage(img, 0, 0); // export base64 $img.attr('src', canvas.toDataURL()); }; img.src = $img.attr('src'); }; Fileinput.prototype.clear = function(e) { if (e) e.preventDefault() this.$hidden.val('') this.$hidden.attr('name', this.name) if (this.options.clearName) this.$input.attr('name', '') //ie8+ doesn't support changing the value of input with type=file so clone instead if (isIE) { var inputClone = this.$input.clone(true); this.$input.after(inputClone); this.$input.remove(); this.$input = inputClone; } else { this.$input.val('') } this.$preview.html('') this.$element.find('.fileinput-filename').text('') this.$element.addClass('fileinput-new').removeClass('fileinput-exists') if (e !== undefined) { this.$input.trigger('change') this.$element.trigger('clear.bs.fileinput') } }, Fileinput.prototype.reset = function() { this.clear() this.$hidden.val(this.original.hiddenVal) this.$preview.html(this.original.preview) this.$element.find('.fileinput-filename').text('') if (this.original.exists) this.$element.addClass('fileinput-exists').removeClass('fileinput-new') else this.$element.addClass('fileinput-new').removeClass('fileinput-exists') this.$element.trigger('reseted.bs.fileinput') }, Fileinput.prototype.trigger = function(e) { this.$input.trigger('click') e.preventDefault() } // FILEUPLOAD PLUGIN DEFINITION // =========================== var old = $.fn.fileinput $.fn.fileinput = function (options) { return this.each(function () { var $this = $(this), data = $this.data('bs.fileinput') if (!data) $this.data('bs.fileinput', (data = new Fileinput(this, options))) if (typeof options == 'string') data[options]() }) } $.fn.fileinput.Constructor = Fileinput // FILEINPUT NO CONFLICT // ==================== $.fn.fileinput.noConflict = function () { $.fn.fileinput = old return this } // FILEUPLOAD DATA-API // ================== $(document).on('click.fileinput.data-api', '[data-provides="fileinput"]', function (e) { var $this = $(this) if ($this.data('bs.fileinput')) return $this.fileinput($this.data()) var $target = $(e.target).closest('[data-dismiss="fileinput"],[data-trigger="fileinput"]'); if ($target.length > 0) { e.preventDefault() $target.trigger('click.bs.fileinput') } }) }(window.jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-layout/jquery.layout-latest.css ================================================ .ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-layout-pane{overflow:auto}.ui-layout-content{padding:10px;position:relative;overflow:auto;width:100%;border:0}.layout-child-container,.layout-content-container{padding:0;overflow:hidden}.layout-child-container{border:0}.layout-scroll{overflow:auto}.layout-hide{display:none}.ui-layout-resizer{background:#fafafa;border:1px solid #eee;border-width:0}.ui-layout-resizer-open-hover,.ui-layout-resizer-dragging{background:#fafafa}.ui-layout-resizer-dragging{border:1px solid #eee}.ui-layout-resizer-north-dragging,.ui-layout-resizer-south-dragging{border-width:1px 0}.ui-layout-resizer-west-dragging,.ui-layout-resizer-east-dragging{border-width:0 1px}.ui-layout-resizer-dragging-limit{background:#e1a4a4}.ui-layout-resizer-closed-hover{background:#ebd5aa}.ui-layout-resizer-north-sliding-hover{border-bottom-width:1px}.ui-layout-resizer-south-sliding-hover{border-top-width:1px}.ui-layout-resizer-west-sliding-hover{border-right-width:1px}.ui-layout-resizer-east-sliding-hover{border-left-width:1px}.ui-layout-toggler{border:1px solid #eee;background-color:#eee}.ui-layout-resizer-hover .ui-layout-toggler{opacity:1.00;filter:alpha(opacity=100)}.ui-layout-toggler-hover,.ui-layout-resizer-hover .ui-layout-toggler-hover{background-color:#FC6;opacity:1.00;filter:alpha(opacity=100)}.ui-layout-toggler-north,.ui-layout-toggler-south{border-width:0 1px}.ui-layout-toggler-west,.ui-layout-toggler-east{border-width:1px 0}.ui-layout-resizer-sliding .ui-layout-toggler{display:none}.ui-layout-toggler .ui-content{color:#666;font-size:12px;font-weight:bold;line-height:8px;width:100%;padding-bottom:.35ex}.ui-layout-toggler .ui-content .fa{line-height:8px}.ui-layout-toggler-north-closed .fa:before,.ui-layout-toggler-south-open .fa:before{content:"\f0d7"}.ui-layout-toggler-south-closed .fa:before,.ui-layout-toggler-north-open .fa:before{content:"\f0d8"}.ui-layout-toggler-west-closed .fa:before,.ui-layout-toggler-east-open .fa:before{content:"\f0da"}.ui-layout-toggler-east-closed .fa:before,.ui-layout-toggler-west-open .fa:before{content:"\f0d9"}.ui-layout-mask{border:none!important;padding:0!important;margin:0!important;overflow:hidden!important;position:absolute!important;opacity:0!important;filter:Alpha(Opacity="0")!important}.ui-layout-mask-inside-pane{top:0!important;left:0!important;width:100%!important;height:100%!important}@media print{html{height:auto!important;overflow:visible!important}body.ui-layout-container{position:static!important;top:auto!important;bottom:auto!important;left:auto!important;right:auto!important;_width:auto!important;_height:auto!important}.ui-layout-resizer,.ui-layout-toggler{display:none!important}.ui-layout-pane{border:none!important;background:transparent!important;position:relative!important;top:auto!important;bottom:auto!important;left:auto!important;right:auto!important;width:auto!important;height:auto!important;overflow:visible!important}} ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-layout/jquery.layout-latest.js ================================================ (function($){var min=Math.min,max=Math.max,round=Math.floor,isStr=function(v){return $.type(v)==="string"},runPluginCallbacks=function(Instance,a_fn){if($.isArray(a_fn)){for(var i=0,c=a_fn.length;i
    ').appendTo("body"),d={width:$c.outerWidth-$c[0].clientWidth,height:100-$c[0].clientHeight};$c.remove();window.scrollbarWidth=d.width;window.scrollbarHeight=d.height;return dim.match(/^(width|height)$/)?d[dim]:d},disableTextSelection:function(){var $d=$(document),s="textSelectionDisabled",x="textSelectionInitialized";if($.fn.disableSelection){if(!$d.data(x)){$d.on("mouseup",$.layout.enableTextSelection).data(x,true)}if(!$d.data(s)){$d.disableSelection().data(s,true)}}},enableTextSelection:function(){var $d=$(document),s="textSelectionDisabled";if($.fn.enableSelection&&$d.data(s)){$d.enableSelection().data(s,false)}},showInvisibly:function($E,force){if($E&&$E.length&&(force||$E.css("display")==="none")){var s=$E[0].style,CSS={display:s.display||"",visibility:s.visibility||""};$E.css({display:"block",visibility:"hidden"});return CSS}return{}},getElementDimensions:function($E,inset){var d={css:{},inset:{}},x=d.css,i={bottom:0},N=$.layout.cssNum,R=Math.round,off=$E.offset(),b,p,ei;d.offsetLeft=off.left;d.offsetTop=off.top;if(!inset){inset={}}$.each("Left,Right,Top,Bottom".split(","),function(idx,e){b=x["border"+e]=$.layout.borderWidth($E,e);p=x["padding"+e]=$.layout.cssNum($E,"padding"+e);ei=e.toLowerCase();d.inset[ei]=inset[ei]>=0?inset[ei]:p;i[ei]=d.inset[ei]+b});x.width=R($E.width());x.height=R($E.height());x.top=N($E,"top",true);x.bottom=N($E,"bottom",true);x.left=N($E,"left",true);x.right=N($E,"right",true);d.outerWidth=R($E.outerWidth());d.outerHeight=R($E.outerHeight());d.innerWidth=max(0,d.outerWidth-i.left-i.right);d.innerHeight=max(0,d.outerHeight-i.top-i.bottom);d.layoutWidth=R($E.innerWidth());d.layoutHeight=R($E.innerHeight());return d},getElementStyles:function($E,list){var CSS={},style=$E[0].style,props=list.split(","),sides="Top,Bottom,Left,Right".split(","),attrs="Color,Style,Width".split(","),p,s,a,i,j,k;for(i=0;i=L&&x<=R)&&(y>=T&&y<=B))},msg:function(info,popup,debugTitle,debugOpts){if($.isPlainObject(info)&&window.debugData){if(typeof popup==="string"){debugOpts=debugTitle;debugTitle=popup}else{if(typeof debugTitle==="object"){debugOpts=debugTitle;debugTitle=null}}var t=debugTitle||"log( )",o=$.extend({sort:false,returnHTML:false,display:false},debugOpts);if(popup===true||o.display){debugData(info,t,o)}else{if(window.console){console.log(debugData(info,t,o))}}}else{if(popup){alert(info)}else{if(window.console){console.log(info)}else{var id="#layoutLogger",$l=$(id);if(!$l.length){$l=createLog()}$l.children("ul").append('
  • '+info.replace(/\/g,">")+"
  • ")}}}function createLog(){var pos=$.support.fixedPosition?"fixed":"absolute",$e=$('
    '+'
    '+'XLayout console.log
    '+'
      '+"
      ").appendTo("body");$e.css("left",$(window).width()-$e.outerWidth()-5);if($.ui.draggable){$e.draggable({handle:":first-child"})}return $e}}};(function(){var u=navigator.userAgent.toLowerCase(),m=/(chrome)[ \/]([\w.]+)/.exec(u)||/(webkit)[ \/]([\w.]+)/.exec(u)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(u)||/(msie) ([\w.]+)/.exec(u)||u.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(u)||[],b=m[1]||"",v=m[2]||0,ie=b==="msie",cm=document.compatMode,$s=$.support,bs=$s.boxSizing!==undefined?$s.boxSizing:$s.boxSizingReliable,bm=!ie||!cm||cm==="CSS1Compat"||$s.boxModel||false,lb=$.layout.browser={version:v,safari:b==="webkit",webkit:b==="chrome",msie:ie,isIE6:ie&&v==6,boxModel:bm,boxSizing:!!(typeof bs==="function"?bs():bs)};if(b){lb[b]=true}if(!bm&&!cm){$(function(){lb.boxModel=$s.boxModel})}})();$.layout.defaults={name:"",containerClass:"ui-layout-container",inset:null,scrollToBookmarkOnLoad:true,resizeWithWindow:true,resizeWithWindowDelay:50,resizeWithWindowMaxDelay:0,maskPanesEarly:false,onresizeall_start:null,onresizeall_end:null,onload_start:null,onload_end:null,onunload_start:null,onunload_end:null,initPanes:true,showErrorMessages:true,showDebugMessages:false,zIndex:null,zIndexes:{pane_normal:0,content_mask:1,resizer_normal:2,pane_sliding:100,pane_animate:1000,resizer_drag:10000},errors:{pane:"pane",selector:"selector",addButtonError:"Error Adding Button\nInvalid ",containerMissing:"UI Layout Initialization Error\nThe specified layout-container does not exist.",centerPaneMissing:"UI Layout Initialization Error\nThe center-pane element does not exist.\nThe center-pane is a required element.",noContainerHeight:"UI Layout Initialization Warning\nThe layout-container \"CONTAINER\" has no height.\nTherefore the layout is 0-height and hence 'invisible'!",callbackError:"UI Layout Callback Error\nThe EVENT callback is not a valid function."},panes:{applyDemoStyles:false,closable:true,resizable:true,slidable:true,initClosed:false,initHidden:false,contentSelector:".ui-layout-content",contentIgnoreSelector:".ui-layout-ignore",findNestedContent:true,paneClass:"ui-layout-pane",resizerClass:"ui-layout-resizer",togglerClass:"ui-layout-toggler",buttonClass:"ui-layout-button",minSize:0,maxSize:0,spacing_open:8,spacing_closed:8,togglerLength_open:50,togglerLength_closed:50,togglerAlign_open:"center",togglerAlign_closed:"center",togglerContent_open:'',togglerContent_closed:'',resizerDblClickToggle:true,autoResize:true,autoReopen:true,resizerDragOpacity:1,maskContents:false,maskObjects:false,maskZindex:null,resizingGrid:false,livePaneResizing:false,liveContentResizing:false,liveResizingTolerance:1,sliderCursor:"pointer",slideTrigger_open:"mouseenter",slideTrigger_close:"mouseleave",slideDelay_open:100,slideDelay_close:500,hideTogglerOnSlide:false,preventQuickSlideClose:$.layout.browser.webkit,preventPrematureSlideClose:false,tips:{Open:"展开",Close:"折叠",Resize:"调整大小/双击折叠",Slide:"鼠标停留自动展开",Pin:"Pin",Unpin:"Un-Pin",noRoomToOpen:"Not enough room to show this panel.",minSizeWarning:"Panel has reached its minimum size",maxSizeWarning:"Panel has reached its maximum size"},showOverflowOnHover:false,enableCursorHotkey:true,customHotkeyModifier:"SHIFT",fxName:"slide",fxSpeed:null,fxSettings:{},fxOpacityFix:true,animatePaneSizing:false,children:null,containerSelector:"",initChildren:true,destroyChildren:true,resizeChildren:true,triggerEventsOnLoad:false,triggerEventsDuringLiveResize:true,onshow_start:null,onshow_end:null,onhide_start:null,onhide_end:null,onopen_start:null,onopen_end:null,onclose_start:null,onclose_end:null,onresize_start:null,onresize_end:null,onsizecontent_start:null,onsizecontent_end:null,onswap_start:null,onswap_end:null,ondrag_start:null,ondrag_end:null},north:{paneSelector:".ui-layout-north",size:"auto",resizerCursor:"n-resize",customHotkey:""},south:{paneSelector:".ui-layout-south",size:"auto",resizerCursor:"s-resize",customHotkey:""},east:{paneSelector:".ui-layout-east",size:200,resizerCursor:"e-resize",customHotkey:""},west:{paneSelector:".ui-layout-west",size:200,resizerCursor:"w-resize",customHotkey:""},center:{paneSelector:".ui-layout-center",minWidth:0,minHeight:0}};$.layout.optionsMap={layout:("name,instanceKey,stateManagement,effects,inset,zIndexes,errors,"+"zIndex,scrollToBookmarkOnLoad,showErrorMessages,maskPanesEarly,"+"outset,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"+"onresizeall,onresizeall_start,onresizeall_end,onload,onload_start,onload_end,onunload,onunload_start,onunload_end").split(","),center:("paneClass,contentSelector,contentIgnoreSelector,findNestedContent,applyDemoStyles,triggerEventsOnLoad,"+"showOverflowOnHover,maskContents,maskObjects,liveContentResizing,"+"containerSelector,children,initChildren,resizeChildren,destroyChildren,"+"onresize,onresize_start,onresize_end,onsizecontent,onsizecontent_start,onsizecontent_end").split(","),noDefault:("paneSelector,resizerCursor,customHotkey").split(",")};$.layout.transformData=function(hash,addKeys){var json=addKeys?{panes:{},center:{}}:{},branch,optKey,keys,key,val,i,c;if(typeof hash!=="object"){return json}for(optKey in hash){branch=json;val=hash[optKey];keys=optKey.split("__");c=keys.length-1;for(i=0;i<=c;i++){key=keys[i];if(i===c){if($.isPlainObject(val)){branch[key]=$.layout.transformData(val)}else{branch[key]=val}}else{if(!branch[key]){branch[key]={}}branch=branch[key]}}}return json};$.layout.backwardCompatibility={map:{applyDefaultStyles:"applyDemoStyles",childOptions:"children",initChildLayout:"initChildren",destroyChildLayout:"destroyChildren",resizeChildLayout:"resizeChildren",resizeNestedLayout:"resizeChildren",resizeWhileDragging:"livePaneResizing",resizeContentWhileDragging:"liveContentResizing",triggerEventsWhileDragging:"triggerEventsDuringLiveResize",maskIframesOnResize:"maskContents",useStateCookie:"stateManagement.enabled","cookie.autoLoad":"stateManagement.autoLoad","cookie.autoSave":"stateManagement.autoSave","cookie.keys":"stateManagement.stateKeys","cookie.name":"stateManagement.cookie.name","cookie.domain":"stateManagement.cookie.domain","cookie.path":"stateManagement.cookie.path","cookie.expires":"stateManagement.cookie.expires","cookie.secure":"stateManagement.cookie.secure",noRoomToOpenTip:"tips.noRoomToOpen",togglerTip_open:"tips.Close",togglerTip_closed:"tips.Open",resizerTip:"tips.Resize",sliderTip:"tips.Slide"},renameOptions:function(opts){var map=$.layout.backwardCompatibility.map,oldData,newData,value;for(var itemPath in map){oldData=getBranch(itemPath);value=oldData.branch[oldData.key];if(value!==undefined){newData=getBranch(map[itemPath],true);newData.branch[newData.key]=value;delete oldData.branch[oldData.key]}}function getBranch(path,create){var a=path.split("."),c=a.length-1,D={branch:opts,key:a[c]},i=0,k,undef;for(;i0){if(autoHide&&$E.data("autoHidden")&&$E.innerHeight()>0){$E.show().data("autoHidden",false);if(!browser.mozilla){$E.css(_c.hidden).css(_c.visible)}}}else{if(autoHide&&!$E.data("autoHidden")){$E.hide().data("autoHidden",true)}}},setOuterHeight=function(el,outerHeight,autoHide){var $E=el,h;if(isStr(el)){$E=$Ps[el]}else{if(!el.jquery){$E=$(el)}}h=cssH($E,outerHeight);$E.css({height:h,visibility:"visible"});if(h>0&&$E.innerWidth()>0){if(autoHide&&$E.data("autoHidden")){$E.show().data("autoHidden",false);if(!browser.mozilla){$E.css(_c.hidden).css(_c.visible)}}}else{if(autoHide&&!$E.data("autoHidden")){$E.hide().data("autoHidden",true)}}},_parseSize=function(pane,size,dir){if(!dir){dir=_c[pane].dir}if(isStr(size)&&size.match(/%/)){size=(size==="100%")?-1:parseInt(size,10)/100}if(size===0){return 0}else{if(size>=1){return parseInt(size,10)}}var o=options,avail=0;if(dir=="horz"){avail=sC.innerHeight-($Ps.north?o.north.spacing_open:0)-($Ps.south?o.south.spacing_open:0)}else{if(dir=="vert"){avail=sC.innerWidth-($Ps.west?o.west.spacing_open:0)-($Ps.east?o.east.spacing_open:0)}}if(size===-1){return avail}else{if(size>0){return round(avail*size)}else{if(pane=="center"){return 0}else{var dim=(dir==="horz"?"height":"width"),$P=$Ps[pane],$C=dim==="height"?$Cs[pane]:false,vis=$.layout.showInvisibly($P),szP=$P.css(dim),szC=$C?$C.css(dim):0;$P.css(dim,"auto");if($C){$C.css(dim,"auto")}size=(dim==="height")?$P.outerHeight():$P.outerWidth();$P.css(dim,szP).css(vis);if($C){$C.css(dim,szC)}return size}}}},getPaneSize=function(pane,inclSpace){var $P=$Ps[pane],o=options[pane],s=state[pane],oSp=(inclSpace?o.spacing_open:0),cSp=(inclSpace?o.spacing_closed:0);if(!$P||s.isHidden){return 0}else{if(s.isClosed||(s.isSliding&&inclSpace)){return cSp}else{if(_c[pane].dir==="horz"){return $P.outerHeight()+oSp}else{return $P.outerWidth()+oSp}}}},setSizeLimits=function(pane,slide){if(!isInitialized()){return}var o=options[pane],s=state[pane],c=_c[pane],dir=c.dir,type=c.sizeType.toLowerCase(),isSliding=(slide!=undefined?slide:s.isSliding),$P=$Ps[pane],paneSpacing=o.spacing_open,altPane=_c.oppositeEdge[pane],altS=state[altPane],$altP=$Ps[altPane],altPaneSize=(!$altP||altS.isVisible===false||altS.isSliding?0:(dir=="horz"?$altP.outerHeight():$altP.outerWidth())),altPaneSpacing=((!$altP||altS.isHidden?0:options[altPane][altS.isClosed!==false?"spacing_closed":"spacing_open"])||0),containerSize=(dir=="horz"?sC.innerHeight:sC.innerWidth),minCenterDims=cssMinDims("center"),minCenterSize=dir=="horz"?max(options.center.minHeight,minCenterDims.minHeight):max(options.center.minWidth,minCenterDims.minWidth),limitSize=(containerSize-paneSpacing-(isSliding?0:(_parseSize("center",minCenterSize,dir)+altPaneSize+altPaneSpacing))),minSize=s.minSize=max(_parseSize(pane,o.minSize),cssMinDims(pane).minSize),maxSize=s.maxSize=min((o.maxSize?_parseSize(pane,o.maxSize):100000),limitSize),r=s.resizerPosition={},top=sC.inset.top,left=sC.inset.left,W=sC.innerWidth,H=sC.innerHeight,rW=o.spacing_open;switch(pane){case"north":r.min=top+minSize;r.max=top+maxSize;break;case"west":r.min=left+minSize;r.max=left+maxSize;break;case"south":r.min=top+H-maxSize-rW;r.max=top+H-minSize-rW;break;case"east":r.min=left+W-maxSize-rW;r.max=left+W-minSize-rW;break}},calcNewCenterPaneDims=function(){var d={top:getPaneSize("north",true),bottom:getPaneSize("south",true),left:getPaneSize("west",true),right:getPaneSize("east",true),width:0,height:0};d.width=sC.innerWidth-d.left-d.right;d.height=sC.innerHeight-d.bottom-d.top;d.top+=sC.inset.top;d.bottom+=sC.inset.bottom;d.left+=sC.inset.left;d.right+=sC.inset.right;return d},getHoverClasses=function(el,allStates){var $El=$(el),type=$El.data("layoutRole"),pane=$El.data("layoutEdge"),o=options[pane],root=o[type+"Class"],_pane="-"+pane,_open="-open",_closed="-closed",_slide="-sliding",_hover="-hover ",_state=$El.hasClass(root+_closed)?_closed:_open,_alt=_state===_closed?_open:_closed,classes=(root+_hover)+(root+_pane+_hover)+(root+_state+_hover)+(root+_pane+_state+_hover);if(allStates){classes+=(root+_alt+_hover)+(root+_pane+_alt+_hover)}if(type=="resizer"&&$El.hasClass(root+_slide)){classes+=(root+_slide+_hover)+(root+_pane+_slide+_hover)}return $.trim(classes)},addHover=function(evt,el){var $E=$(el||this);if(evt&&$E.data("layoutRole")==="toggler"){evt.stopPropagation()}$E.addClass(getHoverClasses($E))},removeHover=function(evt,el){var $E=$(el||this);$E.removeClass(getHoverClasses($E,true))},onResizerEnter=function(evt){var pane=$(this).data("layoutEdge"),s=state[pane],$d=$(document);if(s.isResizing||state.paneResizing){return}if(options.maskPanesEarly){showMasks(pane,{resizing:true})}},onResizerLeave=function(evt,el){var e=el||this,pane=$(e).data("layoutEdge"),name=pane+"ResizerLeave",$d=$(document);timer.clear(pane+"_openSlider");timer.clear(name);if(!el){timer.set(name,function(){onResizerLeave(evt,e)},200)}else{if(options.maskPanesEarly&&!state.paneResizing){hideMasks()}}},_create=function(){initOptions();var o=options,s=state;s.creatingLayout=true;runPluginCallbacks(Instance,$.layout.onCreate);if(false===_runCallbacks("onload_start")){return"cancel"}_initContainer();initHotkeys();$(window).bind("unload."+sID,unload);runPluginCallbacks(Instance,$.layout.onLoad);if(o.initPanes){_initLayoutElements()}delete s.creatingLayout;return state.initialized},isInitialized=function(){if(state.initialized||state.creatingLayout){return true}else{return _initLayoutElements()}},_initLayoutElements=function(retry){var o=options;if(!$N.is(":visible")){if(!retry&&browser.webkit&&$N[0].tagName==="BODY"){setTimeout(function(){_initLayoutElements(true)},50)}return false}if(!getPane("center").length){return _log(o.errors.centerPaneMissing)}state.creatingLayout=true;$.extend(sC,elDims($N,o.inset));initPanes();if(o.scrollToBookmarkOnLoad){var l=self.location;if(l.hash){l.replace(l.hash)}}if(Instance.hasParentLayout){o.resizeWithWindow=false}else{if(o.resizeWithWindow){$(window).bind("resize."+sID,windowResize)}}delete state.creatingLayout;state.initialized=true;runPluginCallbacks(Instance,$.layout.onReady);_runCallbacks("onload_end");return true},createChildren=function(evt_or_pane,opts){var pane=evtPane.call(this,evt_or_pane),$P=$Ps[pane];if(!$P){return}var $C=$Cs[pane],s=state[pane],o=options[pane],sm=options.stateManagement||{},cos=opts?(o.children=opts):o.children;if($.isPlainObject(cos)){cos=[cos]}else{if(!cos||!$.isArray(cos)){return}}$.each(cos,function(idx,co){if(!$.isPlainObject(co)){return}var $containers=co.containerSelector?$P.find(co.containerSelector):($C||$P);$containers.each(function(){var $cont=$(this),child=$cont.data("layout");if(!child){setInstanceKey({container:$cont,options:co},s);if(sm.includeChildren&&state.stateData[pane]){var paneChildren=state.stateData[pane].children||{},childState=paneChildren[co.instanceKey],co_sm=co.stateManagement||(co.stateManagement={autoLoad:true});if(co_sm.autoLoad===true&&childState){co_sm.autoSave=false;co_sm.includeChildren=true;co_sm.autoLoad=$.extend(true,{},childState)}}child=$cont.layout(co);if(child){refreshChildren(pane,child)}}})})},setInstanceKey=function(child,parentPaneState){var $c=child.container,o=child.options,sm=o.stateManagement,key=o.instanceKey||$c.data("layoutInstanceKey");if(!key){key=(sm&&sm.cookie?sm.cookie.name:"")||o.name}if(!key){key="layout"+(++parentPaneState.childIdx)}else{key=key.replace(/[^\w-]/gi,"_").replace(/_{2,}/g,"_")}o.instanceKey=key;$c.data("layoutInstanceKey",key);return key},refreshChildren=function(pane,newChild){var $P=$Ps[pane],pC=children[pane],s=state[pane],o;if($.isPlainObject(pC)){$.each(pC,function(key,child){if(child.destroyed){delete pC[key]}});if($.isEmptyObject(pC)){pC=children[pane]=null}}if(!newChild&&!pC){newChild=$P.data("layout")}if(newChild){newChild.hasParentLayout=true;o=newChild.options;setInstanceKey(newChild,s);if(!pC){pC=children[pane]={}}pC[o.instanceKey]=newChild.container.data("layout")}Instance[pane].children=children[pane];if(!newChild){createChildren(pane)}},windowResize=function(){var o=options,delay=Number(o.resizeWithWindowDelay);if(delay<10){delay=100}timer.clear("winResize");timer.set("winResize",function(){timer.clear("winResize");timer.clear("winResizeRepeater");var dims=elDims($N,o.inset);if(dims.innerWidth!==sC.innerWidth||dims.innerHeight!==sC.innerHeight){resizeAll()}},delay);if(!timer.data["winResizeRepeater"]){setWindowResizeRepeater()}},setWindowResizeRepeater=function(){var delay=Number(options.resizeWithWindowMaxDelay);if(delay>0){timer.set("winResizeRepeater",function(){setWindowResizeRepeater();resizeAll()},delay)}},unload=function(){var o=options;_runCallbacks("onunload_start");runPluginCallbacks(Instance,$.layout.onUnload);_runCallbacks("onunload_end")},_initContainer=function(){var N=$N[0],$H=$("html"),tag=sC.tagName=N.tagName,id=sC.id=N.id,cls=sC.className=N.className,o=options,name=o.name,props="position,margin,padding,border",css="layoutCSS",CSS={},hid="hidden",parent=$N.data("parentLayout"),pane=$N.data("layoutEdge"),isChild=parent&&pane,num=$.layout.cssNum,$parent,n;if($N.selector!==undefined){alert($N.selector);sC.selector=$N.selector.split(".slice")[0]}sC.ref=(o.name?o.name+" layout / ":"")+tag+(id?"#"+id:cls?".["+cls+"]":"");sC.isBody=(tag==="BODY");if(!isChild&&!sC.isBody){$parent=$N.closest("."+$.layout.defaults.panes.paneClass);parent=$parent.data("parentLayout");pane=$parent.data("layoutEdge");isChild=parent&&pane}$N.data({layout:Instance,layoutContainer:sID}).addClass(o.containerClass);var layoutMethods={destroy:"",initPanes:"",resizeAll:"resizeAll",resize:"resizeAll"};for(name in layoutMethods){$N.bind("layout"+name.toLowerCase()+"."+sID,Instance[layoutMethods[name]||name])}if(isChild){Instance.hasParentLayout=true;parent.refreshChildren(pane,Instance)}if(!$N.data(css)){if(sC.isBody){$N.data(css,$.extend(styles($N,props),{height:$N.css("height"),overflow:$N.css("overflow"),overflowX:$N.css("overflowX"),overflowY:$N.css("overflowY")}));$H.data(css,$.extend(styles($H,"padding"),{height:"auto",overflow:$H.css("overflow"),overflowX:$H.css("overflowX"),overflowY:$H.css("overflowY")}))}else{$N.data(css,styles($N,props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY"))}}try{CSS={overflow:hid,overflowX:hid,overflowY:hid};$N.css(CSS);if(o.inset&&!$.isPlainObject(o.inset)){n=parseInt(o.inset,10)||0;o.inset={top:n,bottom:n,left:n,right:n}}if(sC.isBody){if(!o.outset){o.outset={top:num($H,"paddingTop"),bottom:num($H,"paddingBottom"),left:num($H,"paddingLeft"),right:num($H,"paddingRight")}}else{if(!$.isPlainObject(o.outset)){n=parseInt(o.outset,10)||0;o.outset={top:n,bottom:n,left:n,right:n}}}$H.css(CSS).css({height:"100%",border:"none",padding:0,margin:0});if(browser.isIE6){$N.css({width:"100%",height:"100%",border:"none",padding:0,margin:0,position:"relative"});if(!o.inset){o.inset=elDims($N).inset}}else{$N.css({width:"auto",height:"auto",margin:0,position:"absolute"});$N.css(o.outset)}$.extend(sC,elDims($N,o.inset))}else{var p=$N.css("position");if(!p||!p.match(/(fixed|absolute|relative)/)){$N.css("position","relative")}if($N.is(":visible")){$.extend(sC,elDims($N,o.inset));if(sC.innerHeight<1){_log(o.errors.noContainerHeight.replace(/CONTAINER/,sC.ref))}}}if(num($N,"minWidth")){$N.parent().css("overflowX","auto")}if(num($N,"minHeight")){$N.parent().css("overflowY","auto")}}catch(ex){}},initHotkeys=function(panes){panes=panes?panes.split(","):_c.borderPanes;$.each(panes,function(i,pane){var o=options[pane];if(o.enableCursorHotkey||o.customHotkey){$(document).bind("keydown."+sID,keyDown);return false}})},initOptions=function(){var data,d,pane,key,val,i,c,o;opts=$.layout.transformData(opts,true);opts=$.layout.backwardCompatibility.renameAllOptions(opts);if(!$.isEmptyObject(opts.panes)){data=$.layout.optionsMap.noDefault;for(i=0,c=data.length;i0){z.pane_normal=zo;z.content_mask=max(zo+1,z.content_mask);z.resizer_normal=max(zo+2,z.resizer_normal)}delete options.panes;function createFxOptions(pane){var o=options[pane],d=options.panes;if(!o.fxSettings){o.fxSettings={}}if(!d.fxSettings){d.fxSettings={}}$.each(["_open","_close","_size"],function(i,n){var sName="fxName"+n,sSpeed="fxSpeed"+n,sSettings="fxSettings"+n,fxName=o[sName]=o[sName]||d[sName]||o.fxName||d.fxName||"none",fxExists=$.effects&&($.effects[fxName]||($.effects.effect&&$.effects.effect[fxName]));if(fxName==="none"||!options.effects[fxName]||!fxExists){fxName=o[sName]="none"}var fx=options.effects[fxName]||{},fx_all=fx.all||null,fx_pane=fx[pane]||null;o[sSpeed]=o[sSpeed]||d[sSpeed]||o.fxSpeed||d.fxSpeed||null;o[sSettings]=$.extend(true,{},fx_all,fx_pane,d.fxSettings,o.fxSettings,d[sSettings],o[sSettings])});delete o.fxName;delete o.fxSpeed;delete o.fxSettings}},getPane=function(pane){var sel=options[pane].paneSelector;if(sel.substr(0,1)==="#"){return $N.find(sel).eq(0)}else{var $P=$N.children(sel).eq(0);return $P.length?$P:$N.children("form:first").children(sel).eq(0)}},initPanes=function(evt){evtPane(evt);$.each(_c.allPanes,function(idx,pane){addPane(pane,true)});initHandles();$.each(_c.borderPanes,function(i,pane){if($Ps[pane]&&state[pane].isVisible){setSizeLimits(pane);makePaneFit(pane)}});sizeMidPanes("center");$.each(_c.allPanes,function(idx,pane){afterInitPane(pane)})},addPane=function(pane,force){if(!force&&!isInitialized()){return}var o=options[pane],s=state[pane],c=_c[pane],dir=c.dir,fx=s.fx,spacing=o.spacing_open||0,isCenter=(pane==="center"),CSS={},$P=$Ps[pane],size,minSize,maxSize,child;if($P){removePane(pane,false,true,false)}else{$Cs[pane]=false}$P=$Ps[pane]=getPane(pane);if(!$P.length){$Ps[pane]=false;return}if(!$P.data("layoutCSS")){var props="position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";$P.data("layoutCSS",styles($P,props))}Instance[pane]={name:pane,pane:$Ps[pane],content:$Cs[pane],options:options[pane],state:state[pane],children:children[pane]};$P.data({parentLayout:Instance,layoutPane:Instance[pane],layoutEdge:pane,layoutRole:"pane"}).css(c.cssReq).css("zIndex",options.zIndexes.pane_normal).css(o.applyDemoStyles?c.cssDemo:{}).addClass(o.paneClass+" "+o.paneClass+"-"+pane).bind("mouseenter."+sID,addHover).bind("mouseleave."+sID,removeHover);var paneMethods={hide:"",show:"",toggle:"",close:"",open:"",slideOpen:"",slideClose:"",slideToggle:"",size:"sizePane",sizePane:"sizePane",sizeContent:"",sizeHandles:"",enableClosable:"",disableClosable:"",enableSlideable:"",disableSlideable:"",enableResizable:"",disableResizable:"",swapPanes:"swapPanes",swap:"swapPanes",move:"swapPanes",removePane:"removePane",remove:"removePane",createChildren:"",resizeChildren:"",resizeAll:"resizeAll",resizeLayout:"resizeAll"},name;for(name in paneMethods){$P.bind("layoutpane"+name.toLowerCase()+"."+sID,Instance[paneMethods[name]||name])}initContent(pane,false);if(!isCenter){size=s.size=_parseSize(pane,o.size);minSize=_parseSize(pane,o.minSize)||1;maxSize=_parseSize(pane,o.maxSize)||100000;if(size>0){size=max(min(size,maxSize),minSize)}s.autoResize=o.autoResize;s.isClosed=false;s.isSliding=false;s.isResizing=false;s.isHidden=false;if(!s.pins){s.pins=[]}}s.tagName=$P[0].tagName;s.edge=pane;s.noRoom=false;s.isVisible=true;setPanePosition(pane);if(dir==="horz"){CSS.height=cssH($P,size)}else{if(dir==="vert"){CSS.width=cssW($P,size)}}$P.css(CSS);if(dir!="horz"){sizeMidPanes(pane,true)}if(state.initialized){initHandles(pane);initHotkeys(pane)}if(o.initClosed&&o.closable&&!o.initHidden){close(pane,true,true)}else{if(o.initHidden||o.initClosed){hide(pane)}else{if(!s.noRoom){$P.css("display","block")}}}$P.css("visibility","visible");if(o.showOverflowOnHover){$P.hover(allowOverflow,resetOverflow)}if(state.initialized){afterInitPane(pane)}},afterInitPane=function(pane){var $P=$Ps[pane],s=state[pane],o=options[pane];if(!$P){return}if($P.data("layout")){refreshChildren(pane,$P.data("layout"))}if(s.isVisible){if(state.initialized){resizeAll()}else{sizeContent(pane)}if(o.triggerEventsOnLoad){_runCallbacks("onresize_end",pane)}else{resizeChildren(pane,true)}}if(o.initChildren&&o.children){createChildren(pane)}},setPanePosition=function(panes){panes=panes?panes.split(","):_c.borderPanes;$.each(panes,function(i,pane){var $P=$Ps[pane],$R=$Rs[pane],o=options[pane],s=state[pane],side=_c[pane].side,CSS={};if(!$P){return}switch(pane){case"north":CSS.top=sC.inset.top;CSS.left=sC.inset.left;CSS.right=sC.inset.right;break;case"south":CSS.bottom=sC.inset.bottom;CSS.left=sC.inset.left;CSS.right=sC.inset.right;break;case"west":CSS.left=sC.inset.left;break;case"east":CSS.right=sC.inset.right;break;case"center":}$P.css(CSS);if($R&&s.isClosed){$R.css(side,sC.inset[side])}else{if($R&&!s.isHidden){$R.css(side,sC.inset[side]+getPaneSize(pane))}}})},initHandles=function(panes){panes=panes?panes.split(","):_c.borderPanes;$.each(panes,function(i,pane){var $P=$Ps[pane];$Rs[pane]=false;$Ts[pane]=false;if(!$P){return}var o=options[pane],s=state[pane],c=_c[pane],paneId=o.paneSelector.substr(0,1)==="#"?o.paneSelector.substr(1):"",rClass=o.resizerClass,tClass=o.togglerClass,spacing=(s.isVisible?o.spacing_open:o.spacing_closed),_pane="-"+pane,_state=(s.isVisible?"-open":"-closed"),I=Instance[pane],$R=I.resizer=$Rs[pane]=$("
      "),$T=I.toggler=(o.closable?$Ts[pane]=$("
      "):false);if(!s.isVisible&&o.slidable){$R.attr("title",o.tips.Slide).css("cursor",o.sliderCursor)}$R.attr("id",paneId?paneId+"-resizer":"").data({parentLayout:Instance,layoutPane:Instance[pane],layoutEdge:pane,layoutRole:"resizer"}).css(_c.resizers.cssReq).css("zIndex",options.zIndexes.resizer_normal).css(o.applyDemoStyles?_c.resizers.cssDemo:{}).addClass(rClass+" "+rClass+_pane).hover(addHover,removeHover).hover(onResizerEnter,onResizerLeave).mousedown($.layout.disableTextSelection).mouseup($.layout.enableTextSelection).appendTo($N);if($.fn.disableSelection){$R.disableSelection()}if(o.resizerDblClickToggle){$R.bind("dblclick."+sID,toggle)}if($T){$T.attr("id",paneId?paneId+"-toggler":"").data({parentLayout:Instance,layoutPane:Instance[pane],layoutEdge:pane,layoutRole:"toggler"}).css(_c.togglers.cssReq).css(o.applyDemoStyles?_c.togglers.cssDemo:{}).addClass(tClass+" "+tClass+_pane).hover(addHover,removeHover).bind("mouseenter",onResizerEnter).appendTo($R);if(o.togglerContent_open){$(""+o.togglerContent_open+"").data({layoutEdge:pane,layoutRole:"togglerContent"}).data("layoutRole","togglerContent").data("layoutEdge",pane).addClass("ui-content content-open").css("display","none").appendTo($T)}if(o.togglerContent_closed){$(""+o.togglerContent_closed+"").data({layoutEdge:pane,layoutRole:"togglerContent"}).addClass("ui-content content-closed").css("display","none").appendTo($T)}enableClosable(pane)}initResizable(pane);if(s.isVisible){setAsOpen(pane)}else{setAsClosed(pane);bindStartSlidingEvents(pane,true)}});sizeHandles()},initContent=function(pane,resize){if(!isInitialized()){return}var o=options[pane],sel=o.contentSelector,I=Instance[pane],$P=$Ps[pane],$C;if(sel){$C=I.content=$Cs[pane]=(o.findNestedContent)?$P.find(sel).eq(0):$P.children(sel).eq(0)}if($C&&$C.length){$C.data("layoutRole","content");if(!$C.data("layoutCSS")){$C.data("layoutCSS",styles($C,"height"))}$C.css(_c.content.cssReq);if(o.applyDemoStyles){$C.css(_c.content.cssDemo);$P.css(_c.content.cssDemoPane)}if($P.css("overflowX").match(/(scroll|auto)/)){$P.css("overflow","hidden")}state[pane].content={};if(resize!==false){sizeContent(pane)}}else{I.content=$Cs[pane]=false}},initResizable=function(panes){var draggingAvailable=$.layout.plugins.draggable,side;panes=panes?panes.split(","):_c.borderPanes;$.each(panes,function(idx,pane){var o=options[pane];if(!draggingAvailable||!$Ps[pane]||!o.resizable){o.resizable=false;return true}var s=state[pane],z=options.zIndexes,c=_c[pane],side=c.dir=="horz"?"top":"left",$P=$Ps[pane],$R=$Rs[pane],base=o.resizerClass,lastPos=0,r,live,resizerClass=base+"-drag",resizerPaneClass=base+"-"+pane+"-drag",helperClass=base+"-dragging",helperPaneClass=base+"-"+pane+"-dragging",helperLimitClass=base+"-dragging-limit",helperPaneLimitClass=base+"-"+pane+"-dragging-limit",helperClassesSet=false;if(!s.isClosed){$R.attr("title",o.tips.Resize).css("cursor",o.resizerCursor)}$R.draggable({containment:$N[0],axis:(c.dir=="horz"?"y":"x"),delay:0,distance:1,grid:o.resizingGrid,helper:"clone",opacity:o.resizerDragOpacity,addClasses:false,zIndex:z.resizer_drag,iframeFix:true,start:function(e,ui){o=options[pane];s=state[pane];live=o.livePaneResizing;if(false===_runCallbacks("ondrag_start",pane)){return false}s.isResizing=true;state.paneResizing=pane;timer.clear(pane+"_closeSlider");setSizeLimits(pane);r=s.resizerPosition;lastPos=ui.position[side];$R.addClass(resizerClass+" "+resizerPaneClass);helperClassesSet=false;showMasks(pane,{resizing:true})},drag:function(e,ui){if(!helperClassesSet){ui.helper.addClass(helperClass+" "+helperPaneClass).css({right:"auto",bottom:"auto"}).children().css("visibility","hidden");helperClassesSet=true;if(s.isSliding){$Ps[pane].css("zIndex",z.pane_sliding)}}var limit=0;if(ui.position[side]r.max){ui.position[side]=r.max;limit=1}}if(limit){ui.helper.addClass(helperLimitClass+" "+helperPaneLimitClass);window.defaultStatus=(limit>0&&pane.match(/(north|west)/))||(limit<0&&pane.match(/(south|east)/))?o.tips.maxSizeWarning:o.tips.minSizeWarning}else{ui.helper.removeClass(helperLimitClass+" "+helperPaneLimitClass);window.defaultStatus=""}if(live&&Math.abs(ui.position[side]-lastPos)>=o.liveResizingTolerance){lastPos=ui.position[side];resizePanes(e,ui,pane)}},stop:function(e,ui){$("body").enableSelection();window.defaultStatus="";$R.removeClass(resizerClass+" "+resizerPaneClass);s.isResizing=false;state.paneResizing=false;resizePanes(e,ui,pane,true)}})});var resizePanes=function(evt,ui,pane,resizingDone){var dragPos=ui.position,c=_c[pane],o=options[pane],s=state[pane],resizerPos;switch(pane){case"north":resizerPos=dragPos.top;break;case"west":resizerPos=dragPos.left;break;case"south":resizerPos=sC.layoutHeight-dragPos.top-o.spacing_open;break;case"east":resizerPos=sC.layoutWidth-dragPos.left-o.spacing_open;break}var newSize=resizerPos-sC.inset[c.side];if(!resizingDone){if(Math.abs(newSize-s.size)=0;i--){$M=$Ms.eq(i);p=$M.data("layoutMask");if(!options[p].maskObjects){$M.hide()}}}}},getMasks=function(pane){var $Masks=$([]),$M,i=0,c=$Ms.length;for(;is.maxSize){syncPinBtns(pane,false);if(!noAlert&&o.tips.noRoomToOpen){alert(o.tips.noRoomToOpen)}return queueNext()}if(slide){bindStopSlidingEvents(pane,true)}else{if(s.isSliding){bindStopSlidingEvents(pane,false)}else{if(o.slidable){bindStartSlidingEvents(pane,false)}}}s.noRoom=false;makePaneFit(pane);isShowing=s.isShowing;delete s.isShowing;doFX=!noAnimation&&s.isClosed&&(o.fxName_open!="none");s.isMoving=true;s.isVisible=true;s.isClosed=false;if(isShowing){s.isHidden=false}if(doFX){lockPaneForFX(pane,true);$P.show(o.fxName_open,o.fxSettings_open,o.fxSpeed_open,function(){lockPaneForFX(pane,false);if(s.isVisible){open_2()}queueNext()})}else{_showPane(pane);open_2();queueNext()}});function open_2(){s.isMoving=false;_fixIframe(pane);if(!s.isSliding){sizeMidPanes(_c[pane].dir=="vert"?"center":"",false)}setAsOpen(pane)}},setAsOpen=function(pane,skipCallback){var $P=$Ps[pane],$R=$Rs[pane],$T=$Ts[pane],o=options[pane],s=state[pane],side=_c[pane].side,rClass=o.resizerClass,tClass=o.togglerClass,_pane="-"+pane,_open="-open",_closed="-closed",_sliding="-sliding";$R.css(side,sC.inset[side]+getPaneSize(pane)).removeClass(rClass+_closed+" "+rClass+_pane+_closed).addClass(rClass+_open+" "+rClass+_pane+_open);if(s.isSliding){$R.addClass(rClass+_sliding+" "+rClass+_pane+_sliding)}else{$R.removeClass(rClass+_sliding+" "+rClass+_pane+_sliding)}removeHover(0,$R);if(o.resizable&&$.layout.plugins.draggable){$R.draggable("enable").css("cursor",o.resizerCursor).attr("title",o.tips.Resize)}else{if(!s.isSliding){$R.css("cursor","default")}}if($T){$T.removeClass(tClass+_closed+" "+tClass+_pane+_closed).addClass(tClass+_open+" "+tClass+_pane+_open).attr("title",o.tips.Close);removeHover(0,$T);$T.children(".content-closed").hide();$T.children(".content-open").css("display","block")}syncPinBtns(pane,!s.isSliding);$.extend(s,elDims($P));if(state.initialized){sizeHandles();sizeContent(pane,true)}if(!skipCallback&&(state.initialized||o.triggerEventsOnLoad)&&$P.is(":visible")){_runCallbacks("onopen_end",pane);if(s.isShowing){_runCallbacks("onshow_end",pane)}if(state.initialized){_runCallbacks("onresize_end",pane)}}},slideOpen=function(evt_or_pane){if(!isInitialized()){return}var evt=evtObj(evt_or_pane),pane=evtPane.call(this,evt_or_pane),s=state[pane],delay=options[pane].slideDelay_open;if(pane==="center"){return}if(evt){evt.stopImmediatePropagation()}if(s.isClosed&&evt&&evt.type==="mouseenter"&&delay>0){timer.set(pane+"_openSlider",open_NOW,delay)}else{open_NOW()}function open_NOW(){if(!s.isClosed){bindStopSlidingEvents(pane,true)}else{if(!s.isMoving){open(pane,true)}}}},slideClose=function(evt_or_pane){if(!isInitialized()){return}var evt=evtObj(evt_or_pane),pane=evtPane.call(this,evt_or_pane),o=options[pane],s=state[pane],delay=s.isMoving?1000:300;if(pane==="center"){return}if(s.isClosed||s.isResizing){return}else{if(o.slideTrigger_close==="click"){close_NOW()}else{if(o.preventQuickSlideClose&&s.isMoving){return}else{if(o.preventPrematureSlideClose&&evt&&$.layout.isMouseOverElem(evt,$Ps[pane])){return}else{if(evt){timer.set(pane+"_closeSlider",close_NOW,max(o.slideDelay_close,delay))}else{close_NOW()}}}}}function close_NOW(){if(s.isClosed){bindStopSlidingEvents(pane,false)}else{if(!s.isMoving){close(pane)}}}},slideToggle=function(evt_or_pane){var pane=evtPane.call(this,evt_or_pane);toggle(pane,true)},lockPaneForFX=function(pane,doLock){var $P=$Ps[pane],s=state[pane],o=options[pane],z=options.zIndexes;if(doLock){showMasks(pane,{animation:true,objectsOnly:true});$P.css({zIndex:z.pane_animate});if(pane=="south"){$P.css({top:sC.inset.top+sC.innerHeight-$P.outerHeight()})}else{if(pane=="east"){$P.css({left:sC.inset.left+sC.innerWidth-$P.outerWidth()})}}}else{hideMasks();$P.css({zIndex:(s.isSliding?z.pane_sliding:z.pane_normal)});if(pane=="south"){$P.css({top:"auto"})}else{if(pane=="east"&&!$P.css("left").match(/\-99999/)){$P.css({left:"auto"})}}if(browser.msie&&o.fxOpacityFix&&o.fxName_open!="slide"&&$P.css("filter")&&$P.css("opacity")==1){$P[0].style.removeAttribute("filter")}}},bindStartSlidingEvents=function(pane,enable){var o=options[pane],$P=$Ps[pane],$R=$Rs[pane],evtName=o.slideTrigger_open.toLowerCase();if(!$R||(enable&&!o.slidable)){return}if(evtName.match(/mouseover/)){evtName=o.slideTrigger_open="mouseenter"}else{if(!evtName.match(/(click|dblclick|mouseenter)/)){evtName=o.slideTrigger_open="click"}}if(o.resizerDblClickToggle&&evtName.match(/click/)){$R[enable?"unbind":"bind"]("dblclick."+sID,toggle)}$R[enable?"bind":"unbind"](evtName+"."+sID,slideOpen).css("cursor",enable?o.sliderCursor:"default").attr("title",enable?o.tips.Slide:"")},bindStopSlidingEvents=function(pane,enable){var o=options[pane],s=state[pane],c=_c[pane],z=options.zIndexes,evtName=o.slideTrigger_close.toLowerCase(),action=(enable?"bind":"unbind"),$P=$Ps[pane],$R=$Rs[pane];timer.clear(pane+"_closeSlider");if(enable){s.isSliding=true;state.panesSliding[pane]=true;bindStartSlidingEvents(pane,false)}else{s.isSliding=false;delete state.panesSliding[pane]}$P.css("zIndex",enable?z.pane_sliding:z.pane_normal);$R.css("zIndex",enable?z.pane_sliding+2:z.resizer_normal);if(!evtName.match(/(click|mouseleave)/)){evtName=o.slideTrigger_close="mouseleave"}$R[action](evtName,slideClose);if(evtName==="mouseleave"){$P[action]("mouseleave."+sID,slideClose);$R[action]("mouseenter."+sID,cancelMouseOut);$P[action]("mouseenter."+sID,cancelMouseOut)}if(!enable){timer.clear(pane+"_closeSlider")}else{if(evtName==="click"&&!o.resizable){$R.css("cursor",enable?o.sliderCursor:"default");$R.attr("title",enable?o.tips.Close:"")}}function cancelMouseOut(evt){timer.clear(pane+"_closeSlider");evt.stopPropagation()}},makePaneFit=function(pane,isOpening,skipCallback,force){var o=options[pane],s=state[pane],c=_c[pane],$P=$Ps[pane],$R=$Rs[pane],isSidePane=c.dir==="vert",hasRoom=false;if(pane==="center"||(isSidePane&&s.noVerticalRoom)){hasRoom=(s.maxHeight>=0);if(hasRoom&&s.noRoom){_showPane(pane);if($R){$R.show()}s.isVisible=true;s.noRoom=false;if(isSidePane){s.noVerticalRoom=false}_fixIframe(pane)}else{if(!hasRoom&&!s.noRoom){_hidePane(pane);if($R){$R.hide()}s.isVisible=false;s.noRoom=true}}}if(pane==="center"){}else{if(s.minSize<=s.maxSize){hasRoom=true;if(s.size>s.maxSize){sizePane(pane,s.maxSize,skipCallback,true,force)}else{if(s.sizesize){thisTry.attempt=max(0,lastTry.attempt-(lastTry.actual-size))}else{thisTry.attempt=max(0,lastTry.attempt+(size-lastTry.actual))}thisTry.cssSize=cssSize(pane,thisTry.attempt);$P.css(dimName,thisTry.cssSize);thisTry.actual=dimName=="width"?$P.outerWidth():$P.outerHeight();thisTry.correct=(size===thisTry.actual);if(tries.length===1){_log(msg,false,true);_log(lastTry,false,true)}_log(thisTry,false,true);if(tries.length>3){break}tries.push(thisTry);lastTry=tries[tries.length-1]}s.size=size;$.extend(s,elDims($P));if(s.isVisible&&$P.is(":visible")){if($R){$R.css(side,size+sC.inset[side])}sizeContent(pane)}if(!skipCallback&&!skipResizeWhileDragging&&state.initialized&&s.isVisible){_runCallbacks("onresize_end",pane)}if(!skipCallback){if(!s.isSliding){sizeMidPanes(_c[pane].dir=="horz"?"":"center",skipResizeWhileDragging,force)}sizeHandles()}var altPane=_c.oppositeEdge[pane];if(size1){_log(msg+"\nSee the Error Console for details.",true,true)}}},sizeMidPanes=function(panes,skipCallback,force){panes=(panes?panes:"east,west,center").split(",");$.each(panes,function(i,pane){if(!$Ps[pane]){return}var o=options[pane],s=state[pane],$P=$Ps[pane],$R=$Rs[pane],isCenter=(pane=="center"),hasRoom=true,CSS={},visCSS=$.layout.showInvisibly($P),newCenter=calcNewCenterPaneDims();$.extend(s,elDims($P));if(pane==="center"){if(!force&&s.isVisible&&newCenter.width===s.outerWidth&&newCenter.height===s.outerHeight){$P.css(visCSS);return true}$.extend(s,cssMinDims(pane),{maxWidth:newCenter.width,maxHeight:newCenter.height});CSS=newCenter;s.newWidth=CSS.width;s.newHeight=CSS.height;CSS.width=cssW($P,CSS.width);CSS.height=cssH($P,CSS.height);hasRoom=CSS.width>=0&&CSS.height>=0;if(!state.initialized&&o.minWidth>newCenter.width){var reqPx=o.minWidth-s.outerWidth,minE=options.east.minSize||0,minW=options.west.minSize||0,sizeE=state.east.size,sizeW=state.west.size,newE=sizeE,newW=sizeW;if(reqPx>0&&state.east.isVisible&&sizeE>minE){newE=max(sizeE-minE,sizeE-reqPx);reqPx-=sizeE-newE}if(reqPx>0&&state.west.isVisible&&sizeW>minW){newW=max(sizeW-minW,sizeW-reqPx);reqPx-=sizeW-newW}if(reqPx===0){if(sizeE&&sizeE!=minE){sizePane("east",newE,true,true,force)}if(sizeW&&sizeW!=minW){sizePane("west",newW,true,true,force)}sizeMidPanes("center",skipCallback,force);$P.css(visCSS);return}}}else{if(s.isVisible&&!s.noVerticalRoom){$.extend(s,elDims($P),cssMinDims(pane))}if(!force&&!s.noVerticalRoom&&newCenter.height===s.outerHeight){$P.css(visCSS);return true}CSS.top=newCenter.top;CSS.bottom=newCenter.bottom;s.newSize=newCenter.height;CSS.height=cssH($P,newCenter.height);s.maxHeight=CSS.height;hasRoom=(s.maxHeight>=0);if(!hasRoom){s.noVerticalRoom=true}}if(hasRoom){if(!skipCallback&&state.initialized){_runCallbacks("onresize_start",pane)}$P.css(CSS);if(pane!=="center"){sizeHandles(pane)}if(s.noRoom&&!s.isClosed&&!s.isHidden){makePaneFit(pane)}if(s.isVisible){$.extend(s,elDims($P));if(state.initialized){sizeContent(pane)}}}else{if(!s.noRoom&&s.isVisible){makePaneFit(pane)}}$P.css(visCSS);delete s.newSize;delete s.newWidth;delete s.newHeight;if(!s.isVisible){return true}if(pane==="center"){var fix=browser.isIE6||!browser.boxModel;if($Ps.north&&(fix||state.north.tagName=="IFRAME")){$Ps.north.css("width",cssW($Ps.north,sC.innerWidth))}if($Ps.south&&(fix||state.south.tagName=="IFRAME")){$Ps.south.css("width",cssW($Ps.south,sC.innerWidth))}}if(!skipCallback&&state.initialized){_runCallbacks("onresize_end",pane)}})},resizeAll=function(evt_or_refresh){var oldW=sC.innerWidth,oldH=sC.innerHeight;evtPane(evt_or_refresh);if(!$N.is(":visible")){return}if(!state.initialized){_initLayoutElements();return}if(evt_or_refresh===true&&$.isPlainObject(options.outset)){$N.css(options.outset)}$.extend(sC,elDims($N,options.inset));if(!sC.outerHeight){return}if(evt_or_refresh===true){setPanePosition()}if(false===_runCallbacks("onresizeall_start")){return false}var shrunkH=(sC.innerHeight0&&$P.css("overflow")==="hidden"){$P.css("overflow","visible");_measure();$P.css("overflow","hidden")}}var newH=s.innerHeight-(m.spaceAbove-s.css.paddingTop)-(m.spaceBelow-s.css.paddingBottom);if(!$C.is(":visible")||m.height!=newH){setOuterHeight($C,newH,true);m.height=newH}if(state.initialized){_runCallbacks("onsizecontent_end",pane)}function _below($E){return max(s.css.paddingBottom,(parseInt($E.css("marginBottom"),10)||0))}function _measure(){var ignore=options[pane].contentIgnoreSelector,$Fs=$C.nextAll().not(".ui-layout-mask").not(ignore||":lt(0)"),$Fs_vis=$Fs.filter(":visible"),$F=$Fs_vis.filter(":last");m={top:$C[0].offsetTop,height:$C.outerHeight(),numFooters:$Fs.length,hiddenFooters:$Fs.length-$Fs_vis.length,spaceBelow:0};m.spaceAbove=m.top;m.bottom=m.top+m.height;if($F.length){m.spaceBelow=($F[0].offsetTop+$F.outerHeight())-m.bottom+_below($F)}else{m.spaceBelow=_below($C)}}})},sizeHandles=function(evt_or_panes){var panes=evtPane.call(this,evt_or_panes);panes=panes?panes.split(","):_c.borderPanes;$.each(panes,function(i,pane){var o=options[pane],s=state[pane],$P=$Ps[pane],$R=$Rs[pane],$T=$Ts[pane],$TC;if(!$P||!$R){return}var dir=_c[pane].dir,_state=(s.isClosed?"_closed":"_open"),spacing=o["spacing"+_state],togAlign=o["togglerAlign"+_state],togLen=o["togglerLength"+_state],paneLen,left,offset,CSS={};if(spacing===0){$R.hide();return}else{if(!s.noRoom&&!s.isHidden){$R.show()}}if(dir==="horz"){paneLen=sC.innerWidth;s.resizerLength=paneLen;left=$.layout.cssNum($P,"left");$R.css({width:cssW($R,paneLen),height:cssH($R,spacing),left:left>-9999?left:sC.inset.left})}else{paneLen=$P.outerHeight();s.resizerLength=paneLen;$R.css({height:cssH($R,paneLen),width:cssW($R,spacing),top:sC.inset.top+getPaneSize("north",true)})}removeHover(o,$R);if($T){if(togLen===0||(s.isSliding&&o.hideTogglerOnSlide)){$T.hide();return}else{$T.show()}if(!(togLen>0)||togLen==="100%"||togLen>paneLen){togLen=paneLen;offset=0}else{if(isStr(togAlign)){switch(togAlign){case"top":case"left":offset=0;break;case"bottom":case"right":offset=paneLen-togLen;break;case"middle":case"center":default:offset=round((paneLen-togLen)/2)}}else{var x=parseInt(togAlign,10);if(togAlign>=0){offset=x}else{offset=paneLen-togLen+x}}}if(dir==="horz"){var width=cssW($T,togLen);$T.css({width:width,height:cssH($T,spacing),left:offset,top:0})}else{var height=cssH($T,togLen);$T.css({height:height,width:cssW($T,spacing),top:offset,left:0});$T.children(".ui-content").each(function(){$TC=$(this);$TC.css("marginTop",round((height-$TC.outerHeight())/2))})}removeHover(0,$T)}if(!state.initialized&&(o.initHidden||s.isHidden)){$R.hide();if($T){$T.hide()}}})},enableClosable=function(evt_or_pane){if(!isInitialized()){return}var pane=evtPane.call(this,evt_or_pane),$T=$Ts[pane],o=options[pane];if(!$T){return}o.closable=true;$T.bind("click."+sID,function(evt){evt.stopPropagation();toggle(pane)}).css("visibility","visible").css("cursor","pointer").attr("title",state[pane].isClosed?o.tips.Open:o.tips.Close).show()},disableClosable=function(evt_or_pane,hide){if(!isInitialized()){return}var pane=evtPane.call(this,evt_or_pane),$T=$Ts[pane];if(!$T){return}options[pane].closable=false;if(state[pane].isClosed){open(pane,false,true)}$T.unbind("."+sID).css("visibility",hide?"hidden":"visible").css("cursor","default").attr("title","")},enableSlidable=function(evt_or_pane){if(!isInitialized()){return}var pane=evtPane.call(this,evt_or_pane),$R=$Rs[pane];if(!$R||!$R.data("draggable")){return}options[pane].slidable=true;if(state[pane].isClosed){bindStartSlidingEvents(pane,true)}},disableSlidable=function(evt_or_pane){if(!isInitialized()){return}var pane=evtPane.call(this,evt_or_pane),$R=$Rs[pane];if(!$R){return}options[pane].slidable=false;if(state[pane].isSliding){close(pane,false,true)}else{bindStartSlidingEvents(pane,false);$R.css("cursor","default").attr("title","");removeHover(null,$R[0])}},enableResizable=function(evt_or_pane){if(!isInitialized()){return}var pane=evtPane.call(this,evt_or_pane),$R=$Rs[pane],o=options[pane];if(!$R||!$R.data("draggable")){return}o.resizable=true;$R.draggable("enable");if(!state[pane].isClosed){$R.css("cursor",o.resizerCursor).attr("title",o.tips.Resize)}},disableResizable=function(evt_or_pane){if(!isInitialized()){return}var pane=evtPane.call(this,evt_or_pane),$R=$Rs[pane];if(!$R||!$R.data("draggable")){return}options[pane].resizable=false;$R.draggable("disable").css("cursor","default").attr("title","");removeHover(null,$R[0])},swapPanes=function(evt_or_pane1,pane2){if(!isInitialized()){return}var pane1=evtPane.call(this,evt_or_pane1);state[pane1].edge=pane2;state[pane2].edge=pane1;if(false===_runCallbacks("onswap_start",pane1)||false===_runCallbacks("onswap_start",pane2)){state[pane1].edge=pane1;state[pane2].edge=pane2;return}var oPane1=copy(pane1),oPane2=copy(pane2),sizes={};sizes[pane1]=oPane1?oPane1.state.size:0;sizes[pane2]=oPane2?oPane2.state.size:0;$Ps[pane1]=false;$Ps[pane2]=false;state[pane1]={};state[pane2]={};if($Ts[pane1]){$Ts[pane1].remove()}if($Ts[pane2]){$Ts[pane2].remove()}if($Rs[pane1]){$Rs[pane1].remove()}if($Rs[pane2]){$Rs[pane2].remove()}$Rs[pane1]=$Rs[pane2]=$Ts[pane1]=$Ts[pane2]=false;move(oPane1,pane2);move(oPane2,pane1);oPane1=oPane2=sizes=null;if($Ps[pane1]){$Ps[pane1].css(_c.visible)}if($Ps[pane2]){$Ps[pane2].css(_c.visible)}resizeAll();_runCallbacks("onswap_end",pane1);_runCallbacks("onswap_end",pane2);return;function copy(n){var $P=$Ps[n],$C=$Cs[n];return !$P?false:{pane:n,P:$P?$P[0]:false,C:$C?$C[0]:false,state:$.extend(true,{},state[n]),options:$.extend(true,{},options[n])}}function move(oPane,pane){if(!oPane){return}var P=oPane.P,C=oPane.C,oldPane=oPane.pane,c=_c[pane],s=$.extend(true,{},state[pane]),o=options[pane],fx={resizerCursor:o.resizerCursor},re,size,pos;$.each("fxName,fxSpeed,fxSettings".split(","),function(i,k){fx[k+"_open"]=o[k+"_open"];fx[k+"_close"]=o[k+"_close"];fx[k+"_size"]=o[k+"_size"]});$Ps[pane]=$(P).data({layoutPane:Instance[pane],layoutEdge:pane}).css(_c.hidden).css(c.cssReq);$Cs[pane]=C?$(C):false;options[pane]=$.extend(true,{},oPane.options,fx);state[pane]=$.extend(true,{},oPane.state);re=new RegExp(o.paneClass+"-"+oldPane,"g");P.className=P.className.replace(re,o.paneClass+"-"+pane);initHandles(pane);if(c.dir!=_c[oldPane].dir){size=sizes[pane]||0;setSizeLimits(pane);size=max(size,state[pane].minSize);manualSizePane(pane,size,true,true)}else{$Rs[pane].css(c.side,sC.inset[c.side]+(state[pane].isVisible?getPaneSize(pane):0))}if(oPane.state.isVisible&&!s.isVisible){setAsOpen(pane,true)}else{setAsClosed(pane);bindStartSlidingEvents(pane,true)}oPane=null}},syncPinBtns=function(pane,doPin){if($.layout.plugins.buttons){$.each(state[pane].pins,function(i,selector){$.layout.buttons.setPinState(Instance,$(selector),pane,doPin)})}};function keyDown(evt){if(!evt){return true}var code=evt.keyCode;if(code<33){return true}var PANE={38:"north",40:"south",37:"west",39:"east"},ALT=evt.altKey,SHIFT=evt.shiftKey,CTRL=evt.ctrlKey,CURSOR=(CTRL&&code>=37&&code<=40),o,k,m,pane;if(CURSOR&&options[PANE[code]].enableCursorHotkey){pane=PANE[code]}else{if(CTRL||SHIFT){$.each(_c.borderPanes,function(i,p){o=options[p];k=o.customHotkey;m=o.customHotkeyModifier;if((SHIFT&&m=="SHIFT")||(CTRL&&m=="CTRL")||(CTRL&&SHIFT)){if(k&&code===(isNaN(k)||k<=9?k.toUpperCase().charCodeAt(0):k)){pane=p;return false}}})}}if(!pane||!$Ps[pane]||!options[pane].closable||state[pane].isHidden){return true}toggle(pane);evt.stopPropagation();evt.returnValue=false;return false}function allowOverflow(el){if(!isInitialized()){return}if(this&&this.tagName){el=this}var $P;if(isStr(el)){$P=$Ps[el]}else{if($(el).data("layoutRole")){$P=$(el)}else{$(el).parents().each(function(){if($(this).data("layoutRole")){$P=$(this);return false}})}}if(!$P||!$P.length){return}var pane=$P.data("layoutEdge"),s=state[pane];if(s.cssSaved){resetOverflow(pane)}if(s.isSliding||s.isResizing||s.isClosed){s.cssSaved=false;return}var newCSS={zIndex:(options.zIndexes.resizer_normal+1)},curCSS={},of=$P.css("overflow"),ofX=$P.css("overflowX"),ofY=$P.css("overflowY");if(of!="visible"){curCSS.overflow=of;newCSS.overflow="visible"}if(ofX&&!ofX.match(/(visible|auto)/)){curCSS.overflowX=ofX;newCSS.overflowX="visible"}if(ofY&&!ofY.match(/(visible|auto)/)){curCSS.overflowY=ofX;newCSS.overflowY="visible"}s.cssSaved=curCSS;$P.css(newCSS);$.each(_c.allPanes,function(i,p){if(p!=pane){resetOverflow(p)}})}function resetOverflow(el){if(!isInitialized()){return}if(this&&this.tagName){el=this}var $P;if(isStr(el)){$P=$Ps[el]}else{if($(el).data("layoutRole")){$P=$(el)}else{$(el).parents().each(function(){if($(this).data("layoutRole")){$P=$(this);return false}})}}if(!$P||!$P.length){return}var pane=$P.data("layoutEdge"),s=state[pane],CSS=s.cssSaved||{};if(!s.isSliding&&!s.isResizing){$P.css("zIndex",options.zIndexes.pane_normal)}$P.css(CSS);s.cssSaved=false}var $N=$(this).eq(0);if(!$N.length){return _log(options.errors.containerMissing)}if($N.data("layoutContainer")&&$N.data("layout")){return $N.data("layout")}var $Ps={},$Cs={},$Rs={},$Ts={},$Ms=$([]),sC=state.container,sID=state.id;var Instance={options:options,state:state,container:$N,panes:$Ps,contents:$Cs,resizers:$Rs,togglers:$Ts,hide:hide,show:show,toggle:toggle,open:open,close:close,slideOpen:slideOpen,slideClose:slideClose,slideToggle:slideToggle,setSizeLimits:setSizeLimits,_sizePane:sizePane,sizePane:manualSizePane,sizeContent:sizeContent,swapPanes:swapPanes,showMasks:showMasks,hideMasks:hideMasks,initContent:initContent,addPane:addPane,removePane:removePane,createChildren:createChildren,refreshChildren:refreshChildren,enableClosable:enableClosable,disableClosable:disableClosable,enableSlidable:enableSlidable,disableSlidable:disableSlidable,enableResizable:enableResizable,disableResizable:disableResizable,allowOverflow:allowOverflow,resetOverflow:resetOverflow,destroy:destroy,initPanes:isInitialized,resizeAll:resizeAll,runCallbacks:_runCallbacks,hasParentLayout:false,children:children,north:false,south:false,west:false,east:false,center:false};if(_create()==="cancel"){return null}else{return Instance}}})(jQuery);(function($){if(!$.layout){return}if(!$.ui){$.ui={}}$.ui.cookie={acceptsCookies:!!navigator.cookieEnabled,read:function(name){var c=document.cookie,cs=c?c.split(";"):[],pair,data,i;for(i=0;pair=cs[i];i++){data=$.trim(pair).split("=");if(data[0]==name){return decodeURIComponent(data[1])}}return null},write:function(name,val,cookieOpts){var params="",date="",clear=false,o=cookieOpts||{},x=o.expires||null,t=$.type(x);if(t==="date"){date=x}else{if(t==="string"&&x>0){x=parseInt(x,10);t="number"}}if(t==="number"){date=new Date();if(x>0){date.setDate(date.getDate()+x)}else{date.setFullYear(1970);clear=true}}if(date){params+=";expires="+date.toUTCString()}if(o.path){params+=";path="+o.path}if(o.domain){params+=";domain="+o.domain}if(o.secure){params+=";secure"}document.cookie=name+"="+(clear?"":encodeURIComponent(val))+params},clear:function(name){$.ui.cookie.write(name,"",{expires:-1})}};if(!$.cookie){$.cookie=function(k,v,o){var C=$.ui.cookie;if(v===null){C.clear(k)}else{if(v===undefined){return C.read(k)}else{C.write(k,v,o)}}}}$.layout.plugins.stateManagement=true;$.layout.defaults.stateManagement={enabled:false,autoSave:true,autoLoad:true,animateLoad:true,includeChildren:true,stateKeys:"north.size,south.size,east.size,west.size,"+"north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+"north.isHidden,south.isHidden,east.isHidden,west.isHidden",cookie:{name:"",domain:"",path:"",expires:"",secure:false}};$.layout.optionsMap.layout.push("stateManagement");$.layout.config.optionRootKeys.push("stateManagement");$.layout.state={saveCookie:function(inst,keys,cookieOpts){var o=inst.options,sm=o.stateManagement,oC=$.extend(true,{},sm.cookie,cookieOpts||null),data=inst.state.stateData=inst.readState(keys||sm.stateKeys);$.ui.cookie.write(oC.name||o.name||"Layout",$.layout.state.encodeJSON(data),oC);return $.extend(true,{},data)},deleteCookie:function(inst){var o=inst.options;$.ui.cookie.clear(o.stateManagement.cookie.name||o.name||"Layout")},readCookie:function(inst){var o=inst.options;var c=$.ui.cookie.read(o.stateManagement.cookie.name||o.name||"Layout");return c?$.layout.state.decodeJSON(c):{}},loadCookie:function(inst){var c=$.layout.state.readCookie(inst);if(c&&!$.isEmptyObject(c)){inst.state.stateData=$.extend(true,{},c);inst.loadState(c)}return c},loadState:function(inst,data,opts){if(!$.isPlainObject(data)||$.isEmptyObject(data)){return}data=inst.state.stateData=$.layout.transformData(data);var smo=inst.options.stateManagement;opts=$.extend({animateLoad:false,includeChildren:smo.includeChildren},opts);if(!inst.state.initialized){var o=$.extend(true,{},data);$.each($.layout.config.allPanes,function(idx,pane){if(o[pane]){delete o[pane].children}});$.extend(true,inst.options,o)}else{var noAnimate=!opts.animateLoad,o,c,h,state,open;$.each($.layout.config.borderPanes,function(idx,pane){o=data[pane];if(!$.isPlainObject(o)){return}s=o.size;c=o.initClosed;h=o.initHidden;ar=o.autoResize;state=inst.state[pane];open=state.isVisible;if(ar){state.autoResize=ar}if(!open){inst._sizePane(pane,s,false,false,false)}if(h===true){inst.hide(pane,noAnimate)}else{if(c===true){inst.close(pane,false,noAnimate)}else{if(c===false){inst.open(pane,false,noAnimate)}else{if(h===false){inst.show(pane,false,noAnimate)}}}}if(open){inst._sizePane(pane,s,false,false,noAnimate)}});if(opts.includeChildren){var paneStateChildren,childState;$.each(inst.children,function(pane,paneChildren){paneStateChildren=data[pane]?data[pane].children:0;if(paneStateChildren&&paneChildren){$.each(paneChildren,function(stateKey,child){childState=paneStateChildren[stateKey];if(child&&childState){child.loadState(childState)}})}})}}},readState:function(inst,opts){if($.type(opts)==="string"){opts={keys:opts}}if(!opts){opts={}}var sm=inst.options.stateManagement,ic=opts.includeChildren,recurse=ic!==undefined?ic:sm.includeChildren,keys=opts.stateKeys||sm.stateKeys,alt={isClosed:"initClosed",isHidden:"initHidden"},state=inst.state,panes=$.layout.config.allPanes,data={},pair,pane,key,val,ps,pC,child,array,count,branch;if($.isArray(keys)){keys=keys.join(",")}keys=keys.replace(/__/g,".").split(",");for(var i=0,n=keys.length;i=0){var btn=o[pane].buttonClass+"-"+action;$E.addClass(btn+" "+btn+"-"+pane).data("layoutName",o.name)}return $E},bind:function(inst,sel,action,pane){var _=$.layout.buttons;switch(action.toLowerCase()){case"toggle":_.addToggle(inst,sel,pane);break;case"open":_.addOpen(inst,sel,pane);break;case"close":_.addClose(inst,sel,pane);break;case"pin":_.addPin(inst,sel,pane);break;case"toggle-slide":_.addToggle(inst,sel,pane,true);break;case"open-slide":_.addOpen(inst,sel,pane,true);break}return inst},addToggle:function(inst,selector,pane,slide){$.layout.buttons.get(inst,selector,pane,"toggle").click(function(evt){inst.toggle(pane,!!slide);evt.stopPropagation()});return inst},addOpen:function(inst,selector,pane,slide){$.layout.buttons.get(inst,selector,pane,"open").attr("title",inst.options[pane].tips.Open).click(function(evt){inst.open(pane,!!slide);evt.stopPropagation()});return inst},addClose:function(inst,selector,pane){$.layout.buttons.get(inst,selector,pane,"close").attr("title",inst.options[pane].tips.Close).click(function(evt){inst.close(pane);evt.stopPropagation()});return inst},addPin:function(inst,selector,pane){var $E=$.layout.buttons.get(inst,selector,pane,"pin");if($E.length){var s=inst.state[pane];$E.click(function(evt){$.layout.buttons.setPinState(inst,$(this),pane,(s.isSliding||s.isClosed));if(s.isSliding||s.isClosed){inst.open(pane)}else{inst.close(pane)}evt.stopPropagation()});$.layout.buttons.setPinState(inst,$E,pane,(!s.isClosed&&!s.isSliding));s.pins.push(selector)}return inst},setPinState:function(inst,$Pin,pane,doPin){var updown=$Pin.attr("pin");if(updown&&doPin===(updown=="down")){return}var po=inst.options[pane],lang=po.tips,pin=po.buttonClass+"-pin",side=pin+"-"+pane,UP=pin+"-up "+side+"-up",DN=pin+"-down "+side+"-down";$Pin.attr("pin",doPin?"down":"up").attr("title",doPin?lang.Unpin:lang.Pin).removeClass(doPin?UP:DN).addClass(doPin?DN:UP)},syncPinBtns:function(inst,pane,doPin){$.each(state[pane].pins,function(i,selector){$.layout.buttons.setPinState(inst,$(selector),pane,doPin)})},_load:function(inst){$.extend(inst,{bindButton:function(selector,action,pane){return $.layout.buttons.bind(inst,selector,action,pane)},addToggleBtn:function(selector,pane,slide){return $.layout.buttons.addToggle(inst,selector,pane,slide)},addOpenBtn:function(selector,pane,slide){return $.layout.buttons.addOpen(inst,selector,pane,slide)},addCloseBtn:function(selector,pane){return $.layout.buttons.addClose(inst,selector,pane)},addPinBtn:function(selector,pane){return $.layout.buttons.addPin(inst,selector,pane)}});for(var i=0;i<4;i++){var pane=$.layout.buttons.config.borderPanes[i];inst.state[pane].pins=[]}if(inst.options.autoBindCustomButtons){$.layout.buttons.init(inst)}},_unload:function(inst){}};$.layout.onLoad.push($.layout.buttons._load)})(jQuery);(function($){$.layout.plugins.browserZoom=true;$.layout.defaults.browserZoomCheckInterval=1000;$.layout.optionsMap.layout.push("browserZoomCheckInterval");$.layout.browserZoom={_init:function(inst){if($.layout.browserZoom.ratio()!==false){$.layout.browserZoom._setTimer(inst)}},_setTimer:function(inst){if(inst.destroyed){return}var o=inst.options,s=inst.state,ms=inst.hasParentLayout?5000:Math.max(o.browserZoomCheckInterval,100);setTimeout(function(){if(inst.destroyed||!o.resizeWithWindow){return}var d=$.layout.browserZoom.ratio();if(d!==s.browserZoom){s.browserZoom=d;inst.resizeAll()}$.layout.browserZoom._setTimer(inst)},ms)},ratio:function(){var w=window,s=screen,d=document,dE=d.documentElement||d.body,b=$.layout.browser,v=b.version,r,sW,cW;if(!b.msie||v>8){return false}if(s.deviceXDPI&&s.systemXDPI){return calc(s.deviceXDPI,s.systemXDPI)}if(b.webkit&&(r=d.body.getBoundingClientRect)){return calc((r.left-r.right),d.body.offsetWidth)}if(b.webkit&&(sW=w.outerWidth)){return calc(sW,w.innerWidth)}if((sW=s.width)&&(cW=dE.clientWidth)){return calc(sW,cW)}return false;function calc(x,y){return(parseInt(x,10)/parseInt(y,10)*100).toFixed()}}};$.layout.onReady.push($.layout.browserZoom._init)})(jQuery);(function($){if($.effects){$.layout.defaults.panes.useOffscreenClose=false;if($.layout.plugins){$.layout.plugins.effects.slideOffscreen=true}$.layout.effects.slideOffscreen=$.extend(true,{},$.layout.effects.slide);$.effects.slideOffscreen=function(o){return this.queue(function(){var fx=$.effects,opt=o.options,$el=$(this),pane=$el.data("layoutEdge"),state=$el.data("parentLayout").state,dist=state[pane].size,s=this.style,props=["top","bottom","left","right"],mode=fx.setMode($el,opt.mode||"show"),show=(mode=="show"),dir=opt.direction||"left",ref=(dir=="up"||dir=="down")?"top":"left",pos=(dir=="up"||dir=="left"),offscrn=$.layout.config.offscreenCSS||{},keyLR=$.layout.config.offscreenReset,keyTB="offscreenResetTop",animation={};animation[ref]=(show?(pos?"+=":"-="):(pos?"-=":"+="))+dist;if(show){$el.data(keyTB,{top:s.top,bottom:s.bottom});if(pos){$el.css(ref,isNaN(dist)?"-"+dist:-dist)}else{if(dir==="right"){$el.css({left:state.container.layoutWidth,right:"auto"})}else{$el.css({top:state.container.layoutHeight,bottom:"auto"})}}if(ref==="top"){$el.css($el.data(keyLR)||{})}}else{$el.data(keyTB,{top:s.top,bottom:s.bottom});$el.data(keyLR,{left:s.left,right:s.right})}$el.show().animate(animation,{queue:false,duration:o.duration,easing:opt.easing,complete:function(){if($el.data(keyTB)){$el.css($el.data(keyTB)).removeData(keyTB)}if(show){$el.css($el.data(keyLR)||{}).removeData(keyLR)}else{$el.css(offscrn)}if(o.callback){o.callback.apply(this,arguments)}$el.dequeue()}})})}}})(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/default/zTreeStyle.css ================================================ /*------------------------------------- zTree Style version: 3.4 author: Hunter.z email: hunter.z@263.net website: http://code.google.com/p/jquerytree/ -------------------------------------*/ .ztree * {padding:0; margin:0; font-size:12px;} .ztree {margin:0; padding:2px; color:#333} .ztree li{padding:0; margin:0; list-style:none; line-height:18px; text-align:left; white-space:nowrap; outline:0} .ztree li ul{ margin:0; padding:0 0 0 18px} .ztree li ul.line{ background:url(./img/line_conn.gif) 0 0 repeat-y;} .ztree li a {padding:1px 3px 0 0; margin:0; cursor:pointer; /* height:17px; */ color:#333; background-color: transparent; text-decoration:none; vertical-align:top; display: inline-block} .ztree li a:hover {text-decoration:underline} /*.ztree li a.curSelectedNode {padding-top:0px; background-color:#FFE6B0; color:black; height:16px; border:1px #FFB951 solid; opacity:0.8;}*/ .ztree li a.curSelectedNode {padding-top:0px; background-color:#F6F6F6; color:#0663A2; border:1px #DDDDDD solid; opacity:0.8;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} /*.ztree li a.curSelectedNode {padding-top:0px; color:#0663a2; font-weight:bold; height:16px; opacity:0.8;}*/ .ztree li a.curSelectedNode_Edit {padding-top:0px; background-color:#FFE6B0; color:black; height:16px; border:1px #FFB951 solid; opacity:0.8;} .ztree li a.tmpTargetNode_inner {padding-top:0px; background-color:#316AC5; color:white; height:16px; border:1px #316AC5 solid; opacity:0.8; filter:alpha(opacity=80)} .ztree li a.tmpTargetNode_prev {} .ztree li a.tmpTargetNode_next {} .ztree li a input.rename {height:14px; width:80px; padding:0; margin:0; font-size:12px; border:1px #7EC4CC solid; *border:0px} .ztree li span {line-height:16px; margin-right:2px} .ztree li span.button {line-height:0; margin:0; width:16px; height:16px; display: inline-block; vertical-align:middle; border:0 none; cursor: pointer;outline:none; background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; background-image:url("./img/zTreeStandard.png"); *background-image:url("./img/zTreeStandard.gif")} /* IE7 fix */ .ztree li span.button.level0 {*margin-left:-15px;} .ztree li span.button.chk {width:13px; height:13px; margin:0 3px 0 0; cursor: auto} .ztree li span.button.chk.checkbox_false_full {background-position:0 0} .ztree li span.button.chk.checkbox_false_full_focus {background-position:0 -14px} .ztree li span.button.chk.checkbox_false_part {background-position:0 -28px} .ztree li span.button.chk.checkbox_false_part_focus {background-position:0 -42px} .ztree li span.button.chk.checkbox_false_disable {background-position:0 -56px} .ztree li span.button.chk.checkbox_true_full {background-position:-14px 0} .ztree li span.button.chk.checkbox_true_full_focus {background-position:-14px -14px} .ztree li span.button.chk.checkbox_true_part {background-position:-14px -28px} .ztree li span.button.chk.checkbox_true_part_focus {background-position:-14px -42px} .ztree li span.button.chk.checkbox_true_disable {background-position:-14px -56px} .ztree li span.button.chk.radio_false_full {background-position:-28px 0} .ztree li span.button.chk.radio_false_full_focus {background-position:-28px -14px} .ztree li span.button.chk.radio_false_part {background-position:-28px -28px} .ztree li span.button.chk.radio_false_part_focus {background-position:-28px -42px} .ztree li span.button.chk.radio_false_disable {background-position:-28px -56px} .ztree li span.button.chk.radio_true_full {background-position:-42px 0} .ztree li span.button.chk.radio_true_full_focus {background-position:-42px -14px} .ztree li span.button.chk.radio_true_part {background-position:-42px -28px} .ztree li span.button.chk.radio_true_part_focus {background-position:-42px -42px} .ztree li span.button.chk.radio_true_disable {background-position:-42px -56px} .ztree li span.button.switch {width:18px; height:18px} .ztree li span.button.root_open{background-position:-92px -54px} .ztree li span.button.root_close{background-position:-74px -54px} .ztree li span.button.roots_open{background-position:-92px 0} .ztree li span.button.roots_close{background-position:-74px 0} .ztree li span.button.center_open{background-position:-92px -18px} .ztree li span.button.center_close{background-position:-74px -18px} .ztree li span.button.bottom_open{background-position:-92px -36px} .ztree li span.button.bottom_close{background-position:-74px -36px} .ztree li span.button.noline_open{background-position:-92px -72px} .ztree li span.button.noline_close{background-position:-74px -72px} .ztree li span.button.root_docu{ background:none;} .ztree li span.button.roots_docu{background-position:-56px 0} .ztree li span.button.center_docu{background-position:-56px -18px} .ztree li span.button.bottom_docu{background-position:-56px -36px} .ztree li span.button.noline_docu{ background:none;} .ztree li span.button.ico_open{margin-right:2px; background-position:-110px -16px; vertical-align:top; *vertical-align:middle} .ztree li span.button.ico_close{margin-right:2px; background-position:-110px 0; vertical-align:top; *vertical-align:middle} .ztree li span.button.ico_docu{margin-right:2px; background-position:-110px -32px; /* vertical-align:top; * */vertical-align:middle} .ztree li span.button.edit {margin-right:2px; background-position:-110px -48px; vertical-align:top; *vertical-align:middle} .ztree li span.button.remove {margin-right:2px; background-position:-110px -64px; vertical-align:top; *vertical-align:middle} .ztree li span.button.ico_loading{margin-right:2px; background:url(./img/loading.gif) no-repeat scroll 0 0 transparent; vertical-align:top; *vertical-align:middle} ul.tmpTargetzTree {background-color:#FFE6B0; opacity:0.8; filter:alpha(opacity=80)} span.tmpzTreeMove_arrow {width:16px; height:16px; display: inline-block; padding:0; margin:2px 0 0 1px; border:0 none; position:absolute; background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; background-position:-110px -80px; background-image:url("./img/zTreeStandard.png"); *background-image:url("./img/zTreeStandard.gif")} ul.ztree.zTreeDragUL {margin:0; padding:0; position:absolute; width:auto; height:auto;overflow:hidden; background-color:#cfcfcf; border:1px #00B83F dotted; opacity:0.8; filter:alpha(opacity=80)} .zTreeMask {z-index:10000; background-color:#cfcfcf; opacity:0.0; filter:alpha(opacity=0); position:absolute} /* level style*/ /*.ztree li span.button.level0 { display:none; } .ztree li ul.level0 { padding:0; background:none; }*/ ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/metro/zTreeStyle.css ================================================ /*------------------------------------- zTree Style version: 3.4 author: Hunter.z email: hunter.z@263.net website: http://code.google.com/p/jquerytree/ -------------------------------------*/ .ztree * {padding:0; margin:0; font-size:12px; font-family: Verdana, Arial, Helvetica, AppleGothic, sans-serif} .ztree {margin:0; padding:5px; color:#333} .ztree li{padding:0; margin:0; list-style:none; line-height:21px; text-align:left; white-space:nowrap; outline:0} .ztree li ul{ margin:0; padding:0 0 0 18px} .ztree li ul.line{ background:url(./img/line_conn.png) 0 0 repeat-y;} .ztree li a {padding-right:3px; margin:0; cursor:pointer; height:21px; color:#333; background-color: transparent; text-decoration:none; display: inline-block} .ztree li a:hover {text-decoration:underline} .ztree li a.curSelectedNode {padding-top:0px; background-color:#e5e5e5; color:black; height:22px; -webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .ztree li a.curSelectedNode_Edit {padding-top:0px; background-color:#e5e5e5; color:black; height:22px; border:1px #666 solid; -webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .ztree li a.tmpTargetNode_inner {padding-top:0px; background-color:#aaa; color:white; height:21px; border:1px #666 solid; opacity:0.8; filter:alpha(opacity=80)} .ztree li a.tmpTargetNode_prev {} .ztree li a.tmpTargetNode_next {} .ztree li a input.rename {height:14px; width:80px; padding:0; margin:0; font-size:12px; border:1px #7EC4CC solid; *border:0px} .ztree li span {line-height:21px; margin-right:2px} .ztree li span.button {line-height:0; margin:0; width:21px; height:21px; display: inline-block; vertical-align:middle; border:0 none; cursor: pointer;outline:none; background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; background-image:url("./img/metro.png"); *background-image:url("./img/metro.gif")} .ztree li span.button.chk {width:13px; height:13px; margin:0 2px; cursor: auto} .ztree li span.button.chk.checkbox_false_full {background-position: -5px -5px;} .ztree li span.button.chk.checkbox_false_full_focus {background-position: -5px -26px;} .ztree li span.button.chk.checkbox_false_part {background-position: -5px -48px;} .ztree li span.button.chk.checkbox_false_part_focus {background-position: -5px -68px;} .ztree li span.button.chk.checkbox_false_disable {background-position: -5px -89px;} .ztree li span.button.chk.checkbox_true_full {background-position: -26px -5px;} .ztree li span.button.chk.checkbox_true_full_focus {background-position: -26px -26px;} .ztree li span.button.chk.checkbox_true_part {background-position: -26px -48px;} .ztree li span.button.chk.checkbox_true_part_focus {background-position: -26px -68px;} .ztree li span.button.chk.checkbox_true_disable {background-position: -26px -89px;} .ztree li span.button.chk.radio_false_full {background-position: -47px -5px;} .ztree li span.button.chk.radio_false_full_focus {background-position: -47px -26px;} .ztree li span.button.chk.radio_false_part {background-position: -47px -47px;} .ztree li span.button.chk.radio_false_part_focus {background-position: -47px -68px;} .ztree li span.button.chk.radio_false_disable {background-position: -47px -89px;} .ztree li span.button.chk.radio_true_full {background-position: -68px -5px;} .ztree li span.button.chk.radio_true_full_focus {background-position: -68px -26px;} .ztree li span.button.chk.radio_true_part {background-position: -68px -47px;} .ztree li span.button.chk.radio_true_part_focus {background-position: -68px -68px;} .ztree li span.button.chk.radio_true_disable {background-position: -68px -89px;} .ztree li span.button.switch {width:21px; height:21px} .ztree li span.button.root_open{background-position:-105px -85px} .ztree li span.button.root_close{background-position:-126px -85px} .ztree li span.button.roots_open{background-position: -105px 0;} .ztree li span.button.roots_close{background-position: -126px 0;} .ztree li span.button.center_open{background-position: -105px -21px;} .ztree li span.button.center_close{background-position: -126px -21px;} .ztree li span.button.bottom_open{background-position: -105px -42px;} .ztree li span.button.bottom_close{background-position: -126px -42px;} .ztree li span.button.noline_open{background-position: -126px -84px;} .ztree li span.button.noline_close{background-position: -105px -84px;} .ztree li span.button.root_docu{ background:none;} .ztree li span.button.roots_docu{background-position: -84px 0;} .ztree li span.button.center_docu{background-position: -84px -21px;} .ztree li span.button.bottom_docu{background-position: -84px -42px;} .ztree li span.button.noline_docu{ background:none;} .ztree li span.button.ico_open{margin-right:2px; background-position: -147px -21px; vertical-align:top; *vertical-align:middle} .ztree li span.button.ico_close{margin-right:2px; margin-right:2px; background-position: -147px 0; vertical-align:top; *vertical-align:middle} .ztree li span.button.ico_docu{margin-right:2px; background-position: -147px -42px; vertical-align:top; *vertical-align:middle} .ztree li span.button.edit {margin-left:2px; margin-right: -1px; background-position: -189px -21px; vertical-align:top; *vertical-align:middle} .ztree li span.button.edit:hover { background-position: -168px -21px; } .ztree li span.button.remove {margin-left:2px; margin-right: -1px; background-position: -189px -42px; vertical-align:top; *vertical-align:middle} .ztree li span.button.remove:hover { background-position: -168px -42px; } .ztree li span.button.add {margin-left:2px; margin-right: -1px; background-position: -189px 0; vertical-align:top; *vertical-align:middle} .ztree li span.button.add:hover { background-position: -168px 0; } .ztree li span.button.ico_loading{margin-right:2px; background:url(./img/loading.gif) no-repeat scroll 0 0 transparent; vertical-align:top; *vertical-align:middle} ul.tmpTargetzTree {background-color:#FFE6B0; opacity:0.8; filter:alpha(opacity=80)} span.tmpzTreeMove_arrow {width:16px; height:21px; display: inline-block; padding:0; margin:2px 0 0 1px; border:0 none; position:absolute; background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; background-position:-168px -84px; background-image:url("./img/metro.png"); *background-image:url("./img/metro.gif")} ul.ztree.zTreeDragUL {margin:0; padding:0; position:absolute; width:auto; height:auto;overflow:hidden; background-color:#cfcfcf; border:1px #00B83F dotted; opacity:0.8; filter:alpha(opacity=80)} .zTreeMask {z-index:10000; background-color:#cfcfcf; opacity:0.0; filter:alpha(opacity=0); position:absolute} /* 树搜索相关 */ .treeSearchInput {padding:13px 0 0 20px;} .treeSearchInput label {padding:5px 0 3px 0;font-size:13px;font-weight:normal;vertical-align:middle;} .treeSearchInput input {width:145px;vertical-align:middle;line-height:24px;height:26px;border:1px solid #bbb;padding:0 4px;} .treeSearchInput button {border:1px solid #bbb;vertical-align:middle;height:26px;height:26px\9;font-size:13px;background:#efefef;padding:0 8px;} .treeShowHideButton {position:absolute;right:8px;top:2px;font-size:12px;color:#333;z-index:3;} .treeShowHideButton label {cursor:pointer;} .treeExpandCollapse {float:right;margin:6px 5px;padding:5px;font-size:12px;color:#333;position:relative;z-index:2;background:#fff;} .treeExpandCollapse a {text-decoration:none;color:#333} .treeselect.ztree {padding:10px 20px;} ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/css/simple/zTreeStyle.css ================================================ /*------------------------------------- zTree Style version: 3.4 author: Hunter.z email: hunter.z@263.net website: http://code.google.com/p/jquerytree/ -------------------------------------*/ .ztree * {padding:0; margin:0; font-size:12px; font-family: Verdana, Arial, Helvetica, AppleGothic, sans-serif} .ztree {margin:0; padding:5px; color:#333} .ztree li{padding:0; margin:0; list-style:none; line-height:21px; text-align:left; white-space:nowrap; outline:0} .ztree li ul{ margin:0; padding:0 0 0 18px} .ztree li ul.line{ background:url(./img/line_conn.png) 0 0 repeat-y;} .ztree li a {padding-right:3px; margin:0; cursor:pointer; height:21px; color:#333; background-color: transparent; text-decoration:none; display: inline-block} .ztree li a:hover {text-decoration:underline} .ztree li a.curSelectedNode {padding-top:0px; background-color:#e5e5e5; color:black; height:21px; opacity:0.8;} .ztree li a.curSelectedNode_Edit {padding-top:0px; background-color:#e5e5e5; color:black; height:21px; border:1px #666 solid; opacity:0.8;} .ztree li a.tmpTargetNode_inner {padding-top:0px; background-color:#aaa; color:white; height:21px; border:1px #666 solid; opacity:0.8; filter:alpha(opacity=80)} .ztree li a.tmpTargetNode_prev {} .ztree li a.tmpTargetNode_next {} .ztree li a input.rename {height:14px; width:80px; padding:0; margin:0; font-size:12px; border:1px #7EC4CC solid; *border:0px} .ztree li span {line-height:21px; margin-right:2px} .ztree li span.button {line-height:0; margin:0; width:21px; height:21px; display: inline-block; vertical-align:middle; border:0 none; cursor: pointer;outline:none; background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; background-image:url("./img/metro.png"); *background-image:url("./img/metro.gif")} .ztree li span.button.chk {width:13px; height:13px; margin:0 2px; cursor: auto} .ztree li span.button.chk.checkbox_false_full {background-position: -5px -5px;} .ztree li span.button.chk.checkbox_false_full_focus {background-position: -5px -26px;} .ztree li span.button.chk.checkbox_false_part {background-position: -5px -48px;} .ztree li span.button.chk.checkbox_false_part_focus {background-position: -5px -68px;} .ztree li span.button.chk.checkbox_false_disable {background-position: -5px -89px;} .ztree li span.button.chk.checkbox_true_full {background-position: -26px -5px;} .ztree li span.button.chk.checkbox_true_full_focus {background-position: -26px -26px;} .ztree li span.button.chk.checkbox_true_part {background-position: -26px -48px;} .ztree li span.button.chk.checkbox_true_part_focus {background-position: -26px -68px;} .ztree li span.button.chk.checkbox_true_disable {background-position: -26px -89px;} .ztree li span.button.chk.radio_false_full {background-position: -47px -5px;} .ztree li span.button.chk.radio_false_full_focus {background-position: -47px -26px;} .ztree li span.button.chk.radio_false_part {background-position: -47px -47px;} .ztree li span.button.chk.radio_false_part_focus {background-position: -47px -68px;} .ztree li span.button.chk.radio_false_disable {background-position: -47px -89px;} .ztree li span.button.chk.radio_true_full {background-position: -68px -5px;} .ztree li span.button.chk.radio_true_full_focus {background-position: -68px -26px;} .ztree li span.button.chk.radio_true_part {background-position: -68px -47px;} .ztree li span.button.chk.radio_true_part_focus {background-position: -68px -68px;} .ztree li span.button.chk.radio_true_disable {background-position: -68px -89px;} .ztree li span.button.switch {width:21px; height:21px} .ztree li span.button.root_open{background-position:-92px -54px} .ztree li span.button.root_close{background-position:-74px -54px} .ztree li span.button.roots_open{background-position: -105px 0;} .ztree li span.button.roots_close{background-position: -126px 0;} .ztree li span.button.center_open{background-position: -105px -21px;} .ztree li span.button.center_close{background-position: -126px -21px;} .ztree li span.button.bottom_open{background-position: -105px -42px;} .ztree li span.button.bottom_close{background-position: -126px -42px;} .ztree li span.button.noline_open{background-position: -126px -84px;} .ztree li span.button.noline_close{background-position: -105px -84px;} .ztree li span.button.root_docu{ background:none;} .ztree li span.button.roots_docu{background-position: -84px 0;} .ztree li span.button.center_docu{background-position: -84px -21px;} .ztree li span.button.bottom_docu{background-position: -84px -42px;} .ztree li span.button.noline_docu{ background:none;} .ztree li span.button.ico_open{margin-right:2px; background-position: -147px -21px; vertical-align:top; *vertical-align:middle} .ztree li span.button.ico_close{margin-right:2px; margin-right:2px; background-position: -147px 0; vertical-align:top; *vertical-align:middle} .ztree li span.button.ico_docu{margin-right:2px; background-position: -147px -42px; vertical-align:top; *vertical-align:middle} .ztree li span.button.edit {margin-left:2px; margin-right: -1px; background-position: -189px -21px; vertical-align:top; *vertical-align:middle} .ztree li span.button.edit:hover { background-position: -168px -21px; } .ztree li span.button.remove {margin-left:2px; margin-right: -1px; background-position: -189px -42px; vertical-align:top; *vertical-align:middle} .ztree li span.button.remove:hover { background-position: -168px -42px; } .ztree li span.button.add {margin-left:2px; margin-right: -1px; background-position: -189px 0; vertical-align:top; *vertical-align:middle} .ztree li span.button.add:hover { background-position: -168px 0; } .ztree li span.button.ico_loading{margin-right:2px; background:url(./img/loading.gif) no-repeat scroll 0 0 transparent; vertical-align:top; *vertical-align:middle} ul.tmpTargetzTree {background-color:#FFE6B0; opacity:0.8; filter:alpha(opacity=80)} span.tmpzTreeMove_arrow {width:16px; height:21px; display: inline-block; padding:0; margin:2px 0 0 1px; border:0 none; position:absolute; background-color:transparent; background-repeat:no-repeat; background-attachment: scroll; background-position:-168px -84px; background-image:url("./img/metro.png"); *background-image:url("./img/metro.gif")} ul.ztree.zTreeDragUL {margin:0; padding:0; position:absolute; width:auto; height:auto;overflow:hidden; background-color:#cfcfcf; border:1px #00B83F dotted; opacity:0.8; filter:alpha(opacity=80)} .zTreeMask {z-index:10000; background-color:#cfcfcf; opacity:0.0; filter:alpha(opacity=0); position:absolute} /* simple */ .ztree * {font-size:14px;font-family:"Microsoft Yahei",Verdana,Simsun,"Segoe UI Web Light","Segoe UI Light","Segoe UI Web Regular","Segoe UI","Segoe UI Symbol","Helvetica Neue",Arial;} .ztree li ul{ margin:0; padding:0} .ztree li {line-height:28px;} .ztree li a {width:100%;height:28px;padding-top: 0px;} .ztree li a:hover {text-decoration:none; background-color: #E7E7E7;} .ztree11 li a span.button.switch {visibility:hidden} .ztree11.showIcon li a span.button.switch {visibility:visible} .ztree li a.curSelectedNode {background-color:#D4D4D4;border:0;height:28px;} .ztree li span {line-height:26px;margin-right:0px;} .ztree li span.button {margin-top: -7px;} .ztree li span.button.switch {width:16px;height: 16px;} .ztree li a.level0 span {font-size:15px;font-weight:bold;} .ztree li span.button {background-image:url("img/left_menu.png"); *background-image:url("./left_menu.gif")} .ztree li span.button.switch.level0 {width: 20px; height:20px} .ztree li span.button.switch.level1 {width: 20px; height:20px} .ztree li span.button.noline_open {background-position: 0 0;} .ztree li span.button.noline_close {background-position: -18px 0;} .ztree li span.button.noline_open.level0 {background-position: 0 -17px;} .ztree li span.button.noline_close.level0 {background-position: -18px -17px;} ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.all-3.5.js ================================================ /* * JQuery zTree core 3.5.12 * http://zTree.me/ * * Copyright (c) 2010 Hunter.z * * Licensed same as jquery - MIT License * http://www.opensource.org/licenses/mit-license.php * * email: hunter.z@263.net * Date: 2013-03-11 */ (function($){ var settings = {}, roots = {}, caches = {}, //default consts of core _consts = { className: { BUTTON: "button", LEVEL: "level", ICO_LOADING: "ico_loading", SWITCH: "switch" }, event: { NODECREATED: "ztree_nodeCreated", CLICK: "ztree_click", EXPAND: "ztree_expand", COLLAPSE: "ztree_collapse", ASYNC_SUCCESS: "ztree_async_success", ASYNC_ERROR: "ztree_async_error" }, id: { A: "_a", ICON: "_ico", SPAN: "_span", SWITCH: "_switch", UL: "_ul" }, line: { ROOT: "root", ROOTS: "roots", CENTER: "center", BOTTOM: "bottom", NOLINE: "noline", LINE: "line" }, folder: { OPEN: "open", CLOSE: "close", DOCU: "docu" }, node: { CURSELECTED: "curSelectedNode" } }, //default setting of core _setting = { treeId: "", treeObj: null, view: { addDiyDom: null, autoCancelSelected: true, dblClickExpand: true, expandSpeed: "fast", fontCss: {}, nameIsHTML: false, selectedMulti: true, showIcon: true, showLine: true, showTitle: true }, data: { key: { children: "children", name: "name", title: "", url: "url" }, simpleData: { enable: false, idKey: "id", pIdKey: "pId", rootPId: null }, keep: { parent: false, leaf: false } }, async: { enable: false, contentType: "application/x-www-form-urlencoded", type: "post", dataType: "text", url: "", autoParam: [], otherParam: [], dataFilter: null }, callback: { beforeAsync:null, beforeClick:null, beforeDblClick:null, beforeRightClick:null, beforeMouseDown:null, beforeMouseUp:null, beforeExpand:null, beforeCollapse:null, beforeRemove:null, onAsyncError:null, onAsyncSuccess:null, onNodeCreated:null, onClick:null, onDblClick:null, onRightClick:null, onMouseDown:null, onMouseUp:null, onExpand:null, onCollapse:null, onRemove:null } }, //default root of core //zTree use root to save full data _initRoot = function (setting) { var r = data.getRoot(setting); if (!r) { r = {}; data.setRoot(setting, r); } r[setting.data.key.children] = []; r.expandTriggerFlag = false; r.curSelectedList = []; r.noSelection = true; r.createdNodes = []; r.zId = 0; r._ver = (new Date()).getTime(); }, //default cache of core _initCache = function(setting) { var c = data.getCache(setting); if (!c) { c = {}; data.setCache(setting, c); } c.nodes = []; c.doms = []; }, //default bindEvent of core _bindEvent = function(setting) { var o = setting.treeObj, c = consts.event; o.bind(c.NODECREATED, function (event, treeId, node) { tools.apply(setting.callback.onNodeCreated, [event, treeId, node]); }); o.bind(c.CLICK, function (event, srcEvent, treeId, node, clickFlag) { tools.apply(setting.callback.onClick, [srcEvent, treeId, node, clickFlag]); }); o.bind(c.EXPAND, function (event, treeId, node) { tools.apply(setting.callback.onExpand, [event, treeId, node]); }); o.bind(c.COLLAPSE, function (event, treeId, node) { tools.apply(setting.callback.onCollapse, [event, treeId, node]); }); o.bind(c.ASYNC_SUCCESS, function (event, treeId, node, msg) { tools.apply(setting.callback.onAsyncSuccess, [event, treeId, node, msg]); }); o.bind(c.ASYNC_ERROR, function (event, treeId, node, XMLHttpRequest, textStatus, errorThrown) { tools.apply(setting.callback.onAsyncError, [event, treeId, node, XMLHttpRequest, textStatus, errorThrown]); }); }, _unbindEvent = function(setting) { var o = setting.treeObj, c = consts.event; o.unbind(c.NODECREATED) .unbind(c.CLICK) .unbind(c.EXPAND) .unbind(c.COLLAPSE) .unbind(c.ASYNC_SUCCESS) .unbind(c.ASYNC_ERROR); }, //default event proxy of core _eventProxy = function(event) { var target = event.target, setting = data.getSetting(event.data.treeId), tId = "", node = null, nodeEventType = "", treeEventType = "", nodeEventCallback = null, treeEventCallback = null, tmp = null; if (tools.eqs(event.type, "mousedown")) { treeEventType = "mousedown"; } else if (tools.eqs(event.type, "mouseup")) { treeEventType = "mouseup"; } else if (tools.eqs(event.type, "contextmenu")) { treeEventType = "contextmenu"; } else if (tools.eqs(event.type, "click")) { if (tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.SWITCH) !== null) { tId = ($(target).parent("li").get(0) || $(target).parentsUntil("li").parent().get(0)).id; nodeEventType = "switchNode"; } else { tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) { tId = ($(tmp).parent("li").get(0) || $(tmp).parentsUntil("li").parent().get(0)).id; nodeEventType = "clickNode"; } } } else if (tools.eqs(event.type, "dblclick")) { treeEventType = "dblclick"; tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) { tId = ($(tmp).parent("li").get(0) || $(tmp).parentsUntil("li").parent().get(0)).id; nodeEventType = "switchNode"; } } if (treeEventType.length > 0 && tId.length == 0) { tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) {tId = ($(tmp).parent("li").get(0) || $(tmp).parentsUntil("li").parent().get(0)).id;} } // event to node if (tId.length>0) { node = data.getNodeCache(setting, tId); switch (nodeEventType) { case "switchNode" : if (!node.isParent) { nodeEventType = ""; } else if (tools.eqs(event.type, "click") || (tools.eqs(event.type, "dblclick") && tools.apply(setting.view.dblClickExpand, [setting.treeId, node], setting.view.dblClickExpand))) { nodeEventCallback = handler.onSwitchNode; } else { nodeEventType = ""; } break; case "clickNode" : nodeEventCallback = handler.onClickNode; break; } } // event to zTree switch (treeEventType) { case "mousedown" : treeEventCallback = handler.onZTreeMousedown; break; case "mouseup" : treeEventCallback = handler.onZTreeMouseup; break; case "dblclick" : treeEventCallback = handler.onZTreeDblclick; break; case "contextmenu" : treeEventCallback = handler.onZTreeContextmenu; break; } var proxyResult = { stop: false, node: node, nodeEventType: nodeEventType, nodeEventCallback: nodeEventCallback, treeEventType: treeEventType, treeEventCallback: treeEventCallback }; return proxyResult }, //default init node of core _initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { if (!n) return; var r = data.getRoot(setting), childKey = setting.data.key.children; n.level = level; n.tId = setting.treeId + "_" + (++r.zId); n.parentTId = parentNode ? parentNode.tId : null; if (n[childKey] && n[childKey].length > 0) { if (typeof n.open == "string") n.open = tools.eqs(n.open, "true"); n.open = !!n.open; n.isParent = true; n.zAsync = true; } else { n.open = false; if (typeof n.isParent == "string") n.isParent = tools.eqs(n.isParent, "true"); n.isParent = !!n.isParent; n.zAsync = !n.isParent; } n.isFirstNode = isFirstNode; n.isLastNode = isLastNode; n.getParentNode = function() {return data.getNodeCache(setting, n.parentTId);}; n.getPreNode = function() {return data.getPreNode(setting, n);}; n.getNextNode = function() {return data.getNextNode(setting, n);}; n.isAjaxing = false; data.fixPIdKeyValue(setting, n); }, _init = { bind: [_bindEvent], unbind: [_unbindEvent], caches: [_initCache], nodes: [_initNode], proxys: [_eventProxy], roots: [_initRoot], beforeA: [], afterA: [], innerBeforeA: [], innerAfterA: [], zTreeTools: [] }, //method of operate data data = { addNodeCache: function(setting, node) { data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = node; }, getNodeCacheId: function(tId) { return tId.substring(tId.lastIndexOf("_")+1); }, addAfterA: function(afterA) { _init.afterA.push(afterA); }, addBeforeA: function(beforeA) { _init.beforeA.push(beforeA); }, addInnerAfterA: function(innerAfterA) { _init.innerAfterA.push(innerAfterA); }, addInnerBeforeA: function(innerBeforeA) { _init.innerBeforeA.push(innerBeforeA); }, addInitBind: function(bindEvent) { _init.bind.push(bindEvent); }, addInitUnBind: function(unbindEvent) { _init.unbind.push(unbindEvent); }, addInitCache: function(initCache) { _init.caches.push(initCache); }, addInitNode: function(initNode) { _init.nodes.push(initNode); }, addInitProxy: function(initProxy) { _init.proxys.push(initProxy); }, addInitRoot: function(initRoot) { _init.roots.push(initRoot); }, addNodesData: function(setting, parentNode, nodes) { var childKey = setting.data.key.children; if (!parentNode[childKey]) parentNode[childKey] = []; if (parentNode[childKey].length > 0) { parentNode[childKey][parentNode[childKey].length - 1].isLastNode = false; view.setNodeLineIcos(setting, parentNode[childKey][parentNode[childKey].length - 1]); } parentNode.isParent = true; parentNode[childKey] = parentNode[childKey].concat(nodes); }, addSelectedNode: function(setting, node) { var root = data.getRoot(setting); if (!data.isSelectedNode(setting, node)) { root.curSelectedList.push(node); } }, addCreatedNode: function(setting, node) { if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) { var root = data.getRoot(setting); root.createdNodes.push(node); } }, addZTreeTools: function(zTreeTools) { _init.zTreeTools.push(zTreeTools); }, exSetting: function(s) { $.extend(true, _setting, s); }, fixPIdKeyValue: function(setting, node) { if (setting.data.simpleData.enable) { node[setting.data.simpleData.pIdKey] = node.parentTId ? node.getParentNode()[setting.data.simpleData.idKey] : setting.data.simpleData.rootPId; } }, getAfterA: function(setting, node, array) { for (var i=0, j=_init.afterA.length; i-1) { result.push(nodes[i]); } result = result.concat(data.getNodesByParamFuzzy(setting, nodes[i][childKey], key, value)); } return result; }, getNodesByFilter: function(setting, nodes, filter, isSingle, invokeParam) { if (!nodes) return (isSingle ? null : []); var childKey = setting.data.key.children, result = isSingle ? null : []; for (var i = 0, l = nodes.length; i < l; i++) { if (tools.apply(filter, [nodes[i], invokeParam], false)) { if (isSingle) {return nodes[i];} result.push(nodes[i]); } var tmpResult = data.getNodesByFilter(setting, nodes[i][childKey], filter, isSingle, invokeParam); if (isSingle && !!tmpResult) {return tmpResult;} result = isSingle ? tmpResult : result.concat(tmpResult); } return result; }, getPreNode: function(setting, node) { if (!node) return null; var childKey = setting.data.key.children, p = node.parentTId ? node.getParentNode() : data.getRoot(setting); for (var i=0, l=p[childKey].length; i 0))); }, clone: function (obj){ if (obj === null) return null; var o = obj.constructor === Array ? [] : {}; for(var i in obj){ o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? arguments.callee(obj[i]) : obj[i]); } return o; }, eqs: function(str1, str2) { return str1.toLowerCase() === str2.toLowerCase(); }, isArray: function(arr) { return Object.prototype.toString.apply(arr) === "[object Array]"; }, getMDom: function (setting, curDom, targetExpr) { if (!curDom) return null; while (curDom && curDom.id !== setting.treeId) { for (var i=0, l=targetExpr.length; curDom.tagName && i 0) { //make child html first, because checkType childHtml = view.appendNodes(setting, level + 1, node[childKey], node, initFlag, openFlag && node.open); } if (openFlag) { view.makeDOMNodeMainBefore(html, setting, node); view.makeDOMNodeLine(html, setting, node); data.getBeforeA(setting, node, html); view.makeDOMNodeNameBefore(html, setting, node); data.getInnerBeforeA(setting, node, html); view.makeDOMNodeIcon(html, setting, node); data.getInnerAfterA(setting, node, html); view.makeDOMNodeNameAfter(html, setting, node); data.getAfterA(setting, node, html); if (node.isParent && node.open) { view.makeUlHtml(setting, node, html, childHtml.join('')); } view.makeDOMNodeMainAfter(html, setting, node); data.addCreatedNode(setting, node); } } return html; }, appendParentULDom: function(setting, node) { var html = [], nObj = $("#" + node.tId), ulObj = $("#" + node.tId + consts.id.UL), childKey = setting.data.key.children, childHtml = view.appendNodes(setting, node.level+1, node[childKey], node, false, true); view.makeUlHtml(setting, node, html, childHtml.join('')); if (!nObj.get(0) && !!node.parentTId) { view.appendParentULDom(setting, node.getParentNode()); nObj = $("#" + node.tId); } if (ulObj.get(0)) { ulObj.remove(); } nObj.append(html.join('')); }, asyncNode: function(setting, node, isSilent, callback) { var i, l; if (node && !node.isParent) { tools.apply(callback); return false; } else if (node && node.isAjaxing) { return false; } else if (tools.apply(setting.callback.beforeAsync, [setting.treeId, node], true) == false) { tools.apply(callback); return false; } if (node) { node.isAjaxing = true; var icoObj = $("#" + node.tId + consts.id.ICON); icoObj.attr({"style":"", "class":consts.className.BUTTON + " " + consts.className.ICO_LOADING}); } var tmpParam = {}; for (i = 0, l = setting.async.autoParam.length; node && i < l; i++) { var pKey = setting.async.autoParam[i].split("="), spKey = pKey; if (pKey.length>1) { spKey = pKey[1]; pKey = pKey[0]; } tmpParam[spKey] = node[pKey]; } if (tools.isArray(setting.async.otherParam)) { for (i = 0, l = setting.async.otherParam.length; i < l; i += 2) { tmpParam[setting.async.otherParam[i]] = setting.async.otherParam[i + 1]; } } else { for (var p in setting.async.otherParam) { tmpParam[p] = setting.async.otherParam[p]; } } var _tmpV = data.getRoot(setting)._ver; $.ajax({ contentType: setting.async.contentType, type: setting.async.type, url: tools.apply(setting.async.url, [setting.treeId, node], setting.async.url), data: tmpParam, dataType: setting.async.dataType, success: function(msg) { if (_tmpV != data.getRoot(setting)._ver) { return; } var newNodes = []; try { if (!msg || msg.length == 0) { newNodes = []; } else if (typeof msg == "string") { newNodes = eval("(" + msg + ")"); } else { newNodes = msg; } } catch(err) { newNodes = msg; } if (node) { node.isAjaxing = null; node.zAsync = true; } view.setNodeLineIcos(setting, node); if (newNodes && newNodes !== "") { newNodes = tools.apply(setting.async.dataFilter, [setting.treeId, node, newNodes], newNodes); view.addNodes(setting, node, !!newNodes ? tools.clone(newNodes) : [], !!isSilent); } else { view.addNodes(setting, node, [], !!isSilent); } setting.treeObj.trigger(consts.event.ASYNC_SUCCESS, [setting.treeId, node, msg]); tools.apply(callback); }, error: function(XMLHttpRequest, textStatus, errorThrown) { if (_tmpV != data.getRoot(setting)._ver) { return; } if (node) node.isAjaxing = null; view.setNodeLineIcos(setting, node); setting.treeObj.trigger(consts.event.ASYNC_ERROR, [setting.treeId, node, XMLHttpRequest, textStatus, errorThrown]); } }); return true; }, cancelPreSelectedNode: function (setting, node) { var list = data.getRoot(setting).curSelectedList; for (var i=0, j=list.length-1; j>=i; j--) { if (!node || node === list[j]) { $("#" + list[j].tId + consts.id.A).removeClass(consts.node.CURSELECTED); if (node) { data.removeSelectedNode(setting, node); break; } } } if (!node) data.getRoot(setting).curSelectedList = []; }, createNodeCallback: function(setting) { if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) { var root = data.getRoot(setting); while (root.createdNodes.length>0) { var node = root.createdNodes.shift(); tools.apply(setting.view.addDiyDom, [setting.treeId, node]); if (!!setting.callback.onNodeCreated) { setting.treeObj.trigger(consts.event.NODECREATED, [setting.treeId, node]); } } } }, createNodes: function(setting, level, nodes, parentNode) { if (!nodes || nodes.length == 0) return; var root = data.getRoot(setting), childKey = setting.data.key.children, openFlag = !parentNode || parentNode.open || !!$("#" + parentNode[childKey][0].tId).get(0); root.createdNodes = []; var zTreeHtml = view.appendNodes(setting, level, nodes, parentNode, true, openFlag); if (!parentNode) { setting.treeObj.append(zTreeHtml.join('')); } else { var ulObj = $("#" + parentNode.tId + consts.id.UL); if (ulObj.get(0)) { ulObj.append(zTreeHtml.join('')); } } view.createNodeCallback(setting); }, destroy: function(setting) { if (!setting) return; data.initCache(setting); data.initRoot(setting); event.unbindTree(setting); event.unbindEvent(setting); setting.treeObj.empty(); }, expandCollapseNode: function(setting, node, expandFlag, animateFlag, callback) { var root = data.getRoot(setting), childKey = setting.data.key.children; if (!node) { tools.apply(callback, []); return; } if (root.expandTriggerFlag) { var _callback = callback; callback = function(){ if (_callback) _callback(); if (node.open) { setting.treeObj.trigger(consts.event.EXPAND, [setting.treeId, node]); } else { setting.treeObj.trigger(consts.event.COLLAPSE, [setting.treeId, node]); } }; root.expandTriggerFlag = false; } if (!node.open && node.isParent && ((!$("#" + node.tId + consts.id.UL).get(0)) || (node[childKey] && node[childKey].length>0 && !$("#" + node[childKey][0].tId).get(0)))) { view.appendParentULDom(setting, node); view.createNodeCallback(setting); } if (node.open == expandFlag) { tools.apply(callback, []); return; } var ulObj = $("#" + node.tId + consts.id.UL), switchObj = $("#" + node.tId + consts.id.SWITCH), icoObj = $("#" + node.tId + consts.id.ICON); if (node.isParent) { node.open = !node.open; if (node.iconOpen && node.iconClose) { icoObj.attr("style", view.makeNodeIcoStyle(setting, node)); } if (node.open) { view.replaceSwitchClass(node, switchObj, consts.folder.OPEN); view.replaceIcoClass(node, icoObj, consts.folder.OPEN); if (animateFlag == false || setting.view.expandSpeed == "") { ulObj.show(); tools.apply(callback, []); } else { if (node[childKey] && node[childKey].length > 0) { ulObj.slideDown(setting.view.expandSpeed, callback); } else { ulObj.show(); tools.apply(callback, []); } } } else { view.replaceSwitchClass(node, switchObj, consts.folder.CLOSE); view.replaceIcoClass(node, icoObj, consts.folder.CLOSE); if (animateFlag == false || setting.view.expandSpeed == "" || !(node[childKey] && node[childKey].length > 0)) { ulObj.hide(); tools.apply(callback, []); } else { ulObj.slideUp(setting.view.expandSpeed, callback); } } } else { tools.apply(callback, []); } }, expandCollapseParentNode: function(setting, node, expandFlag, animateFlag, callback) { if (!node) return; if (!node.parentTId) { view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback); return; } else { view.expandCollapseNode(setting, node, expandFlag, animateFlag); } if (node.parentTId) { view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, animateFlag, callback); } }, expandCollapseSonNode: function(setting, node, expandFlag, animateFlag, callback) { var root = data.getRoot(setting), childKey = setting.data.key.children, treeNodes = (node) ? node[childKey]: root[childKey], selfAnimateSign = (node) ? false : animateFlag, expandTriggerFlag = data.getRoot(setting).expandTriggerFlag; data.getRoot(setting).expandTriggerFlag = false; if (treeNodes) { for (var i = 0, l = treeNodes.length; i < l; i++) { if (treeNodes[i]) view.expandCollapseSonNode(setting, treeNodes[i], expandFlag, selfAnimateSign); } } data.getRoot(setting).expandTriggerFlag = expandTriggerFlag; view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback ); }, makeDOMNodeIcon: function(html, setting, node) { var nameStr = data.getNodeName(setting, node), name = setting.view.nameIsHTML ? nameStr : nameStr.replace(/&/g,'&').replace(//g,'>'); html.push("",name,""); }, makeDOMNodeLine: function(html, setting, node) { html.push(""); }, makeDOMNodeMainAfter: function(html, setting, node) { html.push(""); }, makeDOMNodeMainBefore: function(html, setting, node) { html.push("
    • "); }, makeDOMNodeNameAfter: function(html, setting, node) { html.push(""); }, makeDOMNodeNameBefore: function(html, setting, node) { var title = data.getNodeTitle(setting, node), url = view.makeNodeUrl(setting, node), fontcss = view.makeNodeFontCss(setting, node), fontStyle = []; for (var f in fontcss) { fontStyle.push(f, ":", fontcss[f], ";"); } html.push(" 0) ? "href='" + url + "'" : ""), " target='",view.makeNodeTarget(node),"' style='", fontStyle.join(''), "'"); if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle) && title) {html.push("title='", title.replace(/'/g,"'").replace(//g,'>'),"'");} html.push(">"); }, makeNodeFontCss: function(setting, node) { var fontCss = tools.apply(setting.view.fontCss, [setting.treeId, node], setting.view.fontCss); return (fontCss && ((typeof fontCss) != "function")) ? fontCss : {}; }, makeNodeIcoClass: function(setting, node) { var icoCss = ["ico"]; if (!node.isAjaxing) { icoCss[0] = (node.iconSkin ? node.iconSkin + "_" : "") + icoCss[0]; if (node.isParent) { icoCss.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE); } else { icoCss.push(consts.folder.DOCU); } } return consts.className.BUTTON + " " + icoCss.join('_'); }, makeNodeIcoStyle: function(setting, node) { var icoStyle = []; if (!node.isAjaxing) { var icon = (node.isParent && node.iconOpen && node.iconClose) ? (node.open ? node.iconOpen : node.iconClose) : node.icon; if (icon) icoStyle.push("background:url(", icon, ") 0 0 no-repeat;"); if (setting.view.showIcon == false || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)) { icoStyle.push("width:0px;height:0px;"); } } return icoStyle.join(''); }, makeNodeLineClass: function(setting, node) { var lineClass = []; if (setting.view.showLine) { if (node.level == 0 && node.isFirstNode && node.isLastNode) { lineClass.push(consts.line.ROOT); } else if (node.level == 0 && node.isFirstNode) { lineClass.push(consts.line.ROOTS); } else if (node.isLastNode) { lineClass.push(consts.line.BOTTOM); } else { lineClass.push(consts.line.CENTER); } } else { lineClass.push(consts.line.NOLINE); } if (node.isParent) { lineClass.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE); } else { lineClass.push(consts.folder.DOCU); } return view.makeNodeLineClassEx(node) + lineClass.join('_'); }, makeNodeLineClassEx: function(node) { return consts.className.BUTTON + " " + consts.className.LEVEL + node.level + " " + consts.className.SWITCH + " "; }, makeNodeTarget: function(node) { return (node.target || "_blank"); }, makeNodeUrl: function(setting, node) { var urlKey = setting.data.key.url; return node[urlKey] ? node[urlKey] : null; }, makeUlHtml: function(setting, node, html, content) { html.push("
        "); html.push(content); html.push("
      "); }, makeUlLineClass: function(setting, node) { return ((setting.view.showLine && !node.isLastNode) ? consts.line.LINE : ""); }, removeChildNodes: function(setting, node) { if (!node) return; var childKey = setting.data.key.children, nodes = node[childKey]; if (!nodes) return; for (var i = 0, l = nodes.length; i < l; i++) { data.removeNodeCache(setting, nodes[i]); } data.removeSelectedNode(setting); delete node[childKey]; if (!setting.data.keep.parent) { node.isParent = false; node.open = false; var tmp_switchObj = $("#" + node.tId + consts.id.SWITCH), tmp_icoObj = $("#" + node.tId + consts.id.ICON); view.replaceSwitchClass(node, tmp_switchObj, consts.folder.DOCU); view.replaceIcoClass(node, tmp_icoObj, consts.folder.DOCU); $("#" + node.tId + consts.id.UL).remove(); } else { $("#" + node.tId + consts.id.UL).empty(); } }, setFirstNode: function(setting, parentNode) { var childKey = setting.data.key.children, childLength = parentNode[childKey].length; if ( childLength > 0) { parentNode[childKey][0].isFirstNode = true; } }, setLastNode: function(setting, parentNode) { var childKey = setting.data.key.children, childLength = parentNode[childKey].length; if ( childLength > 0) { parentNode[childKey][childLength - 1].isLastNode = true; } }, removeNode: function(setting, node) { var root = data.getRoot(setting), childKey = setting.data.key.children, parentNode = (node.parentTId) ? node.getParentNode() : root; node.isFirstNode = false; node.isLastNode = false; node.getPreNode = function() {return null;}; node.getNextNode = function() {return null;}; if (!data.getNodeCache(setting, node.tId)) { return; } $("#" + node.tId).remove(); data.removeNodeCache(setting, node); data.removeSelectedNode(setting, node); for (var i = 0, l = parentNode[childKey].length; i < l; i++) { if (parentNode[childKey][i].tId == node.tId) { parentNode[childKey].splice(i, 1); break; } } view.setFirstNode(setting, parentNode); view.setLastNode(setting, parentNode); var tmp_ulObj,tmp_switchObj,tmp_icoObj, childLength = parentNode[childKey].length; //repair nodes old parent if (!setting.data.keep.parent && childLength == 0) { //old parentNode has no child nodes parentNode.isParent = false; parentNode.open = false; tmp_ulObj = $("#" + parentNode.tId + consts.id.UL); tmp_switchObj = $("#" + parentNode.tId + consts.id.SWITCH); tmp_icoObj = $("#" + parentNode.tId + consts.id.ICON); view.replaceSwitchClass(parentNode, tmp_switchObj, consts.folder.DOCU); view.replaceIcoClass(parentNode, tmp_icoObj, consts.folder.DOCU); tmp_ulObj.css("display", "none"); } else if (setting.view.showLine && childLength > 0) { //old parentNode has child nodes var newLast = parentNode[childKey][childLength - 1]; tmp_ulObj = $("#" + newLast.tId + consts.id.UL); tmp_switchObj = $("#" + newLast.tId + consts.id.SWITCH); tmp_icoObj = $("#" + newLast.tId + consts.id.ICON); if (parentNode == root) { if (parentNode[childKey].length == 1) { //node was root, and ztree has only one root after move node view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.ROOT); } else { var tmp_first_switchObj = $("#" + parentNode[childKey][0].tId + consts.id.SWITCH); view.replaceSwitchClass(parentNode[childKey][0], tmp_first_switchObj, consts.line.ROOTS); view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM); } } else { view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM); } tmp_ulObj.removeClass(consts.line.LINE); } }, replaceIcoClass: function(node, obj, newName) { if (!obj || node.isAjaxing) return; var tmpName = obj.attr("class"); if (tmpName == undefined) return; var tmpList = tmpName.split("_"); switch (newName) { case consts.folder.OPEN: case consts.folder.CLOSE: case consts.folder.DOCU: tmpList[tmpList.length-1] = newName; break; } obj.attr("class", tmpList.join("_")); }, replaceSwitchClass: function(node, obj, newName) { if (!obj) return; var tmpName = obj.attr("class"); if (tmpName == undefined) return; var tmpList = tmpName.split("_"); switch (newName) { case consts.line.ROOT: case consts.line.ROOTS: case consts.line.CENTER: case consts.line.BOTTOM: case consts.line.NOLINE: tmpList[0] = view.makeNodeLineClassEx(node) + newName; break; case consts.folder.OPEN: case consts.folder.CLOSE: case consts.folder.DOCU: tmpList[1] = newName; break; } obj.attr("class", tmpList.join("_")); if (newName !== consts.folder.DOCU) { obj.removeAttr("disabled"); } else { obj.attr("disabled", "disabled"); } }, selectNode: function(setting, node, addFlag) { if (!addFlag) { view.cancelPreSelectedNode(setting); } $("#" + node.tId + consts.id.A).addClass(consts.node.CURSELECTED); data.addSelectedNode(setting, node); }, setNodeFontCss: function(setting, treeNode) { var aObj = $("#" + treeNode.tId + consts.id.A), fontCss = view.makeNodeFontCss(setting, treeNode); if (fontCss) { aObj.css(fontCss); } }, setNodeLineIcos: function(setting, node) { if (!node) return; var switchObj = $("#" + node.tId + consts.id.SWITCH), ulObj = $("#" + node.tId + consts.id.UL), icoObj = $("#" + node.tId + consts.id.ICON), ulLine = view.makeUlLineClass(setting, node); if (ulLine.length==0) { ulObj.removeClass(consts.line.LINE); } else { ulObj.addClass(ulLine); } switchObj.attr("class", view.makeNodeLineClass(setting, node)); if (node.isParent) { switchObj.removeAttr("disabled"); } else { switchObj.attr("disabled", "disabled"); } icoObj.removeAttr("style"); icoObj.attr("style", view.makeNodeIcoStyle(setting, node)); icoObj.attr("class", view.makeNodeIcoClass(setting, node)); }, setNodeName: function(setting, node) { var title = data.getNodeTitle(setting, node), nObj = $("#" + node.tId + consts.id.SPAN); nObj.empty(); if (setting.view.nameIsHTML) { nObj.html(data.getNodeName(setting, node)); } else { nObj.text(data.getNodeName(setting, node)); } if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle)) { var aObj = $("#" + node.tId + consts.id.A); aObj.attr("title", !title ? "" : title); } }, setNodeTarget: function(node) { var aObj = $("#" + node.tId + consts.id.A); aObj.attr("target", view.makeNodeTarget(node)); }, setNodeUrl: function(setting, node) { var aObj = $("#" + node.tId + consts.id.A), url = view.makeNodeUrl(setting, node); if (url == null || url.length == 0) { aObj.removeAttr("href"); } else { aObj.attr("href", url); } }, switchNode: function(setting, node) { if (node.open || !tools.canAsync(setting, node)) { view.expandCollapseNode(setting, node, !node.open); } else if (setting.async.enable) { if (!view.asyncNode(setting, node)) { view.expandCollapseNode(setting, node, !node.open); return; } } else if (node) { view.expandCollapseNode(setting, node, !node.open); } } }; // zTree defind $.fn.zTree = { consts : _consts, _z : { tools: tools, view: view, event: event, data: data }, getZTreeObj: function(treeId) { var o = data.getZTreeTools(treeId); return o ? o : null; }, destroy: function(treeId) { if (!!treeId && treeId.length > 0) { view.destroy(data.getSetting(treeId)); } else { for(var s in settings) { view.destroy(settings[s]); } } }, init: function(obj, zSetting, zNodes) { var setting = tools.clone(_setting); $.extend(true, setting, zSetting); setting.treeId = obj.attr("id"); setting.treeObj = obj; setting.treeObj.empty(); settings[setting.treeId] = setting; //For some older browser,(e.g., ie6) if(typeof document.body.style.maxHeight === "undefined") { setting.view.expandSpeed = ""; } data.initRoot(setting); var root = data.getRoot(setting), childKey = setting.data.key.children; zNodes = zNodes ? tools.clone(tools.isArray(zNodes)? zNodes : [zNodes]) : []; if (setting.data.simpleData.enable) { root[childKey] = data.transformTozTreeFormat(setting, zNodes); } else { root[childKey] = zNodes; } data.initCache(setting); event.unbindTree(setting); event.bindTree(setting); event.unbindEvent(setting); event.bindEvent(setting); var zTreeTools = { setting : setting, addNodes : function(parentNode, newNodes, isSilent) { if (!newNodes) return null; if (!parentNode) parentNode = null; if (parentNode && !parentNode.isParent && setting.data.keep.leaf) return null; var xNewNodes = tools.clone(tools.isArray(newNodes)? newNodes: [newNodes]); function addCallback() { view.addNodes(setting, parentNode, xNewNodes, (isSilent==true)); } if (tools.canAsync(setting, parentNode)) { view.asyncNode(setting, parentNode, isSilent, addCallback); } else { addCallback(); } return xNewNodes; }, cancelSelectedNode : function(node) { view.cancelPreSelectedNode(this.setting, node); }, destroy : function() { view.destroy(this.setting); }, expandAll : function(expandFlag) { expandFlag = !!expandFlag; view.expandCollapseSonNode(this.setting, null, expandFlag, true); return expandFlag; }, expandNode : function(node, expandFlag, sonSign, focus, callbackFlag) { if (!node || !node.isParent) return null; if (expandFlag !== true && expandFlag !== false) { expandFlag = !node.open; } callbackFlag = !!callbackFlag; if (callbackFlag && expandFlag && (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false)) { return null; } else if (callbackFlag && !expandFlag && (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false)) { return null; } if (expandFlag && node.parentTId) { view.expandCollapseParentNode(this.setting, node.getParentNode(), expandFlag, false); } if (expandFlag === node.open && !sonSign) { return null; } data.getRoot(setting).expandTriggerFlag = callbackFlag; if (sonSign) { view.expandCollapseSonNode(this.setting, node, expandFlag, true, function() { if (focus !== false) {try{$("#" + node.tId).focus().blur();}catch(e){}} }); } else { node.open = !expandFlag; view.switchNode(this.setting, node); if (focus !== false) {try{$("#" + node.tId).focus().blur();}catch(e){}} } return expandFlag; }, getNodes : function() { return data.getNodes(this.setting); }, getNodeByParam : function(key, value, parentNode) { if (!key) return null; return data.getNodeByParam(this.setting, parentNode?parentNode[this.setting.data.key.children]:data.getNodes(this.setting), key, value); }, getNodeByTId : function(tId) { return data.getNodeCache(this.setting, tId); }, getNodesByParam : function(key, value, parentNode) { if (!key) return null; return data.getNodesByParam(this.setting, parentNode?parentNode[this.setting.data.key.children]:data.getNodes(this.setting), key, value); }, getNodesByParamFuzzy : function(key, value, parentNode) { if (!key) return null; return data.getNodesByParamFuzzy(this.setting, parentNode?parentNode[this.setting.data.key.children]:data.getNodes(this.setting), key, value); }, getNodesByFilter: function(filter, isSingle, parentNode, invokeParam) { isSingle = !!isSingle; if (!filter || (typeof filter != "function")) return (isSingle ? null : []); return data.getNodesByFilter(this.setting, parentNode?parentNode[this.setting.data.key.children]:data.getNodes(this.setting), filter, isSingle, invokeParam); }, getNodeIndex : function(node) { if (!node) return null; var childKey = setting.data.key.children, parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(this.setting); for (var i=0, l = parentNode[childKey].length; i < l; i++) { if (parentNode[childKey][i] == node) return i; } return -1; }, getSelectedNodes : function() { var r = [], list = data.getRoot(this.setting).curSelectedList; for (var i=0, l=list.length; i 0) { view.createNodes(setting, 0, root[childKey]); } else if (setting.async.enable && setting.async.url && setting.async.url !== '') { view.asyncNode(setting); } return zTreeTools; } }; var zt = $.fn.zTree, consts = zt.consts; })(jQuery); /* * JQuery zTree excheck 3.5.12 * http://zTree.me/ * * Copyright (c) 2010 Hunter.z * * Licensed same as jquery - MIT License * http://www.opensource.org/licenses/mit-license.php * * email: hunter.z@263.net * Date: 2013-03-11 */ (function($){ //default consts of excheck var _consts = { event: { CHECK: "ztree_check" }, id: { CHECK: "_check" }, checkbox: { STYLE: "checkbox", DEFAULT: "chk", DISABLED: "disable", FALSE: "false", TRUE: "true", FULL: "full", PART: "part", FOCUS: "focus" }, radio: { STYLE: "radio", TYPE_ALL: "all", TYPE_LEVEL: "level" } }, //default setting of excheck _setting = { check: { enable: false, autoCheckTrigger: false, chkStyle: _consts.checkbox.STYLE, nocheckInherit: false, chkDisabledInherit: false, radioType: _consts.radio.TYPE_LEVEL, chkboxType: { "Y": "ps", "N": "ps" } }, data: { key: { checked: "checked" } }, callback: { beforeCheck:null, onCheck:null } }, //default root of excheck _initRoot = function (setting) { var r = data.getRoot(setting); r.radioCheckedList = []; }, //default cache of excheck _initCache = function(treeId) {}, //default bind event of excheck _bindEvent = function(setting) { var o = setting.treeObj, c = consts.event; o.bind(c.CHECK, function (event, srcEvent, treeId, node) { tools.apply(setting.callback.onCheck, [!!srcEvent?srcEvent : event, treeId, node]); }); }, _unbindEvent = function(setting) { var o = setting.treeObj, c = consts.event; o.unbind(c.CHECK); }, //default event proxy of excheck _eventProxy = function(e) { var target = e.target, setting = data.getSetting(e.data.treeId), tId = "", node = null, nodeEventType = "", treeEventType = "", nodeEventCallback = null, treeEventCallback = null; if (tools.eqs(e.type, "mouseover")) { if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) { tId = target.parentNode.id; nodeEventType = "mouseoverCheck"; } } else if (tools.eqs(e.type, "mouseout")) { if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) { tId = target.parentNode.id; nodeEventType = "mouseoutCheck"; } } else if (tools.eqs(e.type, "click")) { if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) { tId = target.parentNode.id; nodeEventType = "checkNode"; } } if (tId.length>0) { node = data.getNodeCache(setting, tId); switch (nodeEventType) { case "checkNode" : nodeEventCallback = _handler.onCheckNode; break; case "mouseoverCheck" : nodeEventCallback = _handler.onMouseoverCheck; break; case "mouseoutCheck" : nodeEventCallback = _handler.onMouseoutCheck; break; } } var proxyResult = { stop: false, node: node, nodeEventType: nodeEventType, nodeEventCallback: nodeEventCallback, treeEventType: treeEventType, treeEventCallback: treeEventCallback }; return proxyResult }, //default init node of excheck _initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { if (!n) return; var checkedKey = setting.data.key.checked; if (typeof n[checkedKey] == "string") n[checkedKey] = tools.eqs(n[checkedKey], "true"); n[checkedKey] = !!n[checkedKey]; n.checkedOld = n[checkedKey]; if (typeof n.nocheck == "string") n.nocheck = tools.eqs(n.nocheck, "true"); n.nocheck = !!n.nocheck || (setting.check.nocheckInherit && parentNode && !!parentNode.nocheck); if (typeof n.chkDisabled == "string") n.chkDisabled = tools.eqs(n.chkDisabled, "true"); n.chkDisabled = !!n.chkDisabled || (setting.check.chkDisabledInherit && parentNode && !!parentNode.chkDisabled); if (typeof n.halfCheck == "string") n.halfCheck = tools.eqs(n.halfCheck, "true"); n.halfCheck = !!n.halfCheck; n.check_Child_State = -1; n.check_Focus = false; n.getCheckStatus = function() {return data.getCheckStatus(setting, n);}; }, //add dom for check _beforeA = function(setting, node, html) { var checkedKey = setting.data.key.checked; if (setting.check.enable) { data.makeChkFlag(setting, node); if (setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL && node[checkedKey] ) { var r = data.getRoot(setting); r.radioCheckedList.push(node); } html.push(""); } }, //update zTreeObj, add method of check _zTreeTools = function(setting, zTreeTools) { zTreeTools.checkNode = function(node, checked, checkTypeFlag, callbackFlag) { var checkedKey = this.setting.data.key.checked; if (node.chkDisabled === true) return; if (checked !== true && checked !== false) { checked = !node[checkedKey]; } callbackFlag = !!callbackFlag; if (node[checkedKey] === checked && !checkTypeFlag) { return; } else if (callbackFlag && tools.apply(this.setting.callback.beforeCheck, [this.setting.treeId, node], true) == false) { return; } if (tools.uCanDo(this.setting) && this.setting.check.enable && node.nocheck !== true) { node[checkedKey] = checked; var checkObj = $("#" + node.tId + consts.id.CHECK); if (checkTypeFlag || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node); view.setChkClass(this.setting, checkObj, node); view.repairParentChkClassWithSelf(this.setting, node); if (callbackFlag) { setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); } } } zTreeTools.checkAllNodes = function(checked) { view.repairAllChk(this.setting, !!checked); } zTreeTools.getCheckedNodes = function(checked) { var childKey = this.setting.data.key.children; checked = (checked !== false); return data.getTreeCheckedNodes(this.setting, data.getRoot(setting)[childKey], checked); } zTreeTools.getChangeCheckedNodes = function() { var childKey = this.setting.data.key.children; return data.getTreeChangeCheckedNodes(this.setting, data.getRoot(setting)[childKey]); } zTreeTools.setChkDisabled = function(node, disabled, inheritParent, inheritChildren) { disabled = !!disabled; inheritParent = !!inheritParent; inheritChildren = !!inheritChildren; view.repairSonChkDisabled(this.setting, node, disabled, inheritChildren); view.repairParentChkDisabled(this.setting, node.getParentNode(), disabled, inheritParent); } var _updateNode = zTreeTools.updateNode; zTreeTools.updateNode = function(node, checkTypeFlag) { if (_updateNode) _updateNode.apply(zTreeTools, arguments); if (!node || !this.setting.check.enable) return; var nObj = $("#" + node.tId); if (nObj.get(0) && tools.uCanDo(this.setting)) { var checkObj = $("#" + node.tId + consts.id.CHECK); if (checkTypeFlag == true || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node); view.setChkClass(this.setting, checkObj, node); view.repairParentChkClassWithSelf(this.setting, node); } } }, //method of operate data _data = { getRadioCheckedList: function(setting) { var checkedList = data.getRoot(setting).radioCheckedList; for (var i=0, j=checkedList.length; i -1 && node.check_Child_State < 2) : (node.check_Child_State > 0))) }; return r; }, getTreeCheckedNodes: function(setting, nodes, checked, results) { if (!nodes) return []; var childKey = setting.data.key.children, checkedKey = setting.data.key.checked, onlyOne = (checked && setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL); results = !results ? [] : results; for (var i = 0, l = nodes.length; i < l; i++) { if (nodes[i].nocheck !== true && nodes[i].chkDisabled !== true && nodes[i][checkedKey] == checked) { results.push(nodes[i]); if(onlyOne) { break; } } data.getTreeCheckedNodes(setting, nodes[i][childKey], checked, results); if(onlyOne && results.length > 0) { break; } } return results; }, getTreeChangeCheckedNodes: function(setting, nodes, results) { if (!nodes) return []; var childKey = setting.data.key.children, checkedKey = setting.data.key.checked; results = !results ? [] : results; for (var i = 0, l = nodes.length; i < l; i++) { if (nodes[i].nocheck !== true && nodes[i].chkDisabled !== true && nodes[i][checkedKey] != nodes[i].checkedOld) { results.push(nodes[i]); } data.getTreeChangeCheckedNodes(setting, nodes[i][childKey], results); } return results; }, makeChkFlag: function(setting, node) { if (!node) return; var childKey = setting.data.key.children, checkedKey = setting.data.key.checked, chkFlag = -1; if (node[childKey]) { for (var i = 0, l = node[childKey].length; i < l; i++) { var cNode = node[childKey][i]; var tmp = -1; if (setting.check.chkStyle == consts.radio.STYLE) { if (cNode.nocheck === true || cNode.chkDisabled === true) { tmp = cNode.check_Child_State; } else if (cNode.halfCheck === true) { tmp = 2; } else if (cNode[checkedKey]) { tmp = 2; } else { tmp = cNode.check_Child_State > 0 ? 2:0; } if (tmp == 2) { chkFlag = 2; break; } else if (tmp == 0){ chkFlag = 0; } } else if (setting.check.chkStyle == consts.checkbox.STYLE) { if (cNode.nocheck === true || cNode.chkDisabled === true) { tmp = cNode.check_Child_State; } else if (cNode.halfCheck === true) { tmp = 1; } else if (cNode[checkedKey] ) { tmp = (cNode.check_Child_State === -1 || cNode.check_Child_State === 2) ? 2 : 1; } else { tmp = (cNode.check_Child_State > 0) ? 1 : 0; } if (tmp === 1) { chkFlag = 1; break; } else if (tmp === 2 && chkFlag > -1 && i > 0 && tmp !== chkFlag) { chkFlag = 1; break; } else if (chkFlag === 2 && tmp > -1 && tmp < 2) { chkFlag = 1; break; } else if (tmp > -1) { chkFlag = tmp; } } } } node.check_Child_State = chkFlag; } }, //method of event proxy _event = { }, //method of event handler _handler = { onCheckNode: function (event, node) { if (node.chkDisabled === true) return false; var setting = data.getSetting(event.data.treeId), checkedKey = setting.data.key.checked; if (tools.apply(setting.callback.beforeCheck, [setting.treeId, node], true) == false) return true; node[checkedKey] = !node[checkedKey]; view.checkNodeRelation(setting, node); var checkObj = $("#" + node.tId + consts.id.CHECK); view.setChkClass(setting, checkObj, node); view.repairParentChkClassWithSelf(setting, node); setting.treeObj.trigger(consts.event.CHECK, [event, setting.treeId, node]); return true; }, onMouseoverCheck: function(event, node) { if (node.chkDisabled === true) return false; var setting = data.getSetting(event.data.treeId), checkObj = $("#" + node.tId + consts.id.CHECK); node.check_Focus = true; view.setChkClass(setting, checkObj, node); return true; }, onMouseoutCheck: function(event, node) { if (node.chkDisabled === true) return false; var setting = data.getSetting(event.data.treeId), checkObj = $("#" + node.tId + consts.id.CHECK); node.check_Focus = false; view.setChkClass(setting, checkObj, node); return true; } }, //method of tools for zTree _tools = { }, //method of operate ztree dom _view = { checkNodeRelation: function(setting, node) { var pNode, i, l, childKey = setting.data.key.children, checkedKey = setting.data.key.checked, r = consts.radio; if (setting.check.chkStyle == r.STYLE) { var checkedList = data.getRadioCheckedList(setting); if (node[checkedKey]) { if (setting.check.radioType == r.TYPE_ALL) { for (i = checkedList.length-1; i >= 0; i--) { pNode = checkedList[i]; pNode[checkedKey] = false; checkedList.splice(i, 1); view.setChkClass(setting, $("#" + pNode.tId + consts.id.CHECK), pNode); if (pNode.parentTId != node.parentTId) { view.repairParentChkClassWithSelf(setting, pNode); } } checkedList.push(node); } else { var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting); for (i = 0, l = parentNode[childKey].length; i < l; i++) { pNode = parentNode[childKey][i]; if (pNode[checkedKey] && pNode != node) { pNode[checkedKey] = false; view.setChkClass(setting, $("#" + pNode.tId + consts.id.CHECK), pNode); } } } } else if (setting.check.radioType == r.TYPE_ALL) { for (i = 0, l = checkedList.length; i < l; i++) { if (node == checkedList[i]) { checkedList.splice(i, 1); break; } } } } else { if (node[checkedKey] && (!node[childKey] || node[childKey].length==0 || setting.check.chkboxType.Y.indexOf("s") > -1)) { view.setSonNodeCheckBox(setting, node, true); } if (!node[checkedKey] && (!node[childKey] || node[childKey].length==0 || setting.check.chkboxType.N.indexOf("s") > -1)) { view.setSonNodeCheckBox(setting, node, false); } if (node[checkedKey] && setting.check.chkboxType.Y.indexOf("p") > -1) { view.setParentNodeCheckBox(setting, node, true); } if (!node[checkedKey] && setting.check.chkboxType.N.indexOf("p") > -1) { view.setParentNodeCheckBox(setting, node, false); } } }, makeChkClass: function(setting, node) { var checkedKey = setting.data.key.checked, c = consts.checkbox, r = consts.radio, fullStyle = ""; if (node.chkDisabled === true) { fullStyle = c.DISABLED; } else if (node.halfCheck) { fullStyle = c.PART; } else if (setting.check.chkStyle == r.STYLE) { fullStyle = (node.check_Child_State < 1)? c.FULL:c.PART; } else { fullStyle = node[checkedKey] ? ((node.check_Child_State === 2 || node.check_Child_State === -1) ? c.FULL:c.PART) : ((node.check_Child_State < 1)? c.FULL:c.PART); } var chkName = setting.check.chkStyle + "_" + (node[checkedKey] ? c.TRUE : c.FALSE) + "_" + fullStyle; chkName = (node.check_Focus && node.chkDisabled !== true) ? chkName + "_" + c.FOCUS : chkName; return consts.className.BUTTON + " " + c.DEFAULT + " " + chkName; }, repairAllChk: function(setting, checked) { if (setting.check.enable && setting.check.chkStyle === consts.checkbox.STYLE) { var checkedKey = setting.data.key.checked, childKey = setting.data.key.children, root = data.getRoot(setting); for (var i = 0, l = root[childKey].length; i 0) { view.repairParentChkClass(setting, node[childKey][0]); } else { view.repairParentChkClass(setting, node); } }, repairSonChkDisabled: function(setting, node, chkDisabled, inherit) { if (!node) return; var childKey = setting.data.key.children; if (node.chkDisabled != chkDisabled) { node.chkDisabled = chkDisabled; } view.repairChkClass(setting, node); if (node[childKey] && inherit) { for (var i = 0, l = node[childKey].length; i < l; i++) { var sNode = node[childKey][i]; view.repairSonChkDisabled(setting, sNode, chkDisabled, inherit); } } }, repairParentChkDisabled: function(setting, node, chkDisabled, inherit) { if (!node) return; if (node.chkDisabled != chkDisabled && inherit) { node.chkDisabled = chkDisabled; } view.repairChkClass(setting, node); view.repairParentChkDisabled(setting, node.getParentNode(), chkDisabled, inherit); }, setChkClass: function(setting, obj, node) { if (!obj) return; if (node.nocheck === true) { obj.hide(); } else { obj.show(); } obj.removeClass(); obj.addClass(view.makeChkClass(setting, node)); }, setParentNodeCheckBox: function(setting, node, value, srcNode) { var childKey = setting.data.key.children, checkedKey = setting.data.key.checked, checkObj = $("#" + node.tId + consts.id.CHECK); if (!srcNode) srcNode = node; data.makeChkFlag(setting, node); if (node.nocheck !== true && node.chkDisabled !== true) { node[checkedKey] = value; view.setChkClass(setting, checkObj, node); if (setting.check.autoCheckTrigger && node != srcNode) { setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); } } if (node.parentTId) { var pSign = true; if (!value) { var pNodes = node.getParentNode()[childKey]; for (var i = 0, l = pNodes.length; i < l; i++) { if ((pNodes[i].nocheck !== true && pNodes[i].chkDisabled !== true && pNodes[i][checkedKey]) || ((pNodes[i].nocheck === true || pNodes[i].chkDisabled === true) && pNodes[i].check_Child_State > 0)) { pSign = false; break; } } } if (pSign) { view.setParentNodeCheckBox(setting, node.getParentNode(), value, srcNode); } } }, setSonNodeCheckBox: function(setting, node, value, srcNode) { if (!node) return; var childKey = setting.data.key.children, checkedKey = setting.data.key.checked, checkObj = $("#" + node.tId + consts.id.CHECK); if (!srcNode) srcNode = node; var hasDisable = false; if (node[childKey]) { for (var i = 0, l = node[childKey].length; i < l && node.chkDisabled !== true; i++) { var sNode = node[childKey][i]; view.setSonNodeCheckBox(setting, sNode, value, srcNode); if (sNode.chkDisabled === true) hasDisable = true; } } if (node != data.getRoot(setting) && node.chkDisabled !== true) { if (hasDisable && node.nocheck !== true) { data.makeChkFlag(setting, node); } if (node.nocheck !== true && node.chkDisabled !== true) { node[checkedKey] = value; if (!hasDisable) node.check_Child_State = (node[childKey] && node[childKey].length > 0) ? (value ? 2 : 0) : -1; } else { node.check_Child_State = -1; } view.setChkClass(setting, checkObj, node); if (setting.check.autoCheckTrigger && node != srcNode && node.nocheck !== true && node.chkDisabled !== true) { setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); } } } }, _z = { tools: _tools, view: _view, event: _event, data: _data }; $.extend(true, $.fn.zTree.consts, _consts); $.extend(true, $.fn.zTree._z, _z); var zt = $.fn.zTree, tools = zt._z.tools, consts = zt.consts, view = zt._z.view, data = zt._z.data, event = zt._z.event; data.exSetting(_setting); data.addInitBind(_bindEvent); data.addInitUnBind(_unbindEvent); data.addInitCache(_initCache); data.addInitNode(_initNode); data.addInitProxy(_eventProxy); data.addInitRoot(_initRoot); data.addBeforeA(_beforeA); data.addZTreeTools(_zTreeTools); var _createNodes = view.createNodes; view.createNodes = function(setting, level, nodes, parentNode) { if (_createNodes) _createNodes.apply(view, arguments); if (!nodes) return; view.repairParentChkClassWithSelf(setting, parentNode); } var _removeNode = view.removeNode; view.removeNode = function(setting, node) { var parentNode = node.getParentNode(); if (_removeNode) _removeNode.apply(view, arguments); if (!node || !parentNode) return; view.repairChkClass(setting, parentNode); view.repairParentChkClass(setting, parentNode); } var _appendNodes = view.appendNodes; view.appendNodes = function(setting, level, nodes, parentNode, initFlag, openFlag) { var html = ""; if (_appendNodes) { html = _appendNodes.apply(view, arguments); } if (parentNode) { data.makeChkFlag(setting, parentNode); } return html; } })(jQuery); /* * JQuery zTree exedit 3.5.12 * http://zTree.me/ * * Copyright (c) 2010 Hunter.z * * Licensed same as jquery - MIT License * http://www.opensource.org/licenses/mit-license.php * * email: hunter.z@263.net * Date: 2013-03-11 */ (function($){ //default consts of exedit var _consts = { event: { DRAG: "ztree_drag", DROP: "ztree_drop", REMOVE: "ztree_remove", RENAME: "ztree_rename" }, id: { EDIT: "_edit", INPUT: "_input", REMOVE: "_remove" }, move: { TYPE_INNER: "inner", TYPE_PREV: "prev", TYPE_NEXT: "next" }, node: { CURSELECTED_EDIT: "curSelectedNode_Edit", TMPTARGET_TREE: "tmpTargetzTree", TMPTARGET_NODE: "tmpTargetNode" } }, //default setting of exedit _setting = { edit: { enable: false, editNameSelectAll: false, showRemoveBtn: true, showRenameBtn: true, removeTitle: "remove", renameTitle: "rename", drag: { autoExpandTrigger: false, isCopy: true, isMove: true, prev: true, next: true, inner: true, minMoveSize: 5, borderMax: 10, borderMin: -5, maxShowNodeNum: 5, autoOpenTime: 500 } }, view: { addHoverDom: null, removeHoverDom: null }, callback: { beforeDrag:null, beforeDragOpen:null, beforeDrop:null, beforeEditName:null, beforeRename:null, onDrag:null, onDrop:null, onRename:null } }, //default root of exedit _initRoot = function (setting) { var r = data.getRoot(setting); r.curEditNode = null; r.curEditInput = null; r.curHoverNode = null; r.dragFlag = 0; r.dragNodeShowBefore = []; r.dragMaskList = new Array(); r.showHoverDom = true; }, //default cache of exedit _initCache = function(treeId) {}, //default bind event of exedit _bindEvent = function(setting) { var o = setting.treeObj; var c = consts.event; o.bind(c.RENAME, function (event, treeId, treeNode) { tools.apply(setting.callback.onRename, [event, treeId, treeNode]); }); o.bind(c.REMOVE, function (event, treeId, treeNode) { tools.apply(setting.callback.onRemove, [event, treeId, treeNode]); }); o.bind(c.DRAG, function (event, srcEvent, treeId, treeNodes) { tools.apply(setting.callback.onDrag, [srcEvent, treeId, treeNodes]); }); o.bind(c.DROP, function (event, srcEvent, treeId, treeNodes, targetNode, moveType, isCopy) { tools.apply(setting.callback.onDrop, [srcEvent, treeId, treeNodes, targetNode, moveType, isCopy]); }); }, _unbindEvent = function(setting) { var o = setting.treeObj; var c = consts.event; o.unbind(c.RENAME); o.unbind(c.REMOVE); o.unbind(c.DRAG); o.unbind(c.DROP); }, //default event proxy of exedit _eventProxy = function(e) { var target = e.target, setting = data.getSetting(e.data.treeId), relatedTarget = e.relatedTarget, tId = "", node = null, nodeEventType = "", treeEventType = "", nodeEventCallback = null, treeEventCallback = null, tmp = null; if (tools.eqs(e.type, "mouseover")) { tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) { tId = tmp.parentNode.id; nodeEventType = "hoverOverNode"; } } else if (tools.eqs(e.type, "mouseout")) { tmp = tools.getMDom(setting, relatedTarget, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (!tmp) { tId = "remove"; nodeEventType = "hoverOutNode"; } } else if (tools.eqs(e.type, "mousedown")) { tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) { tId = tmp.parentNode.id; nodeEventType = "mousedownNode"; } } if (tId.length>0) { node = data.getNodeCache(setting, tId); switch (nodeEventType) { case "mousedownNode" : nodeEventCallback = _handler.onMousedownNode; break; case "hoverOverNode" : nodeEventCallback = _handler.onHoverOverNode; break; case "hoverOutNode" : nodeEventCallback = _handler.onHoverOutNode; break; } } var proxyResult = { stop: false, node: node, nodeEventType: nodeEventType, nodeEventCallback: nodeEventCallback, treeEventType: treeEventType, treeEventCallback: treeEventCallback }; return proxyResult }, //default init node of exedit _initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { if (!n) return; n.isHover = false; n.editNameFlag = false; }, //update zTreeObj, add method of edit _zTreeTools = function(setting, zTreeTools) { zTreeTools.cancelEditName = function(newName) { var root = data.getRoot(setting), nameKey = setting.data.key.name, node = root.curEditNode; if (!root.curEditNode) return; view.cancelCurEditNode(setting, newName?newName:node[nameKey]); } zTreeTools.copyNode = function(targetNode, node, moveType, isSilent) { if (!node) return null; if (targetNode && !targetNode.isParent && setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) return null; var newNode = tools.clone(node); if (!targetNode) { targetNode = null; moveType = consts.move.TYPE_INNER; } if (moveType == consts.move.TYPE_INNER) { function copyCallback() { view.addNodes(setting, targetNode, [newNode], isSilent); } if (tools.canAsync(setting, targetNode)) { view.asyncNode(setting, targetNode, isSilent, copyCallback); } else { copyCallback(); } } else { view.addNodes(setting, targetNode.parentNode, [newNode], isSilent); view.moveNode(setting, targetNode, newNode, moveType, false, isSilent); } return newNode; } zTreeTools.editName = function(node) { if (!node || !node.tId || node !== data.getNodeCache(setting, node.tId)) return; if (node.parentTId) view.expandCollapseParentNode(setting, node.getParentNode(), true); view.editNode(setting, node) } zTreeTools.moveNode = function(targetNode, node, moveType, isSilent) { if (!node) return node; if (targetNode && !targetNode.isParent && setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) { return null; } else if (targetNode && ((node.parentTId == targetNode.tId && moveType == consts.move.TYPE_INNER) || $("#" + node.tId).find("#" + targetNode.tId).length > 0)) { return null; } else if (!targetNode) { targetNode = null; } function moveCallback() { view.moveNode(setting, targetNode, node, moveType, false, isSilent); } if (tools.canAsync(setting, targetNode) && moveType === consts.move.TYPE_INNER) { view.asyncNode(setting, targetNode, isSilent, moveCallback); } else { moveCallback(); } return node; } zTreeTools.setEditable = function(editable) { setting.edit.enable = editable; return this.refresh(); } }, //method of operate data _data = { setSonNodeLevel: function(setting, parentNode, node) { if (!node) return; var childKey = setting.data.key.children; node.level = (parentNode)? parentNode.level + 1 : 0; if (!node[childKey]) return; for (var i = 0, l = node[childKey].length; i < l; i++) { if (node[childKey][i]) data.setSonNodeLevel(setting, node, node[childKey][i]); } } }, //method of event proxy _event = { }, //method of event handler _handler = { onHoverOverNode: function(event, node) { var setting = data.getSetting(event.data.treeId), root = data.getRoot(setting); if (root.curHoverNode != node) { _handler.onHoverOutNode(event); } root.curHoverNode = node; view.addHoverDom(setting, node); }, onHoverOutNode: function(event, node) { var setting = data.getSetting(event.data.treeId), root = data.getRoot(setting); if (root.curHoverNode && !data.isSelectedNode(setting, root.curHoverNode)) { view.removeTreeDom(setting, root.curHoverNode); root.curHoverNode = null; } }, onMousedownNode: function(eventMouseDown, _node) { var i,l, setting = data.getSetting(eventMouseDown.data.treeId), root = data.getRoot(setting); //right click can't drag & drop if (eventMouseDown.button == 2 || !setting.edit.enable || (!setting.edit.drag.isCopy && !setting.edit.drag.isMove)) return true; //input of edit node name can't drag & drop var target = eventMouseDown.target, _nodes = data.getRoot(setting).curSelectedList, nodes = []; if (!data.isSelectedNode(setting, _node)) { nodes = [_node]; } else { for (i=0, l=_nodes.length; i1) { var pNodes = nodes[0].parentTId ? nodes[0].getParentNode()[childKey] : data.getNodes(setting); tmpNodes = []; for (i=0, l=pNodes.length; i -1 && (lastIndex+1) !== i) { isOrder = false; } tmpNodes.push(pNodes[i]); lastIndex = i; } if (nodes.length === tmpNodes.length) { nodes = tmpNodes; break; } } } if (isOrder) { preNode = nodes[0].getPreNode(); nextNode = nodes[nodes.length-1].getNextNode(); } //set node in selected curNode = $("
        "); for (i=0, l=nodes.length; i0); view.removeTreeDom(setting, tmpNode); tmpDom = $("
      • "); tmpDom.append($("#" + tmpNode.tId + consts.id.A).clone()); tmpDom.css("padding", "0"); tmpDom.children("#" + tmpNode.tId + consts.id.A).removeClass(consts.node.CURSELECTED); curNode.append(tmpDom); if (i == setting.edit.drag.maxShowNodeNum-1) { tmpDom = $("
      • ...
      • "); curNode.append(tmpDom); break; } } curNode.attr("id", nodes[0].tId + consts.id.UL + "_tmp"); curNode.addClass(setting.treeObj.attr("class")); curNode.appendTo("body"); tmpArrow = $(""); tmpArrow.attr("id", "zTreeMove_arrow_tmp"); tmpArrow.appendTo("body"); setting.treeObj.trigger(consts.event.DRAG, [event, setting.treeId, nodes]); } if (root.dragFlag == 1) { if (tmpTarget && tmpArrow.attr("id") == event.target.id && tmpTargetNodeId && (event.clientX + doc.scrollLeft()+2) > ($("#" + tmpTargetNodeId + consts.id.A, tmpTarget).offset().left)) { var xT = $("#" + tmpTargetNodeId + consts.id.A, tmpTarget); event.target = (xT.length > 0) ? xT.get(0) : event.target; } else if (tmpTarget) { tmpTarget.removeClass(consts.node.TMPTARGET_TREE); if (tmpTargetNodeId) $("#" + tmpTargetNodeId + consts.id.A, tmpTarget).removeClass(consts.node.TMPTARGET_NODE + "_" + consts.move.TYPE_PREV) .removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_NEXT).removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_INNER); } tmpTarget = null; tmpTargetNodeId = null; //judge drag & drop in multi ztree isOtherTree = false; targetSetting = setting; var settings = data.getSettings(); for (var s in settings) { if (settings[s].treeId && settings[s].edit.enable && settings[s].treeId != setting.treeId && (event.target.id == settings[s].treeId || $(event.target).parents("#" + settings[s].treeId).length>0)) { isOtherTree = true; targetSetting = settings[s]; } } var docScrollTop = doc.scrollTop(), docScrollLeft = doc.scrollLeft(), treeOffset = targetSetting.treeObj.offset(), scrollHeight = targetSetting.treeObj.get(0).scrollHeight, scrollWidth = targetSetting.treeObj.get(0).scrollWidth, dTop = (event.clientY + docScrollTop - treeOffset.top), dBottom = (targetSetting.treeObj.height() + treeOffset.top - event.clientY - docScrollTop), dLeft = (event.clientX + docScrollLeft - treeOffset.left), dRight = (targetSetting.treeObj.width() + treeOffset.left - event.clientX - docScrollLeft), isTop = (dTop < setting.edit.drag.borderMax && dTop > setting.edit.drag.borderMin), isBottom = (dBottom < setting.edit.drag.borderMax && dBottom > setting.edit.drag.borderMin), isLeft = (dLeft < setting.edit.drag.borderMax && dLeft > setting.edit.drag.borderMin), isRight = (dRight < setting.edit.drag.borderMax && dRight > setting.edit.drag.borderMin), isTreeInner = dTop > setting.edit.drag.borderMin && dBottom > setting.edit.drag.borderMin && dLeft > setting.edit.drag.borderMin && dRight > setting.edit.drag.borderMin, isTreeTop = (isTop && targetSetting.treeObj.scrollTop() <= 0), isTreeBottom = (isBottom && (targetSetting.treeObj.scrollTop() + targetSetting.treeObj.height()+10) >= scrollHeight), isTreeLeft = (isLeft && targetSetting.treeObj.scrollLeft() <= 0), isTreeRight = (isRight && (targetSetting.treeObj.scrollLeft() + targetSetting.treeObj.width()+10) >= scrollWidth); if (event.target.id && targetSetting.treeObj.find("#" + event.target.id).length > 0) { //get node
      • dom var targetObj = event.target; while (targetObj && targetObj.tagName && !tools.eqs(targetObj.tagName, "li") && targetObj.id != targetSetting.treeId) { targetObj = targetObj.parentNode; } var canMove = true; //don't move to self or children of self for (i=0, l=nodes.length; i 0) { canMove = false; break; } } if (canMove) { if (event.target.id && (event.target.id == (targetObj.id + consts.id.A) || $(event.target).parents("#" + targetObj.id + consts.id.A).length > 0)) { tmpTarget = $(targetObj); tmpTargetNodeId = targetObj.id; } } } //the mouse must be in zTree tmpNode = nodes[0]; if (isTreeInner && (event.target.id == targetSetting.treeId || $(event.target).parents("#" + targetSetting.treeId).length>0)) { //judge mouse move in root of ztree if (!tmpTarget && (event.target.id == targetSetting.treeId || isTreeTop || isTreeBottom || isTreeLeft || isTreeRight) && (isOtherTree || (!isOtherTree && tmpNode.parentTId))) { tmpTarget = targetSetting.treeObj; } //auto scroll top if (isTop) { targetSetting.treeObj.scrollTop(targetSetting.treeObj.scrollTop()-10); } else if (isBottom) { targetSetting.treeObj.scrollTop(targetSetting.treeObj.scrollTop()+10); } if (isLeft) { targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()-10); } else if (isRight) { targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()+10); } //auto scroll left if (tmpTarget && tmpTarget != targetSetting.treeObj && tmpTarget.offset().left < targetSetting.treeObj.offset().left) { targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()+ tmpTarget.offset().left - targetSetting.treeObj.offset().left); } } curNode.css({ "top": (event.clientY + docScrollTop + 3) + "px", "left": (event.clientX + docScrollLeft + 3) + "px" }); var dX = 0; var dY = 0; if (tmpTarget && tmpTarget.attr("id")!=targetSetting.treeId) { var tmpTargetNode = tmpTargetNodeId == null ? null: data.getNodeCache(targetSetting, tmpTargetNodeId), isCopy = (event.ctrlKey && setting.edit.drag.isMove && setting.edit.drag.isCopy) || (!setting.edit.drag.isMove && setting.edit.drag.isCopy), isPrev = !!(preNode && tmpTargetNodeId === preNode.tId), isNext = !!(nextNode && tmpTargetNodeId === nextNode.tId), isInner = (tmpNode.parentTId && tmpNode.parentTId == tmpTargetNodeId), canPrev = (isCopy || !isNext) && tools.apply(targetSetting.edit.drag.prev, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.prev), canNext = (isCopy || !isPrev) && tools.apply(targetSetting.edit.drag.next, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.next), canInner = (isCopy || !isInner) && !(targetSetting.data.keep.leaf && !tmpTargetNode.isParent) && tools.apply(targetSetting.edit.drag.inner, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.inner); if (!canPrev && !canNext && !canInner) { tmpTarget = null; tmpTargetNodeId = ""; moveType = consts.move.TYPE_INNER; tmpArrow.css({ "display":"none" }); if (window.zTreeMoveTimer) { clearTimeout(window.zTreeMoveTimer); window.zTreeMoveTargetNodeTId = null } } else { var tmpTargetA = $("#" + tmpTargetNodeId + consts.id.A, tmpTarget), tmpNextA = tmpTargetNode.isLastNode ? null : $("#" + tmpTargetNode.getNextNode().tId + consts.id.A, tmpTarget.next()), tmpTop = tmpTargetA.offset().top, tmpLeft = tmpTargetA.offset().left, prevPercent = canPrev ? (canInner ? 0.25 : (canNext ? 0.5 : 1) ) : -1, nextPercent = canNext ? (canInner ? 0.75 : (canPrev ? 0.5 : 0) ) : -1, dY_percent = (event.clientY + docScrollTop - tmpTop)/tmpTargetA.height(); if ((prevPercent==1 ||dY_percent<=prevPercent && dY_percent>=-.2) && canPrev) { dX = 1 - tmpArrow.width(); dY = tmpTop - tmpArrow.height()/2; moveType = consts.move.TYPE_PREV; } else if ((nextPercent==0 || dY_percent>=nextPercent && dY_percent<=1.2) && canNext) { dX = 1 - tmpArrow.width(); dY = (tmpNextA == null || (tmpTargetNode.isParent && tmpTargetNode.open)) ? (tmpTop + tmpTargetA.height() - tmpArrow.height()/2) : (tmpNextA.offset().top - tmpArrow.height()/2); moveType = consts.move.TYPE_NEXT; }else { dX = 5 - tmpArrow.width(); dY = tmpTop; moveType = consts.move.TYPE_INNER; } tmpArrow.css({ "display":"block", "top": dY + "px", "left": (tmpLeft + dX) + "px" }); tmpTargetA.addClass(consts.node.TMPTARGET_NODE + "_" + moveType); if (preTmpTargetNodeId != tmpTargetNodeId || preTmpMoveType != moveType) { startTime = (new Date()).getTime(); } if (tmpTargetNode && tmpTargetNode.isParent && moveType == consts.move.TYPE_INNER) { var startTimer = true; if (window.zTreeMoveTimer && window.zTreeMoveTargetNodeTId !== tmpTargetNode.tId) { clearTimeout(window.zTreeMoveTimer); window.zTreeMoveTargetNodeTId = null; } else if (window.zTreeMoveTimer && window.zTreeMoveTargetNodeTId === tmpTargetNode.tId) { startTimer = false; } if (startTimer) { window.zTreeMoveTimer = setTimeout(function() { if (moveType != consts.move.TYPE_INNER) return; if (tmpTargetNode && tmpTargetNode.isParent && !tmpTargetNode.open && (new Date()).getTime() - startTime > targetSetting.edit.drag.autoOpenTime && tools.apply(targetSetting.callback.beforeDragOpen, [targetSetting.treeId, tmpTargetNode], true)) { view.switchNode(targetSetting, tmpTargetNode); if (targetSetting.edit.drag.autoExpandTrigger) { targetSetting.treeObj.trigger(consts.event.EXPAND, [targetSetting.treeId, tmpTargetNode]); } } }, targetSetting.edit.drag.autoOpenTime+50); window.zTreeMoveTargetNodeTId = tmpTargetNode.tId; } } } } else { moveType = consts.move.TYPE_INNER; if (tmpTarget && tools.apply(targetSetting.edit.drag.inner, [targetSetting.treeId, nodes, null], !!targetSetting.edit.drag.inner)) { tmpTarget.addClass(consts.node.TMPTARGET_TREE); } else { tmpTarget = null; } tmpArrow.css({ "display":"none" }); if (window.zTreeMoveTimer) { clearTimeout(window.zTreeMoveTimer); window.zTreeMoveTargetNodeTId = null; } } preTmpTargetNodeId = tmpTargetNodeId; preTmpMoveType = moveType; } return false; } doc.bind("mouseup", _docMouseUp); function _docMouseUp(event) { if (window.zTreeMoveTimer) { clearTimeout(window.zTreeMoveTimer); window.zTreeMoveTargetNodeTId = null; } preTmpTargetNodeId = null; preTmpMoveType = null; doc.unbind("mousemove", _docMouseMove); doc.unbind("mouseup", _docMouseUp); doc.unbind("selectstart", _docSelect); $("body").css("cursor", "auto"); if (tmpTarget) { tmpTarget.removeClass(consts.node.TMPTARGET_TREE); if (tmpTargetNodeId) $("#" + tmpTargetNodeId + consts.id.A, tmpTarget).removeClass(consts.node.TMPTARGET_NODE + "_" + consts.move.TYPE_PREV) .removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_NEXT).removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_INNER); } tools.showIfameMask(setting, false); root.showHoverDom = true; if (root.dragFlag == 0) return; root.dragFlag = 0; var i, l, tmpNode; for (i=0, l=nodes.length; i0); } $("#" + newNodes[0].tId).focus().blur(); setting.treeObj.trigger(consts.event.DROP, [event, targetSetting.treeId, newNodes, dragTargetNode, moveType, isCopy]); } if (moveType == consts.move.TYPE_INNER && tools.canAsync(targetSetting, dragTargetNode)) { view.asyncNode(targetSetting, dragTargetNode, false, dropCallback); } else { dropCallback(); } } else { for (i=0, l=nodes.length; i0); } setting.treeObj.trigger(consts.event.DROP, [event, setting.treeId, nodes, null, null, null]); } } doc.bind("selectstart", _docSelect); function _docSelect() { return false; } //Avoid FireFox's Bug //If zTree Div CSS set 'overflow', so drag node outside of zTree, and event.target is error. if(eventMouseDown.preventDefault) { eventMouseDown.preventDefault(); } return true; } }, //method of tools for zTree _tools = { getAbs: function (obj) { var oRect = obj.getBoundingClientRect(); return [oRect.left,oRect.top] }, inputFocus: function(inputObj) { if (inputObj.get(0)) { inputObj.focus(); tools.setCursorPosition(inputObj.get(0), inputObj.val().length); } }, inputSelect: function(inputObj) { if (inputObj.get(0)) { inputObj.focus(); inputObj.select(); } }, setCursorPosition: function(obj, pos){ if(obj.setSelectionRange) { obj.focus(); obj.setSelectionRange(pos,pos); } else if (obj.createTextRange) { var range = obj.createTextRange(); range.collapse(true); range.moveEnd('character', pos); range.moveStart('character', pos); range.select(); } }, showIfameMask: function(setting, showSign) { var root = data.getRoot(setting); //clear full mask while (root.dragMaskList.length > 0) { root.dragMaskList[0].remove(); root.dragMaskList.shift(); } if (showSign) { //show mask var iframeList = $("iframe"); for (var i = 0, l = iframeList.length; i < l; i++) { var obj = iframeList.get(i), r = tools.getAbs(obj), dragMask = $("
        "); dragMask.appendTo("body"); root.dragMaskList.push(dragMask); } } } }, //method of operate ztree dom _view = { addEditBtn: function(setting, node) { if (node.editNameFlag || $("#" + node.tId + consts.id.EDIT).length > 0) { return; } if (!tools.apply(setting.edit.showRenameBtn, [setting.treeId, node], setting.edit.showRenameBtn)) { return; } var aObj = $("#" + node.tId + consts.id.A), editStr = ""; aObj.append(editStr); $("#" + node.tId + consts.id.EDIT).bind('click', function() { if (!tools.uCanDo(setting) || tools.apply(setting.callback.beforeEditName, [setting.treeId, node], true) == false) return false; view.editNode(setting, node); return false; } ).show(); }, addRemoveBtn: function(setting, node) { if (node.editNameFlag || $("#" + node.tId + consts.id.REMOVE).length > 0) { return; } if (!tools.apply(setting.edit.showRemoveBtn, [setting.treeId, node], setting.edit.showRemoveBtn)) { return; } var aObj = $("#" + node.tId + consts.id.A), removeStr = ""; aObj.append(removeStr); $("#" + node.tId + consts.id.REMOVE).bind('click', function() { if (!tools.uCanDo(setting) || tools.apply(setting.callback.beforeRemove, [setting.treeId, node], true) == false) return false; view.removeNode(setting, node); setting.treeObj.trigger(consts.event.REMOVE, [setting.treeId, node]); return false; } ).bind('mousedown', function(eventMouseDown) { return true; } ).show(); }, addHoverDom: function(setting, node) { if (data.getRoot(setting).showHoverDom) { node.isHover = true; if (setting.edit.enable) { view.addEditBtn(setting, node); view.addRemoveBtn(setting, node); } tools.apply(setting.view.addHoverDom, [setting.treeId, node]); } }, cancelCurEditNode: function (setting, forceName) { var root = data.getRoot(setting), nameKey = setting.data.key.name, node = root.curEditNode; if (node) { var inputObj = root.curEditInput; var newName = forceName ? forceName:inputObj.val(); if (!forceName && tools.apply(setting.callback.beforeRename, [setting.treeId, node, newName], true) === false) { return false; } else { node[nameKey] = newName ? newName:inputObj.val(); if (!forceName) { setting.treeObj.trigger(consts.event.RENAME, [setting.treeId, node]); } } var aObj = $("#" + node.tId + consts.id.A); aObj.removeClass(consts.node.CURSELECTED_EDIT); inputObj.unbind(); view.setNodeName(setting, node); node.editNameFlag = false; root.curEditNode = null; root.curEditInput = null; view.selectNode(setting, node, false); } root.noSelection = true; return true; }, editNode: function(setting, node) { var root = data.getRoot(setting); view.editNodeBlur = false; if (data.isSelectedNode(setting, node) && root.curEditNode == node && node.editNameFlag) { setTimeout(function() {tools.inputFocus(root.curEditInput);}, 0); return; } var nameKey = setting.data.key.name; node.editNameFlag = true; view.removeTreeDom(setting, node); view.cancelCurEditNode(setting); view.selectNode(setting, node, false); $("#" + node.tId + consts.id.SPAN).html(""); var inputObj = $("#" + node.tId + consts.id.INPUT); inputObj.attr("value", node[nameKey]); if (setting.edit.editNameSelectAll) { tools.inputSelect(inputObj); } else { tools.inputFocus(inputObj); } inputObj.bind('blur', function(event) { if (!view.editNodeBlur) { view.cancelCurEditNode(setting); } }).bind('keydown', function(event) { if (event.keyCode=="13") { view.editNodeBlur = true; view.cancelCurEditNode(setting, null, true); } else if (event.keyCode=="27") { view.cancelCurEditNode(setting, node[nameKey]); } }).bind('click', function(event) { return false; }).bind('dblclick', function(event) { return false; }); $("#" + node.tId + consts.id.A).addClass(consts.node.CURSELECTED_EDIT); root.curEditInput = inputObj; root.noSelection = false; root.curEditNode = node; }, moveNode: function(setting, targetNode, node, moveType, animateFlag, isSilent) { var root = data.getRoot(setting), childKey = setting.data.key.children; if (targetNode == node) return; if (setting.data.keep.leaf && targetNode && !targetNode.isParent && moveType == consts.move.TYPE_INNER) return; var oldParentNode = (node.parentTId ? node.getParentNode(): root), targetNodeIsRoot = (targetNode === null || targetNode == root); if (targetNodeIsRoot && targetNode === null) targetNode = root; if (targetNodeIsRoot) moveType = consts.move.TYPE_INNER; var targetParentNode = (targetNode.parentTId ? targetNode.getParentNode() : root); if (moveType != consts.move.TYPE_PREV && moveType != consts.move.TYPE_NEXT) { moveType = consts.move.TYPE_INNER; } if (moveType == consts.move.TYPE_INNER) { if (targetNodeIsRoot) { //parentTId of root node is null node.parentTId = null; } else { if (!targetNode.isParent) { targetNode.isParent = true; targetNode.open = !!targetNode.open; view.setNodeLineIcos(setting, targetNode); } node.parentTId = targetNode.tId; } } //move node Dom var targetObj, target_ulObj; if (targetNodeIsRoot) { targetObj = setting.treeObj; target_ulObj = targetObj; } else { if (!isSilent && moveType == consts.move.TYPE_INNER) { view.expandCollapseNode(setting, targetNode, true, false); } else if (!isSilent) { view.expandCollapseNode(setting, targetNode.getParentNode(), true, false); } targetObj = $("#" + targetNode.tId); target_ulObj = $("#" + targetNode.tId + consts.id.UL); if (!!targetObj.get(0) && !target_ulObj.get(0)) { var ulstr = []; view.makeUlHtml(setting, targetNode, ulstr, ''); targetObj.append(ulstr.join('')); } target_ulObj = $("#" + targetNode.tId + consts.id.UL); } var nodeDom = $("#" + node.tId); if (!nodeDom.get(0)) { nodeDom = view.appendNodes(setting, node.level, [node], null, false, true).join(''); } else if (!targetObj.get(0)) { nodeDom.remove(); } if (target_ulObj.get(0) && moveType == consts.move.TYPE_INNER) { target_ulObj.append(nodeDom); } else if (targetObj.get(0) && moveType == consts.move.TYPE_PREV) { targetObj.before(nodeDom); } else if (targetObj.get(0) && moveType == consts.move.TYPE_NEXT) { targetObj.after(nodeDom); } //repair the data after move var i,l, tmpSrcIndex = -1, tmpTargetIndex = 0, oldNeighbor = null, newNeighbor = null, oldLevel = node.level; if (node.isFirstNode) { tmpSrcIndex = 0; if (oldParentNode[childKey].length > 1 ) { oldNeighbor = oldParentNode[childKey][1]; oldNeighbor.isFirstNode = true; } } else if (node.isLastNode) { tmpSrcIndex = oldParentNode[childKey].length -1; oldNeighbor = oldParentNode[childKey][tmpSrcIndex - 1]; oldNeighbor.isLastNode = true; } else { for (i = 0, l = oldParentNode[childKey].length; i < l; i++) { if (oldParentNode[childKey][i].tId == node.tId) { tmpSrcIndex = i; break; } } } if (tmpSrcIndex >= 0) { oldParentNode[childKey].splice(tmpSrcIndex, 1); } if (moveType != consts.move.TYPE_INNER) { for (i = 0, l = targetParentNode[childKey].length; i < l; i++) { if (targetParentNode[childKey][i].tId == targetNode.tId) tmpTargetIndex = i; } } if (moveType == consts.move.TYPE_INNER) { if (!targetNode[childKey]) targetNode[childKey] = new Array(); if (targetNode[childKey].length > 0) { newNeighbor = targetNode[childKey][targetNode[childKey].length - 1]; newNeighbor.isLastNode = false; } targetNode[childKey].splice(targetNode[childKey].length, 0, node); node.isLastNode = true; node.isFirstNode = (targetNode[childKey].length == 1); } else if (targetNode.isFirstNode && moveType == consts.move.TYPE_PREV) { targetParentNode[childKey].splice(tmpTargetIndex, 0, node); newNeighbor = targetNode; newNeighbor.isFirstNode = false; node.parentTId = targetNode.parentTId; node.isFirstNode = true; node.isLastNode = false; } else if (targetNode.isLastNode && moveType == consts.move.TYPE_NEXT) { targetParentNode[childKey].splice(tmpTargetIndex + 1, 0, node); newNeighbor = targetNode; newNeighbor.isLastNode = false; node.parentTId = targetNode.parentTId; node.isFirstNode = false; node.isLastNode = true; } else { if (moveType == consts.move.TYPE_PREV) { targetParentNode[childKey].splice(tmpTargetIndex, 0, node); } else { targetParentNode[childKey].splice(tmpTargetIndex + 1, 0, node); } node.parentTId = targetNode.parentTId; node.isFirstNode = false; node.isLastNode = false; } data.fixPIdKeyValue(setting, node); data.setSonNodeLevel(setting, node.getParentNode(), node); //repair node what been moved view.setNodeLineIcos(setting, node); view.repairNodeLevelClass(setting, node, oldLevel) //repair node's old parentNode dom if (!setting.data.keep.parent && oldParentNode[childKey].length < 1) { //old parentNode has no child nodes oldParentNode.isParent = false; oldParentNode.open = false; var tmp_ulObj = $("#" + oldParentNode.tId + consts.id.UL), tmp_switchObj = $("#" + oldParentNode.tId + consts.id.SWITCH), tmp_icoObj = $("#" + oldParentNode.tId + consts.id.ICON); view.replaceSwitchClass(oldParentNode, tmp_switchObj, consts.folder.DOCU); view.replaceIcoClass(oldParentNode, tmp_icoObj, consts.folder.DOCU); tmp_ulObj.css("display", "none"); } else if (oldNeighbor) { //old neigbor node view.setNodeLineIcos(setting, oldNeighbor); } //new neigbor node if (newNeighbor) { view.setNodeLineIcos(setting, newNeighbor); } //repair checkbox / radio if (!!setting.check && setting.check.enable && view.repairChkClass) { view.repairChkClass(setting, oldParentNode); view.repairParentChkClassWithSelf(setting, oldParentNode); if (oldParentNode != node.parent) view.repairParentChkClassWithSelf(setting, node); } //expand parents after move if (!isSilent) { view.expandCollapseParentNode(setting, node.getParentNode(), true, animateFlag); } }, removeEditBtn: function(node) { $("#" + node.tId + consts.id.EDIT).unbind().remove(); }, removeRemoveBtn: function(node) { $("#" + node.tId + consts.id.REMOVE).unbind().remove(); }, removeTreeDom: function(setting, node) { node.isHover = false; view.removeEditBtn(node); view.removeRemoveBtn(node); tools.apply(setting.view.removeHoverDom, [setting.treeId, node]); }, repairNodeLevelClass: function(setting, node, oldLevel) { if (oldLevel === node.level) return; var liObj = $("#" + node.tId), aObj = $("#" + node.tId + consts.id.A), ulObj = $("#" + node.tId + consts.id.UL), oldClass = consts.className.LEVEL + oldLevel, newClass = consts.className.LEVEL + node.level; liObj.removeClass(oldClass); liObj.addClass(newClass); aObj.removeClass(oldClass); aObj.addClass(newClass); ulObj.removeClass(oldClass); ulObj.addClass(newClass); } }, _z = { tools: _tools, view: _view, event: _event, data: _data }; $.extend(true, $.fn.zTree.consts, _consts); $.extend(true, $.fn.zTree._z, _z); var zt = $.fn.zTree, tools = zt._z.tools, consts = zt.consts, view = zt._z.view, data = zt._z.data, event = zt._z.event; data.exSetting(_setting); data.addInitBind(_bindEvent); data.addInitUnBind(_unbindEvent); data.addInitCache(_initCache); data.addInitNode(_initNode); data.addInitProxy(_eventProxy); data.addInitRoot(_initRoot); data.addZTreeTools(_zTreeTools); var _cancelPreSelectedNode = view.cancelPreSelectedNode; view.cancelPreSelectedNode = function (setting, node) { var list = data.getRoot(setting).curSelectedList; for (var i=0, j=list.length; i"); }, showNode: function(setting, node, options) { node.isHidden = false; data.initShowForExCheck(setting, node); $("#" + node.tId).show(); }, showNodes: function(setting, nodes, options) { if (!nodes || nodes.length == 0) { return; } var pList = {}, i, j; for (i=0, j=nodes.length; i 0 && !parentNode[childKey][0].isHidden) { parentNode[childKey][0].isFirstNode = true; } else if (childLength > 0) { view.setFirstNodeForHide(setting, parentNode[childKey]); } }, setLastNode: function(setting, parentNode) { var childKey = setting.data.key.children, childLength = parentNode[childKey].length; if (childLength > 0 && !parentNode[childKey][0].isHidden) { parentNode[childKey][childLength - 1].isLastNode = true; } else if (childLength > 0) { view.setLastNodeForHide(setting, parentNode[childKey]); } }, setFirstNodeForHide: function(setting, nodes) { var n,i,j; for (i=0, j=nodes.length; i=0; i--) { n = nodes[i]; if (n.isLastNode) { break; } if (!n.isHidden && !n.isLastNode) { n.isLastNode = true; view.setNodeLineIcos(setting, n); break; } else { n = null; } } return n; }, setLastNodeForShow: function(setting, nodes) { var n,i,j, last, old; for (i=nodes.length-1; i>=0; i--) { n = nodes[i]; if (!last && !n.isHidden && n.isLastNode) { last = n; break; } else if (!last && !n.isHidden && !n.isLastNode) { n.isLastNode = true; last = n; view.setNodeLineIcos(setting, n); } else if (last && n.isLastNode) { n.isLastNode = false; old = n; view.setNodeLineIcos(setting, n); break; } else { n = null; } } return {"new":last, "old":old}; } }, _z = { view: _view, data: _data }; $.extend(true, $.fn.zTree._z, _z); var zt = $.fn.zTree, tools = zt._z.tools, consts = zt.consts, view = zt._z.view, data = zt._z.data, event = zt._z.event; data.addInitNode(_initNode); data.addBeforeA(_beforeA); data.addZTreeTools(_zTreeTools); // Override method in core var _dInitNode = data.initNode; data.tmpHideParent = -1; data.initNode = function(setting, level, node, parentNode, isFirstNode, isLastNode, openFlag) { if (data.tmpHideParent !== parentNode) { data.tmpHideParent = parentNode; var tmpPNode = (parentNode) ? parentNode: data.getRoot(setting), children = tmpPNode[setting.data.key.children]; data.tmpHideFirstNode = view.setFirstNodeForHide(setting, children); data.tmpHideLastNode = view.setLastNodeForHide(setting, children); view.setNodeLineIcos(setting, data.tmpHideFirstNode); view.setNodeLineIcos(setting, data.tmpHideLastNode); } isFirstNode = (data.tmpHideFirstNode === node); isLastNode = (data.tmpHideLastNode === node); if (_dInitNode) _dInitNode.apply(data, arguments); if (isLastNode) { view.clearOldLastNode(setting, node); } } var _makeChkFlag = data.makeChkFlag; if (!!_makeChkFlag) { data.makeChkFlag = function(setting, node) { if (!!node && !!node.isHidden) { return; } _makeChkFlag.apply(data, arguments); } } var _getTreeCheckedNodes = data.getTreeCheckedNodes; if (!!_getTreeCheckedNodes) { data.getTreeCheckedNodes = function(setting, nodes, checked, results) { if (!!nodes && nodes.length > 0) { var p = nodes[0].getParentNode(); if (!!p && !!p.isHidden) { return []; } } return _getTreeCheckedNodes.apply(data, arguments); } } var _getTreeChangeCheckedNodes = data.getTreeChangeCheckedNodes; if (!!_getTreeChangeCheckedNodes) { data.getTreeChangeCheckedNodes = function(setting, nodes, results) { if (!!nodes && nodes.length > 0) { var p = nodes[0].getParentNode(); if (!!p && !!p.isHidden) { return []; } } return _getTreeChangeCheckedNodes.apply(data, arguments); } } var _expandCollapseSonNode = view.expandCollapseSonNode; if (!!_expandCollapseSonNode) { view.expandCollapseSonNode = function(setting, node, expandFlag, animateFlag, callback) { if (!!node && !!node.isHidden) { return; } _expandCollapseSonNode.apply(view, arguments); } } var _setSonNodeCheckBox = view.setSonNodeCheckBox; if (!!_setSonNodeCheckBox) { view.setSonNodeCheckBox = function(setting, node, value, srcNode) { if (!!node && !!node.isHidden) { return; } _setSonNodeCheckBox.apply(view, arguments); } } var _repairParentChkClassWithSelf = view.repairParentChkClassWithSelf; if (!!_repairParentChkClassWithSelf) { view.repairParentChkClassWithSelf = function(setting, node) { if (!!node && !!node.isHidden) { return; } _repairParentChkClassWithSelf.apply(view, arguments); } } })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.core-3.5.js ================================================ /* * JQuery zTree core 3.5.12 * http://zTree.me/ * * Copyright (c) 2010 Hunter.z * * Licensed same as jquery - MIT License * http://www.opensource.org/licenses/mit-license.php * * email: hunter.z@263.net * Date: 2013-03-11 */ (function($){ var settings = {}, roots = {}, caches = {}, //default consts of core _consts = { className: { BUTTON: "button", LEVEL: "level", ICO_LOADING: "ico_loading", SWITCH: "switch" }, event: { NODECREATED: "ztree_nodeCreated", CLICK: "ztree_click", EXPAND: "ztree_expand", COLLAPSE: "ztree_collapse", ASYNC_SUCCESS: "ztree_async_success", ASYNC_ERROR: "ztree_async_error" }, id: { A: "_a", ICON: "_ico", SPAN: "_span", SWITCH: "_switch", UL: "_ul" }, line: { ROOT: "root", ROOTS: "roots", CENTER: "center", BOTTOM: "bottom", NOLINE: "noline", LINE: "line" }, folder: { OPEN: "open", CLOSE: "close", DOCU: "docu" }, node: { CURSELECTED: "curSelectedNode" } }, //default setting of core _setting = { treeId: "", treeObj: null, view: { addDiyDom: null, autoCancelSelected: true, dblClickExpand: true, expandSpeed: "fast", fontCss: {}, nameIsHTML: false, selectedMulti: true, showIcon: true, showLine: true, showTitle: true }, data: { key: { children: "children", name: "name", title: "", url: "url" }, simpleData: { enable: false, idKey: "id", pIdKey: "pId", rootPId: null }, keep: { parent: false, leaf: false } }, async: { enable: false, contentType: "application/x-www-form-urlencoded", type: "post", dataType: "text", url: "", autoParam: [], otherParam: [], dataFilter: null }, callback: { beforeAsync:null, beforeClick:null, beforeDblClick:null, beforeRightClick:null, beforeMouseDown:null, beforeMouseUp:null, beforeExpand:null, beforeCollapse:null, beforeRemove:null, onAsyncError:null, onAsyncSuccess:null, onNodeCreated:null, onClick:null, onDblClick:null, onRightClick:null, onMouseDown:null, onMouseUp:null, onExpand:null, onCollapse:null, onRemove:null } }, //default root of core //zTree use root to save full data _initRoot = function (setting) { var r = data.getRoot(setting); if (!r) { r = {}; data.setRoot(setting, r); } r[setting.data.key.children] = []; r.expandTriggerFlag = false; r.curSelectedList = []; r.noSelection = true; r.createdNodes = []; r.zId = 0; r._ver = (new Date()).getTime(); }, //default cache of core _initCache = function(setting) { var c = data.getCache(setting); if (!c) { c = {}; data.setCache(setting, c); } c.nodes = []; c.doms = []; }, //default bindEvent of core _bindEvent = function(setting) { var o = setting.treeObj, c = consts.event; o.bind(c.NODECREATED, function (event, treeId, node) { tools.apply(setting.callback.onNodeCreated, [event, treeId, node]); }); o.bind(c.CLICK, function (event, srcEvent, treeId, node, clickFlag) { tools.apply(setting.callback.onClick, [srcEvent, treeId, node, clickFlag]); }); o.bind(c.EXPAND, function (event, treeId, node) { tools.apply(setting.callback.onExpand, [event, treeId, node]); }); o.bind(c.COLLAPSE, function (event, treeId, node) { tools.apply(setting.callback.onCollapse, [event, treeId, node]); }); o.bind(c.ASYNC_SUCCESS, function (event, treeId, node, msg) { tools.apply(setting.callback.onAsyncSuccess, [event, treeId, node, msg]); }); o.bind(c.ASYNC_ERROR, function (event, treeId, node, XMLHttpRequest, textStatus, errorThrown) { tools.apply(setting.callback.onAsyncError, [event, treeId, node, XMLHttpRequest, textStatus, errorThrown]); }); }, _unbindEvent = function(setting) { var o = setting.treeObj, c = consts.event; o.unbind(c.NODECREATED) .unbind(c.CLICK) .unbind(c.EXPAND) .unbind(c.COLLAPSE) .unbind(c.ASYNC_SUCCESS) .unbind(c.ASYNC_ERROR); }, //default event proxy of core _eventProxy = function(event) { var target = event.target, setting = data.getSetting(event.data.treeId), tId = "", node = null, nodeEventType = "", treeEventType = "", nodeEventCallback = null, treeEventCallback = null, tmp = null; if (tools.eqs(event.type, "mousedown")) { treeEventType = "mousedown"; } else if (tools.eqs(event.type, "mouseup")) { treeEventType = "mouseup"; } else if (tools.eqs(event.type, "contextmenu")) { treeEventType = "contextmenu"; } else if (tools.eqs(event.type, "click")) { if (tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.SWITCH) !== null) { tId = ($(target).parent("li").get(0) || $(target).parentsUntil("li").parent().get(0)).id; nodeEventType = "switchNode"; } else { tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) { tId = ($(tmp).parent("li").get(0) || $(tmp).parentsUntil("li").parent().get(0)).id; nodeEventType = "clickNode"; } } } else if (tools.eqs(event.type, "dblclick")) { treeEventType = "dblclick"; tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) { tId = ($(tmp).parent("li").get(0) || $(tmp).parentsUntil("li").parent().get(0)).id; nodeEventType = "switchNode"; } } if (treeEventType.length > 0 && tId.length == 0) { tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) {tId = ($(tmp).parent("li").get(0) || $(tmp).parentsUntil("li").parent().get(0)).id;} } // event to node if (tId.length>0) { node = data.getNodeCache(setting, tId); switch (nodeEventType) { case "switchNode" : if (!node.isParent) { nodeEventType = ""; } else if (tools.eqs(event.type, "click") || (tools.eqs(event.type, "dblclick") && tools.apply(setting.view.dblClickExpand, [setting.treeId, node], setting.view.dblClickExpand))) { nodeEventCallback = handler.onSwitchNode; } else { nodeEventType = ""; } break; case "clickNode" : nodeEventCallback = handler.onClickNode; break; } } // event to zTree switch (treeEventType) { case "mousedown" : treeEventCallback = handler.onZTreeMousedown; break; case "mouseup" : treeEventCallback = handler.onZTreeMouseup; break; case "dblclick" : treeEventCallback = handler.onZTreeDblclick; break; case "contextmenu" : treeEventCallback = handler.onZTreeContextmenu; break; } var proxyResult = { stop: false, node: node, nodeEventType: nodeEventType, nodeEventCallback: nodeEventCallback, treeEventType: treeEventType, treeEventCallback: treeEventCallback }; return proxyResult }, //default init node of core _initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { if (!n) return; var r = data.getRoot(setting), childKey = setting.data.key.children; n.level = level; n.tId = setting.treeId + "_" + (++r.zId); n.parentTId = parentNode ? parentNode.tId : null; if (n[childKey] && n[childKey].length > 0) { if (typeof n.open == "string") n.open = tools.eqs(n.open, "true"); n.open = !!n.open; n.isParent = true; n.zAsync = true; } else { n.open = false; if (typeof n.isParent == "string") n.isParent = tools.eqs(n.isParent, "true"); n.isParent = !!n.isParent; n.zAsync = !n.isParent; } n.isFirstNode = isFirstNode; n.isLastNode = isLastNode; n.getParentNode = function() {return data.getNodeCache(setting, n.parentTId);}; n.getPreNode = function() {return data.getPreNode(setting, n);}; n.getNextNode = function() {return data.getNextNode(setting, n);}; n.isAjaxing = false; data.fixPIdKeyValue(setting, n); }, _init = { bind: [_bindEvent], unbind: [_unbindEvent], caches: [_initCache], nodes: [_initNode], proxys: [_eventProxy], roots: [_initRoot], beforeA: [], afterA: [], innerBeforeA: [], innerAfterA: [], zTreeTools: [] }, //method of operate data data = { addNodeCache: function(setting, node) { data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = node; }, getNodeCacheId: function(tId) { return tId.substring(tId.lastIndexOf("_")+1); }, addAfterA: function(afterA) { _init.afterA.push(afterA); }, addBeforeA: function(beforeA) { _init.beforeA.push(beforeA); }, addInnerAfterA: function(innerAfterA) { _init.innerAfterA.push(innerAfterA); }, addInnerBeforeA: function(innerBeforeA) { _init.innerBeforeA.push(innerBeforeA); }, addInitBind: function(bindEvent) { _init.bind.push(bindEvent); }, addInitUnBind: function(unbindEvent) { _init.unbind.push(unbindEvent); }, addInitCache: function(initCache) { _init.caches.push(initCache); }, addInitNode: function(initNode) { _init.nodes.push(initNode); }, addInitProxy: function(initProxy) { _init.proxys.push(initProxy); }, addInitRoot: function(initRoot) { _init.roots.push(initRoot); }, addNodesData: function(setting, parentNode, nodes) { var childKey = setting.data.key.children; if (!parentNode[childKey]) parentNode[childKey] = []; if (parentNode[childKey].length > 0) { parentNode[childKey][parentNode[childKey].length - 1].isLastNode = false; view.setNodeLineIcos(setting, parentNode[childKey][parentNode[childKey].length - 1]); } parentNode.isParent = true; parentNode[childKey] = parentNode[childKey].concat(nodes); }, addSelectedNode: function(setting, node) { var root = data.getRoot(setting); if (!data.isSelectedNode(setting, node)) { root.curSelectedList.push(node); } }, addCreatedNode: function(setting, node) { if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) { var root = data.getRoot(setting); root.createdNodes.push(node); } }, addZTreeTools: function(zTreeTools) { _init.zTreeTools.push(zTreeTools); }, exSetting: function(s) { $.extend(true, _setting, s); }, fixPIdKeyValue: function(setting, node) { if (setting.data.simpleData.enable) { node[setting.data.simpleData.pIdKey] = node.parentTId ? node.getParentNode()[setting.data.simpleData.idKey] : setting.data.simpleData.rootPId; } }, getAfterA: function(setting, node, array) { for (var i=0, j=_init.afterA.length; i-1) { result.push(nodes[i]); } result = result.concat(data.getNodesByParamFuzzy(setting, nodes[i][childKey], key, value)); } return result; }, getNodesByFilter: function(setting, nodes, filter, isSingle, invokeParam) { if (!nodes) return (isSingle ? null : []); var childKey = setting.data.key.children, result = isSingle ? null : []; for (var i = 0, l = nodes.length; i < l; i++) { if (tools.apply(filter, [nodes[i], invokeParam], false)) { if (isSingle) {return nodes[i];} result.push(nodes[i]); } var tmpResult = data.getNodesByFilter(setting, nodes[i][childKey], filter, isSingle, invokeParam); if (isSingle && !!tmpResult) {return tmpResult;} result = isSingle ? tmpResult : result.concat(tmpResult); } return result; }, getPreNode: function(setting, node) { if (!node) return null; var childKey = setting.data.key.children, p = node.parentTId ? node.getParentNode() : data.getRoot(setting); for (var i=0, l=p[childKey].length; i 0))); }, clone: function (obj){ if (obj === null) return null; var o = obj.constructor === Array ? [] : {}; for(var i in obj){ o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? arguments.callee(obj[i]) : obj[i]); } return o; }, eqs: function(str1, str2) { return str1.toLowerCase() === str2.toLowerCase(); }, isArray: function(arr) { return Object.prototype.toString.apply(arr) === "[object Array]"; }, getMDom: function (setting, curDom, targetExpr) { if (!curDom) return null; while (curDom && curDom.id !== setting.treeId) { for (var i=0, l=targetExpr.length; curDom.tagName && i 0) { //make child html first, because checkType childHtml = view.appendNodes(setting, level + 1, node[childKey], node, initFlag, openFlag && node.open); } if (openFlag) { view.makeDOMNodeMainBefore(html, setting, node); view.makeDOMNodeLine(html, setting, node); data.getBeforeA(setting, node, html); view.makeDOMNodeNameBefore(html, setting, node); data.getInnerBeforeA(setting, node, html); view.makeDOMNodeIcon(html, setting, node); data.getInnerAfterA(setting, node, html); view.makeDOMNodeNameAfter(html, setting, node); data.getAfterA(setting, node, html); if (node.isParent && node.open) { view.makeUlHtml(setting, node, html, childHtml.join('')); } view.makeDOMNodeMainAfter(html, setting, node); data.addCreatedNode(setting, node); } } return html; }, appendParentULDom: function(setting, node) { var html = [], nObj = $("#" + node.tId), ulObj = $("#" + node.tId + consts.id.UL), childKey = setting.data.key.children, childHtml = view.appendNodes(setting, node.level+1, node[childKey], node, false, true); view.makeUlHtml(setting, node, html, childHtml.join('')); if (!nObj.get(0) && !!node.parentTId) { view.appendParentULDom(setting, node.getParentNode()); nObj = $("#" + node.tId); } if (ulObj.get(0)) { ulObj.remove(); } nObj.append(html.join('')); }, asyncNode: function(setting, node, isSilent, callback) { var i, l; if (node && !node.isParent) { tools.apply(callback); return false; } else if (node && node.isAjaxing) { return false; } else if (tools.apply(setting.callback.beforeAsync, [setting.treeId, node], true) == false) { tools.apply(callback); return false; } if (node) { node.isAjaxing = true; var icoObj = $("#" + node.tId + consts.id.ICON); icoObj.attr({"style":"", "class":consts.className.BUTTON + " " + consts.className.ICO_LOADING}); } var tmpParam = {}; for (i = 0, l = setting.async.autoParam.length; node && i < l; i++) { var pKey = setting.async.autoParam[i].split("="), spKey = pKey; if (pKey.length>1) { spKey = pKey[1]; pKey = pKey[0]; } tmpParam[spKey] = node[pKey]; } if (tools.isArray(setting.async.otherParam)) { for (i = 0, l = setting.async.otherParam.length; i < l; i += 2) { tmpParam[setting.async.otherParam[i]] = setting.async.otherParam[i + 1]; } } else { for (var p in setting.async.otherParam) { tmpParam[p] = setting.async.otherParam[p]; } } var _tmpV = data.getRoot(setting)._ver; $.ajax({ contentType: setting.async.contentType, type: setting.async.type, url: tools.apply(setting.async.url, [setting.treeId, node], setting.async.url), data: tmpParam, dataType: setting.async.dataType, success: function(msg) { if (_tmpV != data.getRoot(setting)._ver) { return; } var newNodes = []; try { if (!msg || msg.length == 0) { newNodes = []; } else if (typeof msg == "string") { newNodes = eval("(" + msg + ")"); } else { newNodes = msg; } } catch(err) { newNodes = msg; } if (node) { node.isAjaxing = null; node.zAsync = true; } view.setNodeLineIcos(setting, node); if (newNodes && newNodes !== "") { newNodes = tools.apply(setting.async.dataFilter, [setting.treeId, node, newNodes], newNodes); view.addNodes(setting, node, !!newNodes ? tools.clone(newNodes) : [], !!isSilent); } else { view.addNodes(setting, node, [], !!isSilent); } setting.treeObj.trigger(consts.event.ASYNC_SUCCESS, [setting.treeId, node, msg]); tools.apply(callback); }, error: function(XMLHttpRequest, textStatus, errorThrown) { if (_tmpV != data.getRoot(setting)._ver) { return; } if (node) node.isAjaxing = null; view.setNodeLineIcos(setting, node); setting.treeObj.trigger(consts.event.ASYNC_ERROR, [setting.treeId, node, XMLHttpRequest, textStatus, errorThrown]); } }); return true; }, cancelPreSelectedNode: function (setting, node) { var list = data.getRoot(setting).curSelectedList; for (var i=0, j=list.length-1; j>=i; j--) { if (!node || node === list[j]) { $("#" + list[j].tId + consts.id.A).removeClass(consts.node.CURSELECTED); if (node) { data.removeSelectedNode(setting, node); break; } } } if (!node) data.getRoot(setting).curSelectedList = []; }, createNodeCallback: function(setting) { if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) { var root = data.getRoot(setting); while (root.createdNodes.length>0) { var node = root.createdNodes.shift(); tools.apply(setting.view.addDiyDom, [setting.treeId, node]); if (!!setting.callback.onNodeCreated) { setting.treeObj.trigger(consts.event.NODECREATED, [setting.treeId, node]); } } } }, createNodes: function(setting, level, nodes, parentNode) { if (!nodes || nodes.length == 0) return; var root = data.getRoot(setting), childKey = setting.data.key.children, openFlag = !parentNode || parentNode.open || !!$("#" + parentNode[childKey][0].tId).get(0); root.createdNodes = []; var zTreeHtml = view.appendNodes(setting, level, nodes, parentNode, true, openFlag); if (!parentNode) { setting.treeObj.append(zTreeHtml.join('')); } else { var ulObj = $("#" + parentNode.tId + consts.id.UL); if (ulObj.get(0)) { ulObj.append(zTreeHtml.join('')); } } view.createNodeCallback(setting); }, destroy: function(setting) { if (!setting) return; data.initCache(setting); data.initRoot(setting); event.unbindTree(setting); event.unbindEvent(setting); setting.treeObj.empty(); }, expandCollapseNode: function(setting, node, expandFlag, animateFlag, callback) { var root = data.getRoot(setting), childKey = setting.data.key.children; if (!node) { tools.apply(callback, []); return; } if (root.expandTriggerFlag) { var _callback = callback; callback = function(){ if (_callback) _callback(); if (node.open) { setting.treeObj.trigger(consts.event.EXPAND, [setting.treeId, node]); } else { setting.treeObj.trigger(consts.event.COLLAPSE, [setting.treeId, node]); } }; root.expandTriggerFlag = false; } if (!node.open && node.isParent && ((!$("#" + node.tId + consts.id.UL).get(0)) || (node[childKey] && node[childKey].length>0 && !$("#" + node[childKey][0].tId).get(0)))) { view.appendParentULDom(setting, node); view.createNodeCallback(setting); } if (node.open == expandFlag) { tools.apply(callback, []); return; } var ulObj = $("#" + node.tId + consts.id.UL), switchObj = $("#" + node.tId + consts.id.SWITCH), icoObj = $("#" + node.tId + consts.id.ICON); if (node.isParent) { node.open = !node.open; if (node.iconOpen && node.iconClose) { icoObj.attr("style", view.makeNodeIcoStyle(setting, node)); } if (node.open) { view.replaceSwitchClass(node, switchObj, consts.folder.OPEN); view.replaceIcoClass(node, icoObj, consts.folder.OPEN); if (animateFlag == false || setting.view.expandSpeed == "") { ulObj.show(); tools.apply(callback, []); } else { if (node[childKey] && node[childKey].length > 0) { ulObj.slideDown(setting.view.expandSpeed, callback); } else { ulObj.show(); tools.apply(callback, []); } } } else { view.replaceSwitchClass(node, switchObj, consts.folder.CLOSE); view.replaceIcoClass(node, icoObj, consts.folder.CLOSE); if (animateFlag == false || setting.view.expandSpeed == "" || !(node[childKey] && node[childKey].length > 0)) { ulObj.hide(); tools.apply(callback, []); } else { ulObj.slideUp(setting.view.expandSpeed, callback); } } } else { tools.apply(callback, []); } }, expandCollapseParentNode: function(setting, node, expandFlag, animateFlag, callback) { if (!node) return; if (!node.parentTId) { view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback); return; } else { view.expandCollapseNode(setting, node, expandFlag, animateFlag); } if (node.parentTId) { view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, animateFlag, callback); } }, expandCollapseSonNode: function(setting, node, expandFlag, animateFlag, callback) { var root = data.getRoot(setting), childKey = setting.data.key.children, treeNodes = (node) ? node[childKey]: root[childKey], selfAnimateSign = (node) ? false : animateFlag, expandTriggerFlag = data.getRoot(setting).expandTriggerFlag; data.getRoot(setting).expandTriggerFlag = false; if (treeNodes) { for (var i = 0, l = treeNodes.length; i < l; i++) { if (treeNodes[i]) view.expandCollapseSonNode(setting, treeNodes[i], expandFlag, selfAnimateSign); } } data.getRoot(setting).expandTriggerFlag = expandTriggerFlag; view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback ); }, makeDOMNodeIcon: function(html, setting, node) { var nameStr = data.getNodeName(setting, node), name = setting.view.nameIsHTML ? nameStr : nameStr.replace(/&/g,'&').replace(//g,'>'); html.push("",name,""); }, makeDOMNodeLine: function(html, setting, node) { html.push(""); }, makeDOMNodeMainAfter: function(html, setting, node) { html.push("
      • "); }, makeDOMNodeMainBefore: function(html, setting, node) { html.push("
      • "); }, makeDOMNodeNameAfter: function(html, setting, node) { html.push(""); }, makeDOMNodeNameBefore: function(html, setting, node) { var title = data.getNodeTitle(setting, node), url = view.makeNodeUrl(setting, node), fontcss = view.makeNodeFontCss(setting, node), fontStyle = []; for (var f in fontcss) { fontStyle.push(f, ":", fontcss[f], ";"); } html.push(" 0) ? "href='" + url + "'" : ""), " target='",view.makeNodeTarget(node),"' style='", fontStyle.join(''), "'"); if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle) && title) {html.push("title='", title.replace(/'/g,"'").replace(//g,'>'),"'");} html.push(">"); }, makeNodeFontCss: function(setting, node) { var fontCss = tools.apply(setting.view.fontCss, [setting.treeId, node], setting.view.fontCss); return (fontCss && ((typeof fontCss) != "function")) ? fontCss : {}; }, makeNodeIcoClass: function(setting, node) { var icoCss = ["ico"]; if (!node.isAjaxing) { icoCss[0] = (node.iconSkin ? node.iconSkin + "_" : "") + icoCss[0]; if (node.isParent) { icoCss.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE); } else { icoCss.push(consts.folder.DOCU); } } return consts.className.BUTTON + " " + icoCss.join('_'); }, makeNodeIcoStyle: function(setting, node) { var icoStyle = []; if (!node.isAjaxing) { var icon = (node.isParent && node.iconOpen && node.iconClose) ? (node.open ? node.iconOpen : node.iconClose) : node.icon; if (icon) icoStyle.push("background:url(", icon, ") 0 0 no-repeat;"); if (setting.view.showIcon == false || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)) { icoStyle.push("width:0px;height:0px;"); } } return icoStyle.join(''); }, makeNodeLineClass: function(setting, node) { var lineClass = []; if (setting.view.showLine) { if (node.level == 0 && node.isFirstNode && node.isLastNode) { lineClass.push(consts.line.ROOT); } else if (node.level == 0 && node.isFirstNode) { lineClass.push(consts.line.ROOTS); } else if (node.isLastNode) { lineClass.push(consts.line.BOTTOM); } else { lineClass.push(consts.line.CENTER); } } else { lineClass.push(consts.line.NOLINE); } if (node.isParent) { lineClass.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE); } else { lineClass.push(consts.folder.DOCU); } return view.makeNodeLineClassEx(node) + lineClass.join('_'); }, makeNodeLineClassEx: function(node) { return consts.className.BUTTON + " " + consts.className.LEVEL + node.level + " " + consts.className.SWITCH + " "; }, makeNodeTarget: function(node) { return (node.target || "_blank"); }, makeNodeUrl: function(setting, node) { var urlKey = setting.data.key.url; return node[urlKey] ? node[urlKey] : null; }, makeUlHtml: function(setting, node, html, content) { html.push("
          "); html.push(content); html.push("
        "); }, makeUlLineClass: function(setting, node) { return ((setting.view.showLine && !node.isLastNode) ? consts.line.LINE : ""); }, removeChildNodes: function(setting, node) { if (!node) return; var childKey = setting.data.key.children, nodes = node[childKey]; if (!nodes) return; for (var i = 0, l = nodes.length; i < l; i++) { data.removeNodeCache(setting, nodes[i]); } data.removeSelectedNode(setting); delete node[childKey]; if (!setting.data.keep.parent) { node.isParent = false; node.open = false; var tmp_switchObj = $("#" + node.tId + consts.id.SWITCH), tmp_icoObj = $("#" + node.tId + consts.id.ICON); view.replaceSwitchClass(node, tmp_switchObj, consts.folder.DOCU); view.replaceIcoClass(node, tmp_icoObj, consts.folder.DOCU); $("#" + node.tId + consts.id.UL).remove(); } else { $("#" + node.tId + consts.id.UL).empty(); } }, setFirstNode: function(setting, parentNode) { var childKey = setting.data.key.children, childLength = parentNode[childKey].length; if ( childLength > 0) { parentNode[childKey][0].isFirstNode = true; } }, setLastNode: function(setting, parentNode) { var childKey = setting.data.key.children, childLength = parentNode[childKey].length; if ( childLength > 0) { parentNode[childKey][childLength - 1].isLastNode = true; } }, removeNode: function(setting, node) { var root = data.getRoot(setting), childKey = setting.data.key.children, parentNode = (node.parentTId) ? node.getParentNode() : root; node.isFirstNode = false; node.isLastNode = false; node.getPreNode = function() {return null;}; node.getNextNode = function() {return null;}; if (!data.getNodeCache(setting, node.tId)) { return; } $("#" + node.tId).remove(); data.removeNodeCache(setting, node); data.removeSelectedNode(setting, node); for (var i = 0, l = parentNode[childKey].length; i < l; i++) { if (parentNode[childKey][i].tId == node.tId) { parentNode[childKey].splice(i, 1); break; } } view.setFirstNode(setting, parentNode); view.setLastNode(setting, parentNode); var tmp_ulObj,tmp_switchObj,tmp_icoObj, childLength = parentNode[childKey].length; //repair nodes old parent if (!setting.data.keep.parent && childLength == 0) { //old parentNode has no child nodes parentNode.isParent = false; parentNode.open = false; tmp_ulObj = $("#" + parentNode.tId + consts.id.UL); tmp_switchObj = $("#" + parentNode.tId + consts.id.SWITCH); tmp_icoObj = $("#" + parentNode.tId + consts.id.ICON); view.replaceSwitchClass(parentNode, tmp_switchObj, consts.folder.DOCU); view.replaceIcoClass(parentNode, tmp_icoObj, consts.folder.DOCU); tmp_ulObj.css("display", "none"); } else if (setting.view.showLine && childLength > 0) { //old parentNode has child nodes var newLast = parentNode[childKey][childLength - 1]; tmp_ulObj = $("#" + newLast.tId + consts.id.UL); tmp_switchObj = $("#" + newLast.tId + consts.id.SWITCH); tmp_icoObj = $("#" + newLast.tId + consts.id.ICON); if (parentNode == root) { if (parentNode[childKey].length == 1) { //node was root, and ztree has only one root after move node view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.ROOT); } else { var tmp_first_switchObj = $("#" + parentNode[childKey][0].tId + consts.id.SWITCH); view.replaceSwitchClass(parentNode[childKey][0], tmp_first_switchObj, consts.line.ROOTS); view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM); } } else { view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM); } tmp_ulObj.removeClass(consts.line.LINE); } }, replaceIcoClass: function(node, obj, newName) { if (!obj || node.isAjaxing) return; var tmpName = obj.attr("class"); if (tmpName == undefined) return; var tmpList = tmpName.split("_"); switch (newName) { case consts.folder.OPEN: case consts.folder.CLOSE: case consts.folder.DOCU: tmpList[tmpList.length-1] = newName; break; } obj.attr("class", tmpList.join("_")); }, replaceSwitchClass: function(node, obj, newName) { if (!obj) return; var tmpName = obj.attr("class"); if (tmpName == undefined) return; var tmpList = tmpName.split("_"); switch (newName) { case consts.line.ROOT: case consts.line.ROOTS: case consts.line.CENTER: case consts.line.BOTTOM: case consts.line.NOLINE: tmpList[0] = view.makeNodeLineClassEx(node) + newName; break; case consts.folder.OPEN: case consts.folder.CLOSE: case consts.folder.DOCU: tmpList[1] = newName; break; } obj.attr("class", tmpList.join("_")); if (newName !== consts.folder.DOCU) { obj.removeAttr("disabled"); } else { obj.attr("disabled", "disabled"); } }, selectNode: function(setting, node, addFlag) { if (!addFlag) { view.cancelPreSelectedNode(setting); } $("#" + node.tId + consts.id.A).addClass(consts.node.CURSELECTED); data.addSelectedNode(setting, node); }, setNodeFontCss: function(setting, treeNode) { var aObj = $("#" + treeNode.tId + consts.id.A), fontCss = view.makeNodeFontCss(setting, treeNode); if (fontCss) { aObj.css(fontCss); } }, setNodeLineIcos: function(setting, node) { if (!node) return; var switchObj = $("#" + node.tId + consts.id.SWITCH), ulObj = $("#" + node.tId + consts.id.UL), icoObj = $("#" + node.tId + consts.id.ICON), ulLine = view.makeUlLineClass(setting, node); if (ulLine.length==0) { ulObj.removeClass(consts.line.LINE); } else { ulObj.addClass(ulLine); } switchObj.attr("class", view.makeNodeLineClass(setting, node)); if (node.isParent) { switchObj.removeAttr("disabled"); } else { switchObj.attr("disabled", "disabled"); } icoObj.removeAttr("style"); icoObj.attr("style", view.makeNodeIcoStyle(setting, node)); icoObj.attr("class", view.makeNodeIcoClass(setting, node)); }, setNodeName: function(setting, node) { var title = data.getNodeTitle(setting, node), nObj = $("#" + node.tId + consts.id.SPAN); nObj.empty(); if (setting.view.nameIsHTML) { nObj.html(data.getNodeName(setting, node)); } else { nObj.text(data.getNodeName(setting, node)); } if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle)) { var aObj = $("#" + node.tId + consts.id.A); aObj.attr("title", !title ? "" : title); } }, setNodeTarget: function(node) { var aObj = $("#" + node.tId + consts.id.A); aObj.attr("target", view.makeNodeTarget(node)); }, setNodeUrl: function(setting, node) { var aObj = $("#" + node.tId + consts.id.A), url = view.makeNodeUrl(setting, node); if (url == null || url.length == 0) { aObj.removeAttr("href"); } else { aObj.attr("href", url); } }, switchNode: function(setting, node) { if (node.open || !tools.canAsync(setting, node)) { view.expandCollapseNode(setting, node, !node.open); } else if (setting.async.enable) { if (!view.asyncNode(setting, node)) { view.expandCollapseNode(setting, node, !node.open); return; } } else if (node) { view.expandCollapseNode(setting, node, !node.open); } } }; // zTree defind $.fn.zTree = { consts : _consts, _z : { tools: tools, view: view, event: event, data: data }, getZTreeObj: function(treeId) { var o = data.getZTreeTools(treeId); return o ? o : null; }, destroy: function(treeId) { if (!!treeId && treeId.length > 0) { view.destroy(data.getSetting(treeId)); } else { for(var s in settings) { view.destroy(settings[s]); } } }, init: function(obj, zSetting, zNodes) { var setting = tools.clone(_setting); $.extend(true, setting, zSetting); setting.treeId = obj.attr("id"); setting.treeObj = obj; setting.treeObj.empty(); settings[setting.treeId] = setting; //For some older browser,(e.g., ie6) if(typeof document.body.style.maxHeight === "undefined") { setting.view.expandSpeed = ""; } data.initRoot(setting); var root = data.getRoot(setting), childKey = setting.data.key.children; zNodes = zNodes ? tools.clone(tools.isArray(zNodes)? zNodes : [zNodes]) : []; if (setting.data.simpleData.enable) { root[childKey] = data.transformTozTreeFormat(setting, zNodes); } else { root[childKey] = zNodes; } data.initCache(setting); event.unbindTree(setting); event.bindTree(setting); event.unbindEvent(setting); event.bindEvent(setting); var zTreeTools = { setting : setting, addNodes : function(parentNode, newNodes, isSilent) { if (!newNodes) return null; if (!parentNode) parentNode = null; if (parentNode && !parentNode.isParent && setting.data.keep.leaf) return null; var xNewNodes = tools.clone(tools.isArray(newNodes)? newNodes: [newNodes]); function addCallback() { view.addNodes(setting, parentNode, xNewNodes, (isSilent==true)); } if (tools.canAsync(setting, parentNode)) { view.asyncNode(setting, parentNode, isSilent, addCallback); } else { addCallback(); } return xNewNodes; }, cancelSelectedNode : function(node) { view.cancelPreSelectedNode(this.setting, node); }, destroy : function() { view.destroy(this.setting); }, expandAll : function(expandFlag) { expandFlag = !!expandFlag; view.expandCollapseSonNode(this.setting, null, expandFlag, true); return expandFlag; }, expandNode : function(node, expandFlag, sonSign, focus, callbackFlag) { if (!node || !node.isParent) return null; if (expandFlag !== true && expandFlag !== false) { expandFlag = !node.open; } callbackFlag = !!callbackFlag; if (callbackFlag && expandFlag && (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false)) { return null; } else if (callbackFlag && !expandFlag && (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false)) { return null; } if (expandFlag && node.parentTId) { view.expandCollapseParentNode(this.setting, node.getParentNode(), expandFlag, false); } if (expandFlag === node.open && !sonSign) { return null; } data.getRoot(setting).expandTriggerFlag = callbackFlag; if (sonSign) { view.expandCollapseSonNode(this.setting, node, expandFlag, true, function() { if (focus !== false) {try{$("#" + node.tId).focus().blur();}catch(e){}} }); } else { node.open = !expandFlag; view.switchNode(this.setting, node); if (focus !== false) {try{$("#" + node.tId).focus().blur();}catch(e){}} } return expandFlag; }, getNodes : function() { return data.getNodes(this.setting); }, getNodeByParam : function(key, value, parentNode) { if (!key) return null; return data.getNodeByParam(this.setting, parentNode?parentNode[this.setting.data.key.children]:data.getNodes(this.setting), key, value); }, getNodeByTId : function(tId) { return data.getNodeCache(this.setting, tId); }, getNodesByParam : function(key, value, parentNode) { if (!key) return null; return data.getNodesByParam(this.setting, parentNode?parentNode[this.setting.data.key.children]:data.getNodes(this.setting), key, value); }, getNodesByParamFuzzy : function(key, value, parentNode) { if (!key) return null; return data.getNodesByParamFuzzy(this.setting, parentNode?parentNode[this.setting.data.key.children]:data.getNodes(this.setting), key, value); }, getNodesByFilter: function(filter, isSingle, parentNode, invokeParam) { isSingle = !!isSingle; if (!filter || (typeof filter != "function")) return (isSingle ? null : []); return data.getNodesByFilter(this.setting, parentNode?parentNode[this.setting.data.key.children]:data.getNodes(this.setting), filter, isSingle, invokeParam); }, getNodeIndex : function(node) { if (!node) return null; var childKey = setting.data.key.children, parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(this.setting); for (var i=0, l = parentNode[childKey].length; i < l; i++) { if (parentNode[childKey][i] == node) return i; } return -1; }, getSelectedNodes : function() { var r = [], list = data.getRoot(this.setting).curSelectedList; for (var i=0, l=list.length; i 0) { view.createNodes(setting, 0, root[childKey]); } else if (setting.async.enable && setting.async.url && setting.async.url !== '') { view.asyncNode(setting); } return zTreeTools; } }; var zt = $.fn.zTree, consts = zt.consts; })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.excheck-3.5.js ================================================ /* * JQuery zTree excheck 3.5.12 * http://zTree.me/ * * Copyright (c) 2010 Hunter.z * * Licensed same as jquery - MIT License * http://www.opensource.org/licenses/mit-license.php * * email: hunter.z@263.net * Date: 2013-03-11 */ (function($){ //default consts of excheck var _consts = { event: { CHECK: "ztree_check" }, id: { CHECK: "_check" }, checkbox: { STYLE: "checkbox", DEFAULT: "chk", DISABLED: "disable", FALSE: "false", TRUE: "true", FULL: "full", PART: "part", FOCUS: "focus" }, radio: { STYLE: "radio", TYPE_ALL: "all", TYPE_LEVEL: "level" } }, //default setting of excheck _setting = { check: { enable: false, autoCheckTrigger: false, chkStyle: _consts.checkbox.STYLE, nocheckInherit: false, chkDisabledInherit: false, radioType: _consts.radio.TYPE_LEVEL, chkboxType: { "Y": "ps", "N": "ps" } }, data: { key: { checked: "checked" } }, callback: { beforeCheck:null, onCheck:null } }, //default root of excheck _initRoot = function (setting) { var r = data.getRoot(setting); r.radioCheckedList = []; }, //default cache of excheck _initCache = function(treeId) {}, //default bind event of excheck _bindEvent = function(setting) { var o = setting.treeObj, c = consts.event; o.bind(c.CHECK, function (event, srcEvent, treeId, node) { tools.apply(setting.callback.onCheck, [!!srcEvent?srcEvent : event, treeId, node]); }); }, _unbindEvent = function(setting) { var o = setting.treeObj, c = consts.event; o.unbind(c.CHECK); }, //default event proxy of excheck _eventProxy = function(e) { var target = e.target, setting = data.getSetting(e.data.treeId), tId = "", node = null, nodeEventType = "", treeEventType = "", nodeEventCallback = null, treeEventCallback = null; if (tools.eqs(e.type, "mouseover")) { if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) { tId = target.parentNode.id; nodeEventType = "mouseoverCheck"; } } else if (tools.eqs(e.type, "mouseout")) { if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) { tId = target.parentNode.id; nodeEventType = "mouseoutCheck"; } } else if (tools.eqs(e.type, "click")) { if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode"+ consts.id.CHECK) !== null) { tId = target.parentNode.id; nodeEventType = "checkNode"; } } if (tId.length>0) { node = data.getNodeCache(setting, tId); switch (nodeEventType) { case "checkNode" : nodeEventCallback = _handler.onCheckNode; break; case "mouseoverCheck" : nodeEventCallback = _handler.onMouseoverCheck; break; case "mouseoutCheck" : nodeEventCallback = _handler.onMouseoutCheck; break; } } var proxyResult = { stop: false, node: node, nodeEventType: nodeEventType, nodeEventCallback: nodeEventCallback, treeEventType: treeEventType, treeEventCallback: treeEventCallback }; return proxyResult }, //default init node of excheck _initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { if (!n) return; var checkedKey = setting.data.key.checked; if (typeof n[checkedKey] == "string") n[checkedKey] = tools.eqs(n[checkedKey], "true"); n[checkedKey] = !!n[checkedKey]; n.checkedOld = n[checkedKey]; if (typeof n.nocheck == "string") n.nocheck = tools.eqs(n.nocheck, "true"); n.nocheck = !!n.nocheck || (setting.check.nocheckInherit && parentNode && !!parentNode.nocheck); if (typeof n.chkDisabled == "string") n.chkDisabled = tools.eqs(n.chkDisabled, "true"); n.chkDisabled = !!n.chkDisabled || (setting.check.chkDisabledInherit && parentNode && !!parentNode.chkDisabled); if (typeof n.halfCheck == "string") n.halfCheck = tools.eqs(n.halfCheck, "true"); n.halfCheck = !!n.halfCheck; n.check_Child_State = -1; n.check_Focus = false; n.getCheckStatus = function() {return data.getCheckStatus(setting, n);}; }, //add dom for check _beforeA = function(setting, node, html) { var checkedKey = setting.data.key.checked; if (setting.check.enable) { data.makeChkFlag(setting, node); if (setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL && node[checkedKey] ) { var r = data.getRoot(setting); r.radioCheckedList.push(node); } html.push(""); } }, //update zTreeObj, add method of check _zTreeTools = function(setting, zTreeTools) { zTreeTools.checkNode = function(node, checked, checkTypeFlag, callbackFlag) { var checkedKey = this.setting.data.key.checked; if (node.chkDisabled === true) return; if (checked !== true && checked !== false) { checked = !node[checkedKey]; } callbackFlag = !!callbackFlag; if (node[checkedKey] === checked && !checkTypeFlag) { return; } else if (callbackFlag && tools.apply(this.setting.callback.beforeCheck, [this.setting.treeId, node], true) == false) { return; } if (tools.uCanDo(this.setting) && this.setting.check.enable && node.nocheck !== true) { node[checkedKey] = checked; var checkObj = $("#" + node.tId + consts.id.CHECK); if (checkTypeFlag || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node); view.setChkClass(this.setting, checkObj, node); view.repairParentChkClassWithSelf(this.setting, node); if (callbackFlag) { setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); } } } zTreeTools.checkAllNodes = function(checked) { view.repairAllChk(this.setting, !!checked); } zTreeTools.getCheckedNodes = function(checked) { var childKey = this.setting.data.key.children; checked = (checked !== false); return data.getTreeCheckedNodes(this.setting, data.getRoot(setting)[childKey], checked); } zTreeTools.getChangeCheckedNodes = function() { var childKey = this.setting.data.key.children; return data.getTreeChangeCheckedNodes(this.setting, data.getRoot(setting)[childKey]); } zTreeTools.setChkDisabled = function(node, disabled, inheritParent, inheritChildren) { disabled = !!disabled; inheritParent = !!inheritParent; inheritChildren = !!inheritChildren; view.repairSonChkDisabled(this.setting, node, disabled, inheritChildren); view.repairParentChkDisabled(this.setting, node.getParentNode(), disabled, inheritParent); } var _updateNode = zTreeTools.updateNode; zTreeTools.updateNode = function(node, checkTypeFlag) { if (_updateNode) _updateNode.apply(zTreeTools, arguments); if (!node || !this.setting.check.enable) return; var nObj = $("#" + node.tId); if (nObj.get(0) && tools.uCanDo(this.setting)) { var checkObj = $("#" + node.tId + consts.id.CHECK); if (checkTypeFlag == true || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node); view.setChkClass(this.setting, checkObj, node); view.repairParentChkClassWithSelf(this.setting, node); } } }, //method of operate data _data = { getRadioCheckedList: function(setting) { var checkedList = data.getRoot(setting).radioCheckedList; for (var i=0, j=checkedList.length; i -1 && node.check_Child_State < 2) : (node.check_Child_State > 0))) }; return r; }, getTreeCheckedNodes: function(setting, nodes, checked, results) { if (!nodes) return []; var childKey = setting.data.key.children, checkedKey = setting.data.key.checked, onlyOne = (checked && setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL); results = !results ? [] : results; for (var i = 0, l = nodes.length; i < l; i++) { if (nodes[i].nocheck !== true && nodes[i].chkDisabled !== true && nodes[i][checkedKey] == checked) { results.push(nodes[i]); if(onlyOne) { break; } } data.getTreeCheckedNodes(setting, nodes[i][childKey], checked, results); if(onlyOne && results.length > 0) { break; } } return results; }, getTreeChangeCheckedNodes: function(setting, nodes, results) { if (!nodes) return []; var childKey = setting.data.key.children, checkedKey = setting.data.key.checked; results = !results ? [] : results; for (var i = 0, l = nodes.length; i < l; i++) { if (nodes[i].nocheck !== true && nodes[i].chkDisabled !== true && nodes[i][checkedKey] != nodes[i].checkedOld) { results.push(nodes[i]); } data.getTreeChangeCheckedNodes(setting, nodes[i][childKey], results); } return results; }, makeChkFlag: function(setting, node) { if (!node) return; var childKey = setting.data.key.children, checkedKey = setting.data.key.checked, chkFlag = -1; if (node[childKey]) { for (var i = 0, l = node[childKey].length; i < l; i++) { var cNode = node[childKey][i]; var tmp = -1; if (setting.check.chkStyle == consts.radio.STYLE) { if (cNode.nocheck === true || cNode.chkDisabled === true) { tmp = cNode.check_Child_State; } else if (cNode.halfCheck === true) { tmp = 2; } else if (cNode[checkedKey]) { tmp = 2; } else { tmp = cNode.check_Child_State > 0 ? 2:0; } if (tmp == 2) { chkFlag = 2; break; } else if (tmp == 0){ chkFlag = 0; } } else if (setting.check.chkStyle == consts.checkbox.STYLE) { if (cNode.nocheck === true || cNode.chkDisabled === true) { tmp = cNode.check_Child_State; } else if (cNode.halfCheck === true) { tmp = 1; } else if (cNode[checkedKey] ) { tmp = (cNode.check_Child_State === -1 || cNode.check_Child_State === 2) ? 2 : 1; } else { tmp = (cNode.check_Child_State > 0) ? 1 : 0; } if (tmp === 1) { chkFlag = 1; break; } else if (tmp === 2 && chkFlag > -1 && i > 0 && tmp !== chkFlag) { chkFlag = 1; break; } else if (chkFlag === 2 && tmp > -1 && tmp < 2) { chkFlag = 1; break; } else if (tmp > -1) { chkFlag = tmp; } } } } node.check_Child_State = chkFlag; } }, //method of event proxy _event = { }, //method of event handler _handler = { onCheckNode: function (event, node) { if (node.chkDisabled === true) return false; var setting = data.getSetting(event.data.treeId), checkedKey = setting.data.key.checked; if (tools.apply(setting.callback.beforeCheck, [setting.treeId, node], true) == false) return true; node[checkedKey] = !node[checkedKey]; view.checkNodeRelation(setting, node); var checkObj = $("#" + node.tId + consts.id.CHECK); view.setChkClass(setting, checkObj, node); view.repairParentChkClassWithSelf(setting, node); setting.treeObj.trigger(consts.event.CHECK, [event, setting.treeId, node]); return true; }, onMouseoverCheck: function(event, node) { if (node.chkDisabled === true) return false; var setting = data.getSetting(event.data.treeId), checkObj = $("#" + node.tId + consts.id.CHECK); node.check_Focus = true; view.setChkClass(setting, checkObj, node); return true; }, onMouseoutCheck: function(event, node) { if (node.chkDisabled === true) return false; var setting = data.getSetting(event.data.treeId), checkObj = $("#" + node.tId + consts.id.CHECK); node.check_Focus = false; view.setChkClass(setting, checkObj, node); return true; } }, //method of tools for zTree _tools = { }, //method of operate ztree dom _view = { checkNodeRelation: function(setting, node) { var pNode, i, l, childKey = setting.data.key.children, checkedKey = setting.data.key.checked, r = consts.radio; if (setting.check.chkStyle == r.STYLE) { var checkedList = data.getRadioCheckedList(setting); if (node[checkedKey]) { if (setting.check.radioType == r.TYPE_ALL) { for (i = checkedList.length-1; i >= 0; i--) { pNode = checkedList[i]; pNode[checkedKey] = false; checkedList.splice(i, 1); view.setChkClass(setting, $("#" + pNode.tId + consts.id.CHECK), pNode); if (pNode.parentTId != node.parentTId) { view.repairParentChkClassWithSelf(setting, pNode); } } checkedList.push(node); } else { var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting); for (i = 0, l = parentNode[childKey].length; i < l; i++) { pNode = parentNode[childKey][i]; if (pNode[checkedKey] && pNode != node) { pNode[checkedKey] = false; view.setChkClass(setting, $("#" + pNode.tId + consts.id.CHECK), pNode); } } } } else if (setting.check.radioType == r.TYPE_ALL) { for (i = 0, l = checkedList.length; i < l; i++) { if (node == checkedList[i]) { checkedList.splice(i, 1); break; } } } } else { if (node[checkedKey] && (!node[childKey] || node[childKey].length==0 || setting.check.chkboxType.Y.indexOf("s") > -1)) { view.setSonNodeCheckBox(setting, node, true); } if (!node[checkedKey] && (!node[childKey] || node[childKey].length==0 || setting.check.chkboxType.N.indexOf("s") > -1)) { view.setSonNodeCheckBox(setting, node, false); } if (node[checkedKey] && setting.check.chkboxType.Y.indexOf("p") > -1) { view.setParentNodeCheckBox(setting, node, true); } if (!node[checkedKey] && setting.check.chkboxType.N.indexOf("p") > -1) { view.setParentNodeCheckBox(setting, node, false); } } }, makeChkClass: function(setting, node) { var checkedKey = setting.data.key.checked, c = consts.checkbox, r = consts.radio, fullStyle = ""; if (node.chkDisabled === true) { fullStyle = c.DISABLED; } else if (node.halfCheck) { fullStyle = c.PART; } else if (setting.check.chkStyle == r.STYLE) { fullStyle = (node.check_Child_State < 1)? c.FULL:c.PART; } else { fullStyle = node[checkedKey] ? ((node.check_Child_State === 2 || node.check_Child_State === -1) ? c.FULL:c.PART) : ((node.check_Child_State < 1)? c.FULL:c.PART); } var chkName = setting.check.chkStyle + "_" + (node[checkedKey] ? c.TRUE : c.FALSE) + "_" + fullStyle; chkName = (node.check_Focus && node.chkDisabled !== true) ? chkName + "_" + c.FOCUS : chkName; return consts.className.BUTTON + " " + c.DEFAULT + " " + chkName; }, repairAllChk: function(setting, checked) { if (setting.check.enable && setting.check.chkStyle === consts.checkbox.STYLE) { var checkedKey = setting.data.key.checked, childKey = setting.data.key.children, root = data.getRoot(setting); for (var i = 0, l = root[childKey].length; i 0) { view.repairParentChkClass(setting, node[childKey][0]); } else { view.repairParentChkClass(setting, node); } }, repairSonChkDisabled: function(setting, node, chkDisabled, inherit) { if (!node) return; var childKey = setting.data.key.children; if (node.chkDisabled != chkDisabled) { node.chkDisabled = chkDisabled; } view.repairChkClass(setting, node); if (node[childKey] && inherit) { for (var i = 0, l = node[childKey].length; i < l; i++) { var sNode = node[childKey][i]; view.repairSonChkDisabled(setting, sNode, chkDisabled, inherit); } } }, repairParentChkDisabled: function(setting, node, chkDisabled, inherit) { if (!node) return; if (node.chkDisabled != chkDisabled && inherit) { node.chkDisabled = chkDisabled; } view.repairChkClass(setting, node); view.repairParentChkDisabled(setting, node.getParentNode(), chkDisabled, inherit); }, setChkClass: function(setting, obj, node) { if (!obj) return; if (node.nocheck === true) { obj.hide(); } else { obj.show(); } obj.removeClass(); obj.addClass(view.makeChkClass(setting, node)); }, setParentNodeCheckBox: function(setting, node, value, srcNode) { var childKey = setting.data.key.children, checkedKey = setting.data.key.checked, checkObj = $("#" + node.tId + consts.id.CHECK); if (!srcNode) srcNode = node; data.makeChkFlag(setting, node); if (node.nocheck !== true && node.chkDisabled !== true) { node[checkedKey] = value; view.setChkClass(setting, checkObj, node); if (setting.check.autoCheckTrigger && node != srcNode) { setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); } } if (node.parentTId) { var pSign = true; if (!value) { var pNodes = node.getParentNode()[childKey]; for (var i = 0, l = pNodes.length; i < l; i++) { if ((pNodes[i].nocheck !== true && pNodes[i].chkDisabled !== true && pNodes[i][checkedKey]) || ((pNodes[i].nocheck === true || pNodes[i].chkDisabled === true) && pNodes[i].check_Child_State > 0)) { pSign = false; break; } } } if (pSign) { view.setParentNodeCheckBox(setting, node.getParentNode(), value, srcNode); } } }, setSonNodeCheckBox: function(setting, node, value, srcNode) { if (!node) return; var childKey = setting.data.key.children, checkedKey = setting.data.key.checked, checkObj = $("#" + node.tId + consts.id.CHECK); if (!srcNode) srcNode = node; var hasDisable = false; if (node[childKey]) { for (var i = 0, l = node[childKey].length; i < l && node.chkDisabled !== true; i++) { var sNode = node[childKey][i]; view.setSonNodeCheckBox(setting, sNode, value, srcNode); if (sNode.chkDisabled === true) hasDisable = true; } } if (node != data.getRoot(setting) && node.chkDisabled !== true) { if (hasDisable && node.nocheck !== true) { data.makeChkFlag(setting, node); } if (node.nocheck !== true && node.chkDisabled !== true) { node[checkedKey] = value; if (!hasDisable) node.check_Child_State = (node[childKey] && node[childKey].length > 0) ? (value ? 2 : 0) : -1; } else { node.check_Child_State = -1; } view.setChkClass(setting, checkObj, node); if (setting.check.autoCheckTrigger && node != srcNode && node.nocheck !== true && node.chkDisabled !== true) { setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]); } } } }, _z = { tools: _tools, view: _view, event: _event, data: _data }; $.extend(true, $.fn.zTree.consts, _consts); $.extend(true, $.fn.zTree._z, _z); var zt = $.fn.zTree, tools = zt._z.tools, consts = zt.consts, view = zt._z.view, data = zt._z.data, event = zt._z.event; data.exSetting(_setting); data.addInitBind(_bindEvent); data.addInitUnBind(_unbindEvent); data.addInitCache(_initCache); data.addInitNode(_initNode); data.addInitProxy(_eventProxy); data.addInitRoot(_initRoot); data.addBeforeA(_beforeA); data.addZTreeTools(_zTreeTools); var _createNodes = view.createNodes; view.createNodes = function(setting, level, nodes, parentNode) { if (_createNodes) _createNodes.apply(view, arguments); if (!nodes) return; view.repairParentChkClassWithSelf(setting, parentNode); } var _removeNode = view.removeNode; view.removeNode = function(setting, node) { var parentNode = node.getParentNode(); if (_removeNode) _removeNode.apply(view, arguments); if (!node || !parentNode) return; view.repairChkClass(setting, parentNode); view.repairParentChkClass(setting, parentNode); } var _appendNodes = view.appendNodes; view.appendNodes = function(setting, level, nodes, parentNode, initFlag, openFlag) { var html = ""; if (_appendNodes) { html = _appendNodes.apply(view, arguments); } if (parentNode) { data.makeChkFlag(setting, parentNode); } return html; } })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/js/jquery.ztree.exedit-3.5.js ================================================ /* * JQuery zTree exedit 3.5.12 * http://zTree.me/ * * Copyright (c) 2010 Hunter.z * * Licensed same as jquery - MIT License * http://www.opensource.org/licenses/mit-license.php * * email: hunter.z@263.net * Date: 2013-03-11 */ (function($){ //default consts of exedit var _consts = { event: { DRAG: "ztree_drag", DROP: "ztree_drop", REMOVE: "ztree_remove", RENAME: "ztree_rename" }, id: { EDIT: "_edit", INPUT: "_input", REMOVE: "_remove" }, move: { TYPE_INNER: "inner", TYPE_PREV: "prev", TYPE_NEXT: "next" }, node: { CURSELECTED_EDIT: "curSelectedNode_Edit", TMPTARGET_TREE: "tmpTargetzTree", TMPTARGET_NODE: "tmpTargetNode" } }, //default setting of exedit _setting = { edit: { enable: false, editNameSelectAll: false, showRemoveBtn: true, showRenameBtn: true, removeTitle: "remove", renameTitle: "rename", drag: { autoExpandTrigger: false, isCopy: true, isMove: true, prev: true, next: true, inner: true, minMoveSize: 5, borderMax: 10, borderMin: -5, maxShowNodeNum: 5, autoOpenTime: 500 } }, view: { addHoverDom: null, removeHoverDom: null }, callback: { beforeDrag:null, beforeDragOpen:null, beforeDrop:null, beforeEditName:null, beforeRename:null, onDrag:null, onDrop:null, onRename:null } }, //default root of exedit _initRoot = function (setting) { var r = data.getRoot(setting); r.curEditNode = null; r.curEditInput = null; r.curHoverNode = null; r.dragFlag = 0; r.dragNodeShowBefore = []; r.dragMaskList = new Array(); r.showHoverDom = true; }, //default cache of exedit _initCache = function(treeId) {}, //default bind event of exedit _bindEvent = function(setting) { var o = setting.treeObj; var c = consts.event; o.bind(c.RENAME, function (event, treeId, treeNode) { tools.apply(setting.callback.onRename, [event, treeId, treeNode]); }); o.bind(c.REMOVE, function (event, treeId, treeNode) { tools.apply(setting.callback.onRemove, [event, treeId, treeNode]); }); o.bind(c.DRAG, function (event, srcEvent, treeId, treeNodes) { tools.apply(setting.callback.onDrag, [srcEvent, treeId, treeNodes]); }); o.bind(c.DROP, function (event, srcEvent, treeId, treeNodes, targetNode, moveType, isCopy) { tools.apply(setting.callback.onDrop, [srcEvent, treeId, treeNodes, targetNode, moveType, isCopy]); }); }, _unbindEvent = function(setting) { var o = setting.treeObj; var c = consts.event; o.unbind(c.RENAME); o.unbind(c.REMOVE); o.unbind(c.DRAG); o.unbind(c.DROP); }, //default event proxy of exedit _eventProxy = function(e) { var target = e.target, setting = data.getSetting(e.data.treeId), relatedTarget = e.relatedTarget, tId = "", node = null, nodeEventType = "", treeEventType = "", nodeEventCallback = null, treeEventCallback = null, tmp = null; if (tools.eqs(e.type, "mouseover")) { tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) { tId = tmp.parentNode.id; nodeEventType = "hoverOverNode"; } } else if (tools.eqs(e.type, "mouseout")) { tmp = tools.getMDom(setting, relatedTarget, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (!tmp) { tId = "remove"; nodeEventType = "hoverOutNode"; } } else if (tools.eqs(e.type, "mousedown")) { tmp = tools.getMDom(setting, target, [{tagName:"a", attrName:"treeNode"+consts.id.A}]); if (tmp) { tId = tmp.parentNode.id; nodeEventType = "mousedownNode"; } } if (tId.length>0) { node = data.getNodeCache(setting, tId); switch (nodeEventType) { case "mousedownNode" : nodeEventCallback = _handler.onMousedownNode; break; case "hoverOverNode" : nodeEventCallback = _handler.onHoverOverNode; break; case "hoverOutNode" : nodeEventCallback = _handler.onHoverOutNode; break; } } var proxyResult = { stop: false, node: node, nodeEventType: nodeEventType, nodeEventCallback: nodeEventCallback, treeEventType: treeEventType, treeEventCallback: treeEventCallback }; return proxyResult }, //default init node of exedit _initNode = function(setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) { if (!n) return; n.isHover = false; n.editNameFlag = false; }, //update zTreeObj, add method of edit _zTreeTools = function(setting, zTreeTools) { zTreeTools.cancelEditName = function(newName) { var root = data.getRoot(setting), nameKey = setting.data.key.name, node = root.curEditNode; if (!root.curEditNode) return; view.cancelCurEditNode(setting, newName?newName:node[nameKey]); } zTreeTools.copyNode = function(targetNode, node, moveType, isSilent) { if (!node) return null; if (targetNode && !targetNode.isParent && setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) return null; var newNode = tools.clone(node); if (!targetNode) { targetNode = null; moveType = consts.move.TYPE_INNER; } if (moveType == consts.move.TYPE_INNER) { function copyCallback() { view.addNodes(setting, targetNode, [newNode], isSilent); } if (tools.canAsync(setting, targetNode)) { view.asyncNode(setting, targetNode, isSilent, copyCallback); } else { copyCallback(); } } else { view.addNodes(setting, targetNode.parentNode, [newNode], isSilent); view.moveNode(setting, targetNode, newNode, moveType, false, isSilent); } return newNode; } zTreeTools.editName = function(node) { if (!node || !node.tId || node !== data.getNodeCache(setting, node.tId)) return; if (node.parentTId) view.expandCollapseParentNode(setting, node.getParentNode(), true); view.editNode(setting, node) } zTreeTools.moveNode = function(targetNode, node, moveType, isSilent) { if (!node) return node; if (targetNode && !targetNode.isParent && setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) { return null; } else if (targetNode && ((node.parentTId == targetNode.tId && moveType == consts.move.TYPE_INNER) || $("#" + node.tId).find("#" + targetNode.tId).length > 0)) { return null; } else if (!targetNode) { targetNode = null; } function moveCallback() { view.moveNode(setting, targetNode, node, moveType, false, isSilent); } if (tools.canAsync(setting, targetNode) && moveType === consts.move.TYPE_INNER) { view.asyncNode(setting, targetNode, isSilent, moveCallback); } else { moveCallback(); } return node; } zTreeTools.setEditable = function(editable) { setting.edit.enable = editable; return this.refresh(); } }, //method of operate data _data = { setSonNodeLevel: function(setting, parentNode, node) { if (!node) return; var childKey = setting.data.key.children; node.level = (parentNode)? parentNode.level + 1 : 0; if (!node[childKey]) return; for (var i = 0, l = node[childKey].length; i < l; i++) { if (node[childKey][i]) data.setSonNodeLevel(setting, node, node[childKey][i]); } } }, //method of event proxy _event = { }, //method of event handler _handler = { onHoverOverNode: function(event, node) { var setting = data.getSetting(event.data.treeId), root = data.getRoot(setting); if (root.curHoverNode != node) { _handler.onHoverOutNode(event); } root.curHoverNode = node; view.addHoverDom(setting, node); }, onHoverOutNode: function(event, node) { var setting = data.getSetting(event.data.treeId), root = data.getRoot(setting); if (root.curHoverNode && !data.isSelectedNode(setting, root.curHoverNode)) { view.removeTreeDom(setting, root.curHoverNode); root.curHoverNode = null; } }, onMousedownNode: function(eventMouseDown, _node) { var i,l, setting = data.getSetting(eventMouseDown.data.treeId), root = data.getRoot(setting); //right click can't drag & drop if (eventMouseDown.button == 2 || !setting.edit.enable || (!setting.edit.drag.isCopy && !setting.edit.drag.isMove)) return true; //input of edit node name can't drag & drop var target = eventMouseDown.target, _nodes = data.getRoot(setting).curSelectedList, nodes = []; if (!data.isSelectedNode(setting, _node)) { nodes = [_node]; } else { for (i=0, l=_nodes.length; i1) { var pNodes = nodes[0].parentTId ? nodes[0].getParentNode()[childKey] : data.getNodes(setting); tmpNodes = []; for (i=0, l=pNodes.length; i -1 && (lastIndex+1) !== i) { isOrder = false; } tmpNodes.push(pNodes[i]); lastIndex = i; } if (nodes.length === tmpNodes.length) { nodes = tmpNodes; break; } } } if (isOrder) { preNode = nodes[0].getPreNode(); nextNode = nodes[nodes.length-1].getNextNode(); } //set node in selected curNode = $("
          "); for (i=0, l=nodes.length; i0); view.removeTreeDom(setting, tmpNode); tmpDom = $("
        • "); tmpDom.append($("#" + tmpNode.tId + consts.id.A).clone()); tmpDom.css("padding", "0"); tmpDom.children("#" + tmpNode.tId + consts.id.A).removeClass(consts.node.CURSELECTED); curNode.append(tmpDom); if (i == setting.edit.drag.maxShowNodeNum-1) { tmpDom = $("
        • ...
        • "); curNode.append(tmpDom); break; } } curNode.attr("id", nodes[0].tId + consts.id.UL + "_tmp"); curNode.addClass(setting.treeObj.attr("class")); curNode.appendTo("body"); tmpArrow = $(""); tmpArrow.attr("id", "zTreeMove_arrow_tmp"); tmpArrow.appendTo("body"); setting.treeObj.trigger(consts.event.DRAG, [event, setting.treeId, nodes]); } if (root.dragFlag == 1) { if (tmpTarget && tmpArrow.attr("id") == event.target.id && tmpTargetNodeId && (event.clientX + doc.scrollLeft()+2) > ($("#" + tmpTargetNodeId + consts.id.A, tmpTarget).offset().left)) { var xT = $("#" + tmpTargetNodeId + consts.id.A, tmpTarget); event.target = (xT.length > 0) ? xT.get(0) : event.target; } else if (tmpTarget) { tmpTarget.removeClass(consts.node.TMPTARGET_TREE); if (tmpTargetNodeId) $("#" + tmpTargetNodeId + consts.id.A, tmpTarget).removeClass(consts.node.TMPTARGET_NODE + "_" + consts.move.TYPE_PREV) .removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_NEXT).removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_INNER); } tmpTarget = null; tmpTargetNodeId = null; //judge drag & drop in multi ztree isOtherTree = false; targetSetting = setting; var settings = data.getSettings(); for (var s in settings) { if (settings[s].treeId && settings[s].edit.enable && settings[s].treeId != setting.treeId && (event.target.id == settings[s].treeId || $(event.target).parents("#" + settings[s].treeId).length>0)) { isOtherTree = true; targetSetting = settings[s]; } } var docScrollTop = doc.scrollTop(), docScrollLeft = doc.scrollLeft(), treeOffset = targetSetting.treeObj.offset(), scrollHeight = targetSetting.treeObj.get(0).scrollHeight, scrollWidth = targetSetting.treeObj.get(0).scrollWidth, dTop = (event.clientY + docScrollTop - treeOffset.top), dBottom = (targetSetting.treeObj.height() + treeOffset.top - event.clientY - docScrollTop), dLeft = (event.clientX + docScrollLeft - treeOffset.left), dRight = (targetSetting.treeObj.width() + treeOffset.left - event.clientX - docScrollLeft), isTop = (dTop < setting.edit.drag.borderMax && dTop > setting.edit.drag.borderMin), isBottom = (dBottom < setting.edit.drag.borderMax && dBottom > setting.edit.drag.borderMin), isLeft = (dLeft < setting.edit.drag.borderMax && dLeft > setting.edit.drag.borderMin), isRight = (dRight < setting.edit.drag.borderMax && dRight > setting.edit.drag.borderMin), isTreeInner = dTop > setting.edit.drag.borderMin && dBottom > setting.edit.drag.borderMin && dLeft > setting.edit.drag.borderMin && dRight > setting.edit.drag.borderMin, isTreeTop = (isTop && targetSetting.treeObj.scrollTop() <= 0), isTreeBottom = (isBottom && (targetSetting.treeObj.scrollTop() + targetSetting.treeObj.height()+10) >= scrollHeight), isTreeLeft = (isLeft && targetSetting.treeObj.scrollLeft() <= 0), isTreeRight = (isRight && (targetSetting.treeObj.scrollLeft() + targetSetting.treeObj.width()+10) >= scrollWidth); if (event.target.id && targetSetting.treeObj.find("#" + event.target.id).length > 0) { //get node
        • dom var targetObj = event.target; while (targetObj && targetObj.tagName && !tools.eqs(targetObj.tagName, "li") && targetObj.id != targetSetting.treeId) { targetObj = targetObj.parentNode; } var canMove = true; //don't move to self or children of self for (i=0, l=nodes.length; i 0) { canMove = false; break; } } if (canMove) { if (event.target.id && (event.target.id == (targetObj.id + consts.id.A) || $(event.target).parents("#" + targetObj.id + consts.id.A).length > 0)) { tmpTarget = $(targetObj); tmpTargetNodeId = targetObj.id; } } } //the mouse must be in zTree tmpNode = nodes[0]; if (isTreeInner && (event.target.id == targetSetting.treeId || $(event.target).parents("#" + targetSetting.treeId).length>0)) { //judge mouse move in root of ztree if (!tmpTarget && (event.target.id == targetSetting.treeId || isTreeTop || isTreeBottom || isTreeLeft || isTreeRight) && (isOtherTree || (!isOtherTree && tmpNode.parentTId))) { tmpTarget = targetSetting.treeObj; } //auto scroll top if (isTop) { targetSetting.treeObj.scrollTop(targetSetting.treeObj.scrollTop()-10); } else if (isBottom) { targetSetting.treeObj.scrollTop(targetSetting.treeObj.scrollTop()+10); } if (isLeft) { targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()-10); } else if (isRight) { targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()+10); } //auto scroll left if (tmpTarget && tmpTarget != targetSetting.treeObj && tmpTarget.offset().left < targetSetting.treeObj.offset().left) { targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft()+ tmpTarget.offset().left - targetSetting.treeObj.offset().left); } } curNode.css({ "top": (event.clientY + docScrollTop + 3) + "px", "left": (event.clientX + docScrollLeft + 3) + "px" }); var dX = 0; var dY = 0; if (tmpTarget && tmpTarget.attr("id")!=targetSetting.treeId) { var tmpTargetNode = tmpTargetNodeId == null ? null: data.getNodeCache(targetSetting, tmpTargetNodeId), isCopy = (event.ctrlKey && setting.edit.drag.isMove && setting.edit.drag.isCopy) || (!setting.edit.drag.isMove && setting.edit.drag.isCopy), isPrev = !!(preNode && tmpTargetNodeId === preNode.tId), isNext = !!(nextNode && tmpTargetNodeId === nextNode.tId), isInner = (tmpNode.parentTId && tmpNode.parentTId == tmpTargetNodeId), canPrev = (isCopy || !isNext) && tools.apply(targetSetting.edit.drag.prev, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.prev), canNext = (isCopy || !isPrev) && tools.apply(targetSetting.edit.drag.next, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.next), canInner = (isCopy || !isInner) && !(targetSetting.data.keep.leaf && !tmpTargetNode.isParent) && tools.apply(targetSetting.edit.drag.inner, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.inner); if (!canPrev && !canNext && !canInner) { tmpTarget = null; tmpTargetNodeId = ""; moveType = consts.move.TYPE_INNER; tmpArrow.css({ "display":"none" }); if (window.zTreeMoveTimer) { clearTimeout(window.zTreeMoveTimer); window.zTreeMoveTargetNodeTId = null } } else { var tmpTargetA = $("#" + tmpTargetNodeId + consts.id.A, tmpTarget), tmpNextA = tmpTargetNode.isLastNode ? null : $("#" + tmpTargetNode.getNextNode().tId + consts.id.A, tmpTarget.next()), tmpTop = tmpTargetA.offset().top, tmpLeft = tmpTargetA.offset().left, prevPercent = canPrev ? (canInner ? 0.25 : (canNext ? 0.5 : 1) ) : -1, nextPercent = canNext ? (canInner ? 0.75 : (canPrev ? 0.5 : 0) ) : -1, dY_percent = (event.clientY + docScrollTop - tmpTop)/tmpTargetA.height(); if ((prevPercent==1 ||dY_percent<=prevPercent && dY_percent>=-.2) && canPrev) { dX = 1 - tmpArrow.width(); dY = tmpTop - tmpArrow.height()/2; moveType = consts.move.TYPE_PREV; } else if ((nextPercent==0 || dY_percent>=nextPercent && dY_percent<=1.2) && canNext) { dX = 1 - tmpArrow.width(); dY = (tmpNextA == null || (tmpTargetNode.isParent && tmpTargetNode.open)) ? (tmpTop + tmpTargetA.height() - tmpArrow.height()/2) : (tmpNextA.offset().top - tmpArrow.height()/2); moveType = consts.move.TYPE_NEXT; }else { dX = 5 - tmpArrow.width(); dY = tmpTop; moveType = consts.move.TYPE_INNER; } tmpArrow.css({ "display":"block", "top": dY + "px", "left": (tmpLeft + dX) + "px" }); tmpTargetA.addClass(consts.node.TMPTARGET_NODE + "_" + moveType); if (preTmpTargetNodeId != tmpTargetNodeId || preTmpMoveType != moveType) { startTime = (new Date()).getTime(); } if (tmpTargetNode && tmpTargetNode.isParent && moveType == consts.move.TYPE_INNER) { var startTimer = true; if (window.zTreeMoveTimer && window.zTreeMoveTargetNodeTId !== tmpTargetNode.tId) { clearTimeout(window.zTreeMoveTimer); window.zTreeMoveTargetNodeTId = null; } else if (window.zTreeMoveTimer && window.zTreeMoveTargetNodeTId === tmpTargetNode.tId) { startTimer = false; } if (startTimer) { window.zTreeMoveTimer = setTimeout(function() { if (moveType != consts.move.TYPE_INNER) return; if (tmpTargetNode && tmpTargetNode.isParent && !tmpTargetNode.open && (new Date()).getTime() - startTime > targetSetting.edit.drag.autoOpenTime && tools.apply(targetSetting.callback.beforeDragOpen, [targetSetting.treeId, tmpTargetNode], true)) { view.switchNode(targetSetting, tmpTargetNode); if (targetSetting.edit.drag.autoExpandTrigger) { targetSetting.treeObj.trigger(consts.event.EXPAND, [targetSetting.treeId, tmpTargetNode]); } } }, targetSetting.edit.drag.autoOpenTime+50); window.zTreeMoveTargetNodeTId = tmpTargetNode.tId; } } } } else { moveType = consts.move.TYPE_INNER; if (tmpTarget && tools.apply(targetSetting.edit.drag.inner, [targetSetting.treeId, nodes, null], !!targetSetting.edit.drag.inner)) { tmpTarget.addClass(consts.node.TMPTARGET_TREE); } else { tmpTarget = null; } tmpArrow.css({ "display":"none" }); if (window.zTreeMoveTimer) { clearTimeout(window.zTreeMoveTimer); window.zTreeMoveTargetNodeTId = null; } } preTmpTargetNodeId = tmpTargetNodeId; preTmpMoveType = moveType; } return false; } doc.bind("mouseup", _docMouseUp); function _docMouseUp(event) { if (window.zTreeMoveTimer) { clearTimeout(window.zTreeMoveTimer); window.zTreeMoveTargetNodeTId = null; } preTmpTargetNodeId = null; preTmpMoveType = null; doc.unbind("mousemove", _docMouseMove); doc.unbind("mouseup", _docMouseUp); doc.unbind("selectstart", _docSelect); $("body").css("cursor", "auto"); if (tmpTarget) { tmpTarget.removeClass(consts.node.TMPTARGET_TREE); if (tmpTargetNodeId) $("#" + tmpTargetNodeId + consts.id.A, tmpTarget).removeClass(consts.node.TMPTARGET_NODE + "_" + consts.move.TYPE_PREV) .removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_NEXT).removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_INNER); } tools.showIfameMask(setting, false); root.showHoverDom = true; if (root.dragFlag == 0) return; root.dragFlag = 0; var i, l, tmpNode; for (i=0, l=nodes.length; i0); } $("#" + newNodes[0].tId).focus().blur(); setting.treeObj.trigger(consts.event.DROP, [event, targetSetting.treeId, newNodes, dragTargetNode, moveType, isCopy]); } if (moveType == consts.move.TYPE_INNER && tools.canAsync(targetSetting, dragTargetNode)) { view.asyncNode(targetSetting, dragTargetNode, false, dropCallback); } else { dropCallback(); } } else { for (i=0, l=nodes.length; i0); } setting.treeObj.trigger(consts.event.DROP, [event, setting.treeId, nodes, null, null, null]); } } doc.bind("selectstart", _docSelect); function _docSelect() { return false; } //Avoid FireFox's Bug //If zTree Div CSS set 'overflow', so drag node outside of zTree, and event.target is error. if(eventMouseDown.preventDefault) { eventMouseDown.preventDefault(); } return true; } }, //method of tools for zTree _tools = { getAbs: function (obj) { var oRect = obj.getBoundingClientRect(); return [oRect.left,oRect.top] }, inputFocus: function(inputObj) { if (inputObj.get(0)) { inputObj.focus(); tools.setCursorPosition(inputObj.get(0), inputObj.val().length); } }, inputSelect: function(inputObj) { if (inputObj.get(0)) { inputObj.focus(); inputObj.select(); } }, setCursorPosition: function(obj, pos){ if(obj.setSelectionRange) { obj.focus(); obj.setSelectionRange(pos,pos); } else if (obj.createTextRange) { var range = obj.createTextRange(); range.collapse(true); range.moveEnd('character', pos); range.moveStart('character', pos); range.select(); } }, showIfameMask: function(setting, showSign) { var root = data.getRoot(setting); //clear full mask while (root.dragMaskList.length > 0) { root.dragMaskList[0].remove(); root.dragMaskList.shift(); } if (showSign) { //show mask var iframeList = $("iframe"); for (var i = 0, l = iframeList.length; i < l; i++) { var obj = iframeList.get(i), r = tools.getAbs(obj), dragMask = $("
          "); dragMask.appendTo("body"); root.dragMaskList.push(dragMask); } } } }, //method of operate ztree dom _view = { addEditBtn: function(setting, node) { if (node.editNameFlag || $("#" + node.tId + consts.id.EDIT).length > 0) { return; } if (!tools.apply(setting.edit.showRenameBtn, [setting.treeId, node], setting.edit.showRenameBtn)) { return; } var aObj = $("#" + node.tId + consts.id.A), editStr = ""; aObj.append(editStr); $("#" + node.tId + consts.id.EDIT).bind('click', function() { if (!tools.uCanDo(setting) || tools.apply(setting.callback.beforeEditName, [setting.treeId, node], true) == false) return false; view.editNode(setting, node); return false; } ).show(); }, addRemoveBtn: function(setting, node) { if (node.editNameFlag || $("#" + node.tId + consts.id.REMOVE).length > 0) { return; } if (!tools.apply(setting.edit.showRemoveBtn, [setting.treeId, node], setting.edit.showRemoveBtn)) { return; } var aObj = $("#" + node.tId + consts.id.A), removeStr = ""; aObj.append(removeStr); $("#" + node.tId + consts.id.REMOVE).bind('click', function() { if (!tools.uCanDo(setting) || tools.apply(setting.callback.beforeRemove, [setting.treeId, node], true) == false) return false; view.removeNode(setting, node); setting.treeObj.trigger(consts.event.REMOVE, [setting.treeId, node]); return false; } ).bind('mousedown', function(eventMouseDown) { return true; } ).show(); }, addHoverDom: function(setting, node) { if (data.getRoot(setting).showHoverDom) { node.isHover = true; if (setting.edit.enable) { view.addEditBtn(setting, node); view.addRemoveBtn(setting, node); } tools.apply(setting.view.addHoverDom, [setting.treeId, node]); } }, cancelCurEditNode: function (setting, forceName) { var root = data.getRoot(setting), nameKey = setting.data.key.name, node = root.curEditNode; if (node) { var inputObj = root.curEditInput; var newName = forceName ? forceName:inputObj.val(); if (!forceName && tools.apply(setting.callback.beforeRename, [setting.treeId, node, newName], true) === false) { return false; } else { node[nameKey] = newName ? newName:inputObj.val(); if (!forceName) { setting.treeObj.trigger(consts.event.RENAME, [setting.treeId, node]); } } var aObj = $("#" + node.tId + consts.id.A); aObj.removeClass(consts.node.CURSELECTED_EDIT); inputObj.unbind(); view.setNodeName(setting, node); node.editNameFlag = false; root.curEditNode = null; root.curEditInput = null; view.selectNode(setting, node, false); } root.noSelection = true; return true; }, editNode: function(setting, node) { var root = data.getRoot(setting); view.editNodeBlur = false; if (data.isSelectedNode(setting, node) && root.curEditNode == node && node.editNameFlag) { setTimeout(function() {tools.inputFocus(root.curEditInput);}, 0); return; } var nameKey = setting.data.key.name; node.editNameFlag = true; view.removeTreeDom(setting, node); view.cancelCurEditNode(setting); view.selectNode(setting, node, false); $("#" + node.tId + consts.id.SPAN).html(""); var inputObj = $("#" + node.tId + consts.id.INPUT); inputObj.attr("value", node[nameKey]); if (setting.edit.editNameSelectAll) { tools.inputSelect(inputObj); } else { tools.inputFocus(inputObj); } inputObj.bind('blur', function(event) { if (!view.editNodeBlur) { view.cancelCurEditNode(setting); } }).bind('keydown', function(event) { if (event.keyCode=="13") { view.editNodeBlur = true; view.cancelCurEditNode(setting, null, true); } else if (event.keyCode=="27") { view.cancelCurEditNode(setting, node[nameKey]); } }).bind('click', function(event) { return false; }).bind('dblclick', function(event) { return false; }); $("#" + node.tId + consts.id.A).addClass(consts.node.CURSELECTED_EDIT); root.curEditInput = inputObj; root.noSelection = false; root.curEditNode = node; }, moveNode: function(setting, targetNode, node, moveType, animateFlag, isSilent) { var root = data.getRoot(setting), childKey = setting.data.key.children; if (targetNode == node) return; if (setting.data.keep.leaf && targetNode && !targetNode.isParent && moveType == consts.move.TYPE_INNER) return; var oldParentNode = (node.parentTId ? node.getParentNode(): root), targetNodeIsRoot = (targetNode === null || targetNode == root); if (targetNodeIsRoot && targetNode === null) targetNode = root; if (targetNodeIsRoot) moveType = consts.move.TYPE_INNER; var targetParentNode = (targetNode.parentTId ? targetNode.getParentNode() : root); if (moveType != consts.move.TYPE_PREV && moveType != consts.move.TYPE_NEXT) { moveType = consts.move.TYPE_INNER; } if (moveType == consts.move.TYPE_INNER) { if (targetNodeIsRoot) { //parentTId of root node is null node.parentTId = null; } else { if (!targetNode.isParent) { targetNode.isParent = true; targetNode.open = !!targetNode.open; view.setNodeLineIcos(setting, targetNode); } node.parentTId = targetNode.tId; } } //move node Dom var targetObj, target_ulObj; if (targetNodeIsRoot) { targetObj = setting.treeObj; target_ulObj = targetObj; } else { if (!isSilent && moveType == consts.move.TYPE_INNER) { view.expandCollapseNode(setting, targetNode, true, false); } else if (!isSilent) { view.expandCollapseNode(setting, targetNode.getParentNode(), true, false); } targetObj = $("#" + targetNode.tId); target_ulObj = $("#" + targetNode.tId + consts.id.UL); if (!!targetObj.get(0) && !target_ulObj.get(0)) { var ulstr = []; view.makeUlHtml(setting, targetNode, ulstr, ''); targetObj.append(ulstr.join('')); } target_ulObj = $("#" + targetNode.tId + consts.id.UL); } var nodeDom = $("#" + node.tId); if (!nodeDom.get(0)) { nodeDom = view.appendNodes(setting, node.level, [node], null, false, true).join(''); } else if (!targetObj.get(0)) { nodeDom.remove(); } if (target_ulObj.get(0) && moveType == consts.move.TYPE_INNER) { target_ulObj.append(nodeDom); } else if (targetObj.get(0) && moveType == consts.move.TYPE_PREV) { targetObj.before(nodeDom); } else if (targetObj.get(0) && moveType == consts.move.TYPE_NEXT) { targetObj.after(nodeDom); } //repair the data after move var i,l, tmpSrcIndex = -1, tmpTargetIndex = 0, oldNeighbor = null, newNeighbor = null, oldLevel = node.level; if (node.isFirstNode) { tmpSrcIndex = 0; if (oldParentNode[childKey].length > 1 ) { oldNeighbor = oldParentNode[childKey][1]; oldNeighbor.isFirstNode = true; } } else if (node.isLastNode) { tmpSrcIndex = oldParentNode[childKey].length -1; oldNeighbor = oldParentNode[childKey][tmpSrcIndex - 1]; oldNeighbor.isLastNode = true; } else { for (i = 0, l = oldParentNode[childKey].length; i < l; i++) { if (oldParentNode[childKey][i].tId == node.tId) { tmpSrcIndex = i; break; } } } if (tmpSrcIndex >= 0) { oldParentNode[childKey].splice(tmpSrcIndex, 1); } if (moveType != consts.move.TYPE_INNER) { for (i = 0, l = targetParentNode[childKey].length; i < l; i++) { if (targetParentNode[childKey][i].tId == targetNode.tId) tmpTargetIndex = i; } } if (moveType == consts.move.TYPE_INNER) { if (!targetNode[childKey]) targetNode[childKey] = new Array(); if (targetNode[childKey].length > 0) { newNeighbor = targetNode[childKey][targetNode[childKey].length - 1]; newNeighbor.isLastNode = false; } targetNode[childKey].splice(targetNode[childKey].length, 0, node); node.isLastNode = true; node.isFirstNode = (targetNode[childKey].length == 1); } else if (targetNode.isFirstNode && moveType == consts.move.TYPE_PREV) { targetParentNode[childKey].splice(tmpTargetIndex, 0, node); newNeighbor = targetNode; newNeighbor.isFirstNode = false; node.parentTId = targetNode.parentTId; node.isFirstNode = true; node.isLastNode = false; } else if (targetNode.isLastNode && moveType == consts.move.TYPE_NEXT) { targetParentNode[childKey].splice(tmpTargetIndex + 1, 0, node); newNeighbor = targetNode; newNeighbor.isLastNode = false; node.parentTId = targetNode.parentTId; node.isFirstNode = false; node.isLastNode = true; } else { if (moveType == consts.move.TYPE_PREV) { targetParentNode[childKey].splice(tmpTargetIndex, 0, node); } else { targetParentNode[childKey].splice(tmpTargetIndex + 1, 0, node); } node.parentTId = targetNode.parentTId; node.isFirstNode = false; node.isLastNode = false; } data.fixPIdKeyValue(setting, node); data.setSonNodeLevel(setting, node.getParentNode(), node); //repair node what been moved view.setNodeLineIcos(setting, node); view.repairNodeLevelClass(setting, node, oldLevel) //repair node's old parentNode dom if (!setting.data.keep.parent && oldParentNode[childKey].length < 1) { //old parentNode has no child nodes oldParentNode.isParent = false; oldParentNode.open = false; var tmp_ulObj = $("#" + oldParentNode.tId + consts.id.UL), tmp_switchObj = $("#" + oldParentNode.tId + consts.id.SWITCH), tmp_icoObj = $("#" + oldParentNode.tId + consts.id.ICON); view.replaceSwitchClass(oldParentNode, tmp_switchObj, consts.folder.DOCU); view.replaceIcoClass(oldParentNode, tmp_icoObj, consts.folder.DOCU); tmp_ulObj.css("display", "none"); } else if (oldNeighbor) { //old neigbor node view.setNodeLineIcos(setting, oldNeighbor); } //new neigbor node if (newNeighbor) { view.setNodeLineIcos(setting, newNeighbor); } //repair checkbox / radio if (!!setting.check && setting.check.enable && view.repairChkClass) { view.repairChkClass(setting, oldParentNode); view.repairParentChkClassWithSelf(setting, oldParentNode); if (oldParentNode != node.parent) view.repairParentChkClassWithSelf(setting, node); } //expand parents after move if (!isSilent) { view.expandCollapseParentNode(setting, node.getParentNode(), true, animateFlag); } }, removeEditBtn: function(node) { $("#" + node.tId + consts.id.EDIT).unbind().remove(); }, removeRemoveBtn: function(node) { $("#" + node.tId + consts.id.REMOVE).unbind().remove(); }, removeTreeDom: function(setting, node) { node.isHover = false; view.removeEditBtn(node); view.removeRemoveBtn(node); tools.apply(setting.view.removeHoverDom, [setting.treeId, node]); }, repairNodeLevelClass: function(setting, node, oldLevel) { if (oldLevel === node.level) return; var liObj = $("#" + node.tId), aObj = $("#" + node.tId + consts.id.A), ulObj = $("#" + node.tId + consts.id.UL), oldClass = consts.className.LEVEL + oldLevel, newClass = consts.className.LEVEL + node.level; liObj.removeClass(oldClass); liObj.addClass(newClass); aObj.removeClass(oldClass); aObj.addClass(newClass); ulObj.removeClass(oldClass); ulObj.addClass(newClass); } }, _z = { tools: _tools, view: _view, event: _event, data: _data }; $.extend(true, $.fn.zTree.consts, _consts); $.extend(true, $.fn.zTree._z, _z); var zt = $.fn.zTree, tools = zt._z.tools, consts = zt.consts, view = zt._z.view, data = zt._z.data, event = zt._z.event; data.exSetting(_setting); data.addInitBind(_bindEvent); data.addInitUnBind(_unbindEvent); data.addInitCache(_initCache); data.addInitNode(_initNode); data.addInitProxy(_eventProxy); data.addInitRoot(_initRoot); data.addZTreeTools(_zTreeTools); var _cancelPreSelectedNode = view.cancelPreSelectedNode; view.cancelPreSelectedNode = function (setting, node) { var list = data.getRoot(setting).curSelectedList; for (var i=0, j=list.length; i"); }, showNode: function(setting, node, options) { node.isHidden = false; data.initShowForExCheck(setting, node); $("#" + node.tId).show(); }, showNodes: function(setting, nodes, options) { if (!nodes || nodes.length == 0) { return; } var pList = {}, i, j; for (i=0, j=nodes.length; i 0 && !parentNode[childKey][0].isHidden) { parentNode[childKey][0].isFirstNode = true; } else if (childLength > 0) { view.setFirstNodeForHide(setting, parentNode[childKey]); } }, setLastNode: function(setting, parentNode) { var childKey = setting.data.key.children, childLength = parentNode[childKey].length; if (childLength > 0 && !parentNode[childKey][0].isHidden) { parentNode[childKey][childLength - 1].isLastNode = true; } else if (childLength > 0) { view.setLastNodeForHide(setting, parentNode[childKey]); } }, setFirstNodeForHide: function(setting, nodes) { var n,i,j; for (i=0, j=nodes.length; i=0; i--) { n = nodes[i]; if (n.isLastNode) { break; } if (!n.isHidden && !n.isLastNode) { n.isLastNode = true; view.setNodeLineIcos(setting, n); break; } else { n = null; } } return n; }, setLastNodeForShow: function(setting, nodes) { var n,i,j, last, old; for (i=nodes.length-1; i>=0; i--) { n = nodes[i]; if (!last && !n.isHidden && n.isLastNode) { last = n; break; } else if (!last && !n.isHidden && !n.isLastNode) { n.isLastNode = true; last = n; view.setNodeLineIcos(setting, n); } else if (last && n.isLastNode) { n.isLastNode = false; old = n; view.setNodeLineIcos(setting, n); break; } else { n = null; } } return {"new":last, "old":old}; } }, _z = { view: _view, data: _data }; $.extend(true, $.fn.zTree._z, _z); var zt = $.fn.zTree, tools = zt._z.tools, consts = zt.consts, view = zt._z.view, data = zt._z.data, event = zt._z.event; data.addInitNode(_initNode); data.addBeforeA(_beforeA); data.addZTreeTools(_zTreeTools); // Override method in core var _dInitNode = data.initNode; data.tmpHideParent = -1; data.initNode = function(setting, level, node, parentNode, isFirstNode, isLastNode, openFlag) { if (data.tmpHideParent !== parentNode) { data.tmpHideParent = parentNode; var tmpPNode = (parentNode) ? parentNode: data.getRoot(setting), children = tmpPNode[setting.data.key.children]; data.tmpHideFirstNode = view.setFirstNodeForHide(setting, children); data.tmpHideLastNode = view.setLastNodeForHide(setting, children); view.setNodeLineIcos(setting, data.tmpHideFirstNode); view.setNodeLineIcos(setting, data.tmpHideLastNode); } isFirstNode = (data.tmpHideFirstNode === node); isLastNode = (data.tmpHideLastNode === node); if (_dInitNode) _dInitNode.apply(data, arguments); if (isLastNode) { view.clearOldLastNode(setting, node); } } var _makeChkFlag = data.makeChkFlag; if (!!_makeChkFlag) { data.makeChkFlag = function(setting, node) { if (!!node && !!node.isHidden) { return; } _makeChkFlag.apply(data, arguments); } } var _getTreeCheckedNodes = data.getTreeCheckedNodes; if (!!_getTreeCheckedNodes) { data.getTreeCheckedNodes = function(setting, nodes, checked, results) { if (!!nodes && nodes.length > 0) { var p = nodes[0].getParentNode(); if (!!p && !!p.isHidden) { return []; } } return _getTreeCheckedNodes.apply(data, arguments); } } var _getTreeChangeCheckedNodes = data.getTreeChangeCheckedNodes; if (!!_getTreeChangeCheckedNodes) { data.getTreeChangeCheckedNodes = function(setting, nodes, results) { if (!!nodes && nodes.length > 0) { var p = nodes[0].getParentNode(); if (!!p && !!p.isHidden) { return []; } } return _getTreeChangeCheckedNodes.apply(data, arguments); } } var _expandCollapseSonNode = view.expandCollapseSonNode; if (!!_expandCollapseSonNode) { view.expandCollapseSonNode = function(setting, node, expandFlag, animateFlag, callback) { if (!!node && !!node.isHidden) { return; } _expandCollapseSonNode.apply(view, arguments); } } var _setSonNodeCheckBox = view.setSonNodeCheckBox; if (!!_setSonNodeCheckBox) { view.setSonNodeCheckBox = function(setting, node, value, srcNode) { if (!!node && !!node.isHidden) { return; } _setSonNodeCheckBox.apply(view, arguments); } } var _repairParentChkClassWithSelf = view.repairParentChkClassWithSelf; if (!!_repairParentChkClassWithSelf) { view.repairParentChkClassWithSelf = function(setting, node) { if (!!node && !!node.isHidden) { return; } _repairParentChkClassWithSelf.apply(view, arguments); } } })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jquery-ztree/3.5/log v3.x.txt ================================================ =ZTree v3.x (JQuery Tree插件) 更新日志= 为了更好的优化及扩展zTree, 因此决定升级为v3.x,并且对之前的v2.x不兼容,会有很多结构上的修改,对此深感无奈与抱歉,请大家谅解。 具体修改内容可参考: * [http://www.ztree.me/v3/api.php zTree v3.0 API 文档] * [http://www.ztree.me/v3/demo.php#_101 zTree v3.0 Demo 演示] * [http://www.ztree.me/v3/faq.php#_101 zTree v3.0 常见问题] *2013.03.11* v3.5.12 * 【修改】由于 jquery 1.9 中移除 event.srcElement 导致的 js 报错的bug。 * 【修改】在异步加载模式下,使用 moveNode 方法,且 moveType != "inner" 时,也会导致 targetNode 自动加载子节点的 bug * 【修改】对已经显示的节点(nochecked=true)使用 showNodes 或 showNode 方法后,导致勾选框出现的bug。 * 【修改】对已经隐藏的节点(nochecked=false)使用 hideNodes 或 hideNode 方法后,导致勾选框消失的bug。 * 【修改】getNodesByParamFuzzy 支持 大小写模糊。 * 【修改】className 结构,提取 _consts.className.BUTTON / LEVEL / ICO_LOADING / SWITCH,便于快速修改 css 冲突。 例如:与 WordPress 产生冲突后,直接修改 core 中的 "button" 和 "level" 即可。 Issue: https://github.com/zTree/zTree_v3/issues/2 *2013.01.28* v3.5.02 * 【增加】setting.check.chkDisabledInherit 属性,用于设置 chkDisabled 在初始化时子节点是否可以继承父节点的 chkDisabled 属性 * 【删除】内部 noSel 方法,使用 selectstart事件 和 "-moz-user-select"样式 处理禁止 节点文字被选择的功能 * 【修改】不兼容 jQuery 1.9 的bug * 【修改】onDrop 的触发规则,保证异步加载模式下,可以在延迟加载结束后触发,避免 onDrop 中被拖拽的节点是已经更新后的数据。 * 【修改】setChkDisabled 方法,增加 inheritParent, inheritChildren 参数设置是否让父子节点继承 disabled * 【修改】异步加载时 拼接参数的方法,由 string 修改为 json 对象 * 【修正】1-2-3 3级节点时,如果 2级节点 全部设置为 nocheck 或 chkDisabled后,勾选3级节点时,1级节点的半勾选状态错误的 bug * 【修改】Demo: checkbox_nocheck.html & checkbox_chkDisabled.html; * 【修改】Demo: edit_super.html,增加 showRenameBtn & showRemoveBtn 的演示 * 【修改】Demo: asyncForAll, 将 post 修改为 get;为了避免由于 IE10 的 bug 造成的客户端 以及 服务端崩溃 IE10 ajax Post 无法提交参数的bug (http://bugs.jquery.com/ticket/12790) *2012.12.21* v3.5.01 * 【优化】clone 方法 * 【修正】对于初始化无 children 属性的父节点进行 reAsyncChildNodes 操作时出错的 bug * 【修正】beforeRename 回调中使用 cancelEditName 方法后,再 return false 导致无法重新进行编辑的 bug * 【修正】exedit 扩展包让 setting.data.key.url 失效的 bug * 【修正】setting.check.autoCheckTrigger 设置为 true 时,onCheck 回调缺少 event 参数的 bug * 【修正】singlepath.html Demo 中的 bug *2012.11.20* v3.5 * 【优化】原先的 clone 方法 (特别感谢:愚人码头) * 【修改】隐藏父节点后,使用 expandAll 方法导致 父节点展开的 bug * 【修改】使用 jQuery v1.7 以上时,设置 zTree 容器 ul 隐藏(visibility: hidden;)后, 调用 selectNode 导致 IE 浏览器报错 Can't move focus 的 bug * 【修改】正在异步加载时,执行 destory 或 init 方法后,异步加载的节点影响新树的 bug * 【修改】方法 reAsyncChildNodes 在 refresh 的时候未清空内部 cache 导致内存泄露 的 bug * 【修改】批量节点拖拽到其他父节点内(inner)时,导致顺序反转 的 bug * 【修改】对于 使用 html格式的 节点无法触发 双击事件 的 bug * 【修改】onCheck 回调中的 event ,保证与触发事件中的 event 一致 * 【修改】异步加载时,在 onNodeCreated 中执行 selectNode 后,导致节点折叠的 bug * 【修改】API 中 dataFilter 的参数名称 childNodes -> responseData * 【修改】API 中 iconSkin 的 举例内容 * 【修改】API 中 chkDisabled 的说明 * 【修改】Demo 中 index.html 内的 loadReady 重复绑定问题 *2012.09.03* v3.4 * 【增加】 Demo —— OutLook 样式的左侧菜单 * 【增加】清空 zTree 的方法 $.fn.zTree.destory(treeId) & zTree.destory() * 【修改】core核心文件内 _eventProxy 方法中获取 tId 的方法,提高 DOM 的灵活性 * 【修改】初始化时 多层父节点的 checkbox 半选状态计算错误的 bug * 【修改】同时选中父、子节点后,利用 getSelectedNodes 获取选中节点并利用 removeNode 删除时报错的 bug * 【修改】treeNode.chkDisabled / nocheck 属性,支持字符串格式的 "false"/"true" * 【修改】异步加载模式下无法利用 server 返回 xml 并且 在 dataFilter 中继续处理的 bug * 【修改】title 只允许设置为 string 类型值的问题。 修正后允许设置为 number 类型的值 * 【修改】zId 计数规则 & Cache 保存,减少 IE9 的 bug 造成的内存泄漏 * 【修改】API 页面搜索功能导致 IE 崩溃的 bug *2012.07.16* v3.3 * 【增加】扩展库 exhide -- 节点隐藏功能 * 【修改】getNodesByFilter 方法,添加 invokeParam 自定义参数 * 【修改】拖拽中测试代码未删除,导致出现黄颜色的 iframe 遮罩层的 bug * 【修改】延迟加载方法 对于使用 expandAll 进行全部展开时,导致 onNodeCreated 回调 和 addDiyDom 方法触发过早的 bug * 【修改】使用 moveNode 移动尚未生成 DOM 的节点时,视图会出现异常的 bug * 【修改】删除节点后,相关节点的 isFirstNode 属性未重置的 bug * 【修改】getPreNode(),getNextNode() 方法在对于特殊情况时计算错误的 bug * 【修改】设置 title 之后,如果重新将 title 内容设置为空后,会导致无法更新 title 的 bug * 【修改】针对 setting.check.chkStyle=="radio" && setting.check.radioType=="all" 的情况时,getTreeCheckedNodes方法优化,找到一个结果就 break * 【修改】zTreeObj.getCheckedNodes(false) 在 radioType = "all" 时计算错误的 bug * 【修改】完善 API 中 beforeDrop / onDrop 的关于 treeId 的说明 *2012.05.13* v3.2 * 【增加】setting.data.key.url 允许修改 treeNode.url 属性 * 【增加】getNodesByFilter(filter, isSingle) 方法 * 【增加】"与其他 DOM 拖拽互动" 的 Demo (http://www.ztree.me/v3/demo.php#_511) * 【增加】"异步加载模式下全部展开" 的 Demo (http://www.ztree.me/v3/demo.php#_512) * 【修改】代码结构,将 addNodes、removeNode、removeChildNodes 方法 和 beforeRemove、onRemove 回调 转移到 core 内 * 【修改】IE7的环境下无子节点的父节点反复展开出现多余空行的 bug * 【修改】异步加载时,如果出现网络异常等,会导致 图标显示错误的 bug * 【修改】dataFilter中 return null 导致异常 的 bug * 【修改】removeChildNodes 方法清空子节点后,无法正常添加节点的 bug * 【修改】moveNode 后节点中的自定义元素的事件丢失的 bug * 【修改】moveNode 方法中设置 isSilent = true 时,如果移动到已展开的 父节点后,出现异常的 bug * 【修改】onClick/onDrag/onDrop 回调中 event 不是原始 event 的 bug * 【修改】onDrop 回调中 当拖拽无效时,无法获得 treeNodes 的 bug * 【修改】onDrop 无法判断拖拽是 移动还是复制的问题 * 【修改】未开启异步加载模式时,拖拽节点到子节点为空的父节点内时 出现异常 的 bug * 【修改】拖拽过程中,反复在 父节点图标上划动时,会出现停顿的 bug (需要css 结构—— button -> span.button) * 【修改】拖拽操作时箭头 与 targetNode 背景之间的细节现实问题,便于用户拖拽时更容易区分 prev、next 和 inner 操作 * 【修改】拖拽操作时IE6/7 下 在 节点 右侧 10px 内会导致 targetNode = root 的 bug * 【修改】编辑模式下 默认的编辑按钮、删除按钮点击后,如果相应的 before 回调 return false 时会触发 onClick 回调的 bug *2012.02.14* v3.1 * 【增加】ajax 的参数 setting.async.contentType ,让提交参数适用于 json 数据提交 (主要适用于 .Net 的开发)。 * 【增加】setting.edit.editNameSelectAll, 用于设定编辑节点名称时初次显示 input 后 text 内容为全选 * 【修改】异步加载 规则,不再仅仅依靠父节点的子节点数来判定,增加内部属性 zAsync,保证默认状态下父节点及时无子节点也只能异步加载一次,除非使用 reAsyncChildNodes 方法强行控制异步加载。 * 【修改】放大浏览器后导致 界面出现多余连接线的bug (需要更新:icon 图标和 css ) * 【修改】在编辑状态,如果节点名超过编辑框宽度,左右键在框内不起作用的bug(IE 6 7 8 出现) CSS 中 filter:alpha(opacity=80) 造成的,应该是 ie 的 bug,需要更新 css 文件 * 【修改】title 设置后,如果属性不存在,则默认为 title 为空,便于数据容错和用户灵活使用 * 【修改】editName 方法如果针对尚未展开的 父节点,会导致该父节点自动展开的 bug * 【修改】title 中存在标签时导致 title 显示异常的bug(例如:蓝色字22%"'``) *2012.01.10* v3.0 * 【增加】setting.check.autoCheckTrigger 默认值 false,可以设置联动选中时是否触发事件回调函数 * 【增加】setting.callback.beforeEditName 回调函数,以保证用户可以捕获点击编辑按钮的事件 * 【增加】treeNode.chkDisabled 属性,显示 checkbox 但是用户无法修改 checkbox 状态,并且该 checkbox 会影响父节点的 checkbox 的半选状态 * 【增加】setting.check.nocheckInherit 属性,用户设置子节点继承 nocheck 属性,用于批量初始化节点,不适用于已经显示的节点 * 【增加】setting.edit.drag.autoExpandTrigger 默认值 false,可以设置自动展开、折叠操作时是否触发事件回调函数 * 【增加】setting.view.nameIsHTML 默认值 false,允许用户对 name 设置 DOM 对象 * 【增加】treeNode.click 属性的说明文档 * 【增加】treeObj.setChkDisabled 方法用于设置 checkbox / radio disabled 状态 * 【增加】treeNode.halfCheck 属性,用于强制设定节点的半选状态 * 【修改】异步加载 & 编辑功能 共存时,拖拽节点 或 增加节点 导致 ie 上报错的 bug (apply 方法引起) * 【修改】zTreeStyle 样式冲突 * 【修改】setting.data.key.title 默认值设置为 "",初始化时自动赋值为 setting.data.key.name 这样可避免希望 title 与 name 一致的用户反复设置参数 * 【修改】点击叶子节点的连接线会触发 expand 事件的 bug * 【修改】IE 下 点击叶子节点连线会出现虚线框的 bug * 【修改】updateNode 导致 checkbox 半选状态错误的 bug * 【修改】checkNode 方法实现 toggle 操作, 取消 expandAll 方法的 toggle 操作 * 【修改】zTree 内鼠标移动会抢页面上 input 内的焦点的 bug * 【修改】beforeRename / onRename 的触发方式——即使名称内容未改变也会触发,便于用户配合 beforeEditName 捕获编辑状态的结束,赋予用户更多调整规则的权利 * 【修改】与 easyUI 共存时无法拖拽的bug * 【修改】beforeRename 在 Firefox 下如果利用 alert,会触发两次的 bug * 【修改】checkNode/expandNode/removeNode 方法,默认不触发回调函数,恢复 v2.6 的默认状态,同时增加 callbackFlag 参数,设置为 true 时,可以触发回调函数 * 【修改】IE9下“根据参数查找节点”的Demo 报错:行14 重新声明常量属性(Demo 自身的问题,定义了history变量) * 【修改】初始化 zTree 时 onNodeCreated 事件回调函数中无法 用 getZTreeObj 获取 zTree 对象的 bug * 【修改】setting.edit.drag.prev / next / inner 参数,增加被拖拽的节点集合 * 【修改】异步加载模式下,otherParam 使用Array数组会出错的 bug。例如: ["id", "1", "name", "test"] * 【修改】FireFox 下多棵树拖拽异常的 bug * 【修改】exedit 中调用 excheck库的方法时没有进行容错处理,导致如果只加入 exedit 而没有 excheck的时候,会出现 js 错误 * 【修改】显示 checkbox 的 zTree 在编辑模式下,移动节点不会更新父节点半选状态的 bug * 【修改】treeNode.childs --> children; treeObject.removeChilds --> removeChildNodes; setting.data.key.childs --> children(英文不好惹的祸!抱歉了!) * 【修改】onRemove 回调中得到的 treeNode 还可以查找 preNode、nextNode 的bug。 修正后,getPreNode 和 getNextNode 都返回 null; 为了便于查找父节点,getParentNode 仍保留 * 【修改】简单数据模式下,如果 id 与 pId 的值相同会导致该节点无法正常加载的 bug * 【修改】移动或删除中间节点会导致最后一个节点连接线图标变小的 bug *2011.09.05* v3.0 beta * 【修改】zTree 的 js 代码架构全面修改,并且拆分 * 【修改】zTree 的 css 样式全面修改,对浏览器可以更好地兼容,同时解决了以前1个像素差的问题 * 【优化】采用延迟加载技术,一次性加载大数据量的节点性能飞速提升 * 【增加】支持多节点同时选中、拖拽 * 【增加】checkNode、checkAllNodes 等多种方法 * 【增加】IE6 自动取消动画展开、折叠的功能 * 【修正】异步加载 & 编辑模式 能够更完美的共存 * 【修正】setting 配置更加合理,并且增加了若干项配置参数 * 【修正】treeNode 节点数据的属性更加合理,并且增加了一些方法 * 【修正】拖拽操作更加灵活方便,更容易制定自己的规则 * 【修正】其他若干修改,详细对比请参考 url:[http://www.ztree.me/v3/faq.php#_101 zTree v3.0 常见问题] ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jsonview/jquery.jsonview.css ================================================ @charset "UTF-8"; .jsonview { font-family: monospace; font-size: 1.1em; white-space: pre-wrap; } .jsonview .prop { font-weight: bold; } .jsonview .null { color: red; } .jsonview .bool { color: blue; } .jsonview .num { color: blue; } .jsonview .string { color: green; white-space: pre-wrap; } .jsonview .string.multiline { display: inline-block; vertical-align: text-top; } .jsonview .collapser { position: absolute; left: -1em; cursor: pointer; } .jsonview .collapsible { transition: height 1.2s; transition: width 1.2s; } .jsonview .collapsible.collapsed { height: .8em; width: 1em; display: inline-block; overflow: hidden; margin: 0; } .jsonview .collapsible.collapsed:before { content: "…"; width: 1em; margin-left: .2em; } .jsonview .collapser.collapsed { transform: rotate(0deg); } .jsonview .q { display: inline-block; width: 0px; color: transparent; } .jsonview li { position: relative; } .jsonview ul { list-style: none; margin: 0 0 0 2em; padding: 0; } .jsonview h1 { font-size: 1.2em; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/jsonview/jquery.jsonview.js ================================================ (function(jQuery) { var $, Collapser, JSONFormatter, JSONView; JSONFormatter = (function() { function JSONFormatter(options) { if (options == null) { options = {}; } this.options = options; } JSONFormatter.prototype.htmlEncode = function(html) { if (html !== null) { return html.toString().replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); } else { return ''; } }; JSONFormatter.prototype.jsString = function(s) { s = JSON.stringify(s).slice(1, -1); return this.htmlEncode(s); }; JSONFormatter.prototype.decorateWithSpan = function(value, className) { return "" + (this.htmlEncode(value)) + ""; }; JSONFormatter.prototype.valueToHTML = function(value, level) { var valueType; if (level == null) { level = 0; } valueType = Object.prototype.toString.call(value).match(/\s(.+)]/)[1].toLowerCase(); return this["" + valueType + "ToHTML"].call(this, value, level); }; JSONFormatter.prototype.nullToHTML = function(value) { return this.decorateWithSpan('null', 'null'); }; JSONFormatter.prototype.numberToHTML = function(value) { return this.decorateWithSpan(value, 'num'); }; JSONFormatter.prototype.stringToHTML = function(value) { var multilineClass, newLinePattern; if (/^(http|https|file):\/\/[^\s]+$/i.test(value)) { return "\"" + (this.jsString(value)) + "\""; } else { multilineClass = ''; value = this.jsString(value); if (this.options.nl2br) { newLinePattern = /([^>\\r\\n]?)(\\r\\n|\\n\\r|\\r|\\n)/g; if (newLinePattern.test(value)) { multilineClass = ' multiline'; value = (value + '').replace(newLinePattern, '$1' + '
          '); } } return "\"" + value + "\""; } }; JSONFormatter.prototype.booleanToHTML = function(value) { return this.decorateWithSpan(value, 'bool'); }; JSONFormatter.prototype.arrayToHTML = function(array, level) { var collapsible, hasContents, index, numProps, output, value, _i, _len; if (level == null) { level = 0; } hasContents = false; output = ''; numProps = array.length; for (index = _i = 0, _len = array.length; _i < _len; index = ++_i) { value = array[index]; hasContents = true; output += '
        • ' + this.valueToHTML(value, level + 1); if (numProps > 1) { output += ','; } output += '
        • '; numProps--; } if (hasContents) { collapsible = level === 0 ? '' : ' collapsible'; return "[
            " + output + "
          ]"; } else { return '[ ]'; } }; JSONFormatter.prototype.objectToHTML = function(object, level) { var collapsible, hasContents, numProps, output, prop, value; if (level == null) { level = 0; } hasContents = false; output = ''; numProps = 0; for (prop in object) { numProps++; } for (prop in object) { value = object[prop]; hasContents = true; output += "
        • \"" + (this.jsString(prop)) + "\": " + (this.valueToHTML(value, level + 1)); if (numProps > 1) { output += ','; } output += '
        • '; numProps--; } if (hasContents) { collapsible = level === 0 ? '' : ' collapsible'; return "{
            " + output + "
          }"; } else { return '{ }'; } }; JSONFormatter.prototype.jsonToHTML = function(json) { return "
          " + (this.valueToHTML(json)) + "
          "; }; return JSONFormatter; })(); (typeof module !== "undefined" && module !== null) && (module.exports = JSONFormatter); Collapser = { bindEvent: function(item, collapsed) { var collapser; collapser = document.createElement('div'); collapser.className = 'collapser'; collapser.innerHTML = collapsed ? '+' : '-'; collapser.addEventListener('click', (function(_this) { return function(event) { return _this.toggle(event.target); }; })(this)); item.insertBefore(collapser, item.firstChild); if (collapsed) { return this.collapse(collapser); } }, expand: function(collapser) { var ellipsis, target; target = this.collapseTarget(collapser); ellipsis = target.parentNode.getElementsByClassName('ellipsis')[0]; target.parentNode.removeChild(ellipsis); target.style.display = ''; return collapser.innerHTML = '-'; }, collapse: function(collapser) { var ellipsis, target; target = this.collapseTarget(collapser); target.style.display = 'none'; ellipsis = document.createElement('span'); ellipsis.className = 'ellipsis'; ellipsis.innerHTML = ' … '; target.parentNode.insertBefore(ellipsis, target); return collapser.innerHTML = '+'; }, toggle: function(collapser) { var target; target = this.collapseTarget(collapser); if (target.style.display === 'none') { return this.expand(collapser); } else { return this.collapse(collapser); } }, collapseTarget: function(collapser) { var target, targets; targets = collapser.parentNode.getElementsByClassName('collapsible'); if (!targets.length) { return; } return target = targets[0]; } }; $ = jQuery; JSONView = { collapse: function(el) { if (el.innerHTML === '-') { return Collapser.collapse(el); } }, expand: function(el) { if (el.innerHTML === '+') { return Collapser.expand(el); } }, toggle: function(el) { return Collapser.toggle(el); } }; return $.fn.JSONView = function() { var args, defaultOptions, formatter, json, method, options, outputDoc; args = arguments; if (JSONView[args[0]] != null) { method = args[0]; return this.each(function() { var $this, level; $this = $(this); if (args[1] != null) { level = args[1]; return $this.find(".jsonview .collapsible.level" + level).siblings('.collapser').each(function() { return JSONView[method](this); }); } else { return $this.find('.jsonview > ul > li > .collapsible').siblings('.collapser').each(function() { return JSONView[method](this); }); } }); } else { json = args[0]; options = args[1] || {}; defaultOptions = { collapsed: false, nl2br: false }; options = $.extend(defaultOptions, options); formatter = new JSONFormatter({ nl2br: options.nl2br }); if (Object.prototype.toString.call(json) === '[object String]') { json = JSON.parse(json); } outputDoc = formatter.jsonToHTML(json); return this.each(function() { var $this, item, items, _i, _len, _results; $this = $(this); $this.html(outputDoc); items = $this[0].getElementsByClassName('collapsible'); _results = []; for (_i = 0, _len = items.length; _i < _len; _i++) { item = items[_i]; if (item.parentNode.nodeName === 'LI') { _results.push(Collapser.bindEvent(item.parentNode, options.collapsed)); } else { _results.push(void 0); } } return _results; }); } }; })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/layer/css/layer.css ================================================ html #layuicss-layer{display: none; position: absolute; width: 1989px;} /* common */ .layui-layer-shade, .layui-layer{position:fixed; _position:absolute; pointer-events: auto;} .layui-layer-shade{top:0; left:0; width:100%; height:100%; _height:expression(document.body.offsetHeight+"px");} .layui-layer{-webkit-overflow-scrolling: touch;} .layui-layer{top:150px; left: 0; margin:0; padding:0; background-color:#fff; -webkit-background-clip: content; border-radius: 2px; box-shadow: 1px 1px 50px rgba(0,0,0,.3);} .layui-layer-close{position:absolute;} .layui-layer-content{position:relative;} .layui-layer-border{border: 1px solid #B2B2B2; border: 1px solid rgba(0,0,0,.1); box-shadow: 1px 1px 5px rgba(0,0,0,.2);} .layui-layer-setwin span, .layui-layer-btn a{display: inline-block; vertical-align: middle; *display: inline; *zoom:1; } .layui-layer-move{display: none; position: fixed; *position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; cursor: move; opacity: 0; filter:alpha(opacity=0); background-color: #fff; z-index: 2147483647;} .layui-layer-resize{position: absolute; width: 15px; height: 15px; right: 0; bottom: 0; cursor: se-resize;} /* 动画 */ .layer-anim{-webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-duration:.3s; animation-duration:.3s;} @-webkit-keyframes layer-bounceIn { /* 默认 */ 0% {opacity: 0; -webkit-transform: scale(.5); transform: scale(.5)} 100% {opacity: 1; -webkit-transform: scale(1); transform: scale(1)} } @keyframes layer-bounceIn { 0% {opacity: 0; -webkit-transform: scale(.5); -ms-transform: scale(.5); transform: scale(.5)} 100% {opacity: 1; -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1)} } .layer-anim-00{-webkit-animation-name: layer-bounceIn;animation-name: layer-bounceIn} @-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown} @-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig} @-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft} @-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0px) rotate(0deg);transform:translateX(0px) rotate(0deg)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0px) rotate(0deg);-ms-transform:translateX(0px) rotate(0deg);transform:translateX(0px) rotate(0deg)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn} @keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn} @-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}} /* 从上往下 */ @keyframes layer-slide-down { from { transform: translate3d(0,-100%,0); } to { transform: translate3d(0,0,0); } } @keyframes layer-slide-down-out { from { transform: translate3d(0,0,0); } to { transform: translate3d(0,-100%,0); } } .layer-anim-slide-down{animation-name: layer-slide-down} .layer-anim-slide-down-out{animation-name: layer-slide-down-out} /* 从右往左 */ @keyframes layer-slide-left { from { transform: translate3d(100%,0,0); } to { transform: translate3d(0,0,0); } } @keyframes layer-slide-left-out { from { transform: translate3d(0,0,0); } to { transform: translate3d(100%,0,0); } } .layer-anim-slide-left{animation-name: layer-slide-left} .layer-anim-slide-left-out{animation-name: layer-slide-left-out} /* 从下往上 */ @keyframes layer-slide-up { from { transform: translate3d(0,100%,0); } to { transform: translate3d(0,0,0); } } @keyframes layer-slide-up-out { from { transform: translate3d(0,0,0); } to { transform: translate3d(0,100%,0); } } .layer-anim-slide-up{animation-name: layer-slide-up} .layer-anim-slide-up-out{animation-name: layer-slide-up-out} /* 从左往右 */ @keyframes layer-slide-right { from { transform: translate3d(-100%,0,0); } to { transform: translate3d(0,0,0); } } @keyframes layer-slide-right-out { from { transform: translate3d(0,0,0); } to { transform: translate3d(-100%,0,0); } } .layer-anim-slide-right{animation-name: layer-slide-right;} .layer-anim-slide-right-out{animation-name: layer-slide-right-out;} /* 标题栏 */ .layui-layer-title{padding: 0 81px 0 16px; height: 50px; line-height: 50px; border-bottom:1px solid #F0F0F0; font-size: 14px; color:#333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-radius: 2px 2px 0 0;} .layui-layer-setwin{position:absolute; right: 15px; *right:0; top: 16px; font-size:0; line-height: initial;} .layui-layer-setwin span{position:relative; width: 16px; height: 16px; line-height: 18px; margin-left: 10px; text-align: center; font-size: 16px; cursor: pointer; color: #000; _overflow: hidden; box-sizing: border-box;} .layui-layer-setwin .layui-layer-min:before{content: ''; position: absolute; width: 12px; border-bottom: 1px solid #2E2D3C; left: 50%; top: 50%; margin: -0.5px 0 0 -6px; cursor: pointer; _overflow:hidden;} .layui-layer-setwin .layui-layer-min:hover:before{background-color: #2D93CA} .layui-layer-setwin .layui-layer-max:hover:before, .layui-layer-setwin .layui-layer-max:hover:after{border-color: #2D93CA;} .layui-layer-setwin .layui-layer-min:hover:before{background-color: #2D93CA} .layui-layer-setwin .layui-layer-maxmin:before, .layui-layer-setwin .layui-layer-maxmin:after{width: 7px; height: 7px; margin: -3px 0 0 -3px; background-color: #fff;} .layui-layer-setwin .layui-layer-maxmin:after{z-index: 0; margin: -5px 0 0 -1px;} .layui-layer-setwin .layui-layer-close{cursor: pointer;} .layui-layer-setwin .layui-layer-close:hover{opacity:0.7;} .layui-layer-setwin .layui-layer-close2{position:absolute; right: -28px; top: -28px; color: #fff; background-color: #787878; padding: 3px; width: 16px; height: 20px; font-size: 16px; font-weight: bolder; border-radius: 50%; margin-left: 0; *right:-18px; _display:none;} .layui-layer-setwin .layui-layer-close2:hover{opacity: unset; background-color: #3888f6;} /* 按钮栏 */ .layui-layer-btn{text-align: right; padding: 0 15px 12px; pointer-events: auto; user-select: none; -webkit-user-select: none;} .layui-layer-btn a{height: 30px; line-height: 30px; margin: 5px 5px 0; padding: 0 16px; border: 1px solid #dedede; background-color: #fff; color: #333; border-radius: 2px; font-weight: 400; cursor: pointer; text-decoration: none; box-sizing: border-box;} .layui-layer-btn a:hover{opacity: 0.9; text-decoration: none;} .layui-layer-btn a:active{opacity: 0.8;} .layui-layer-btn .layui-layer-btn0{border-color: transparent; background-color: #1E9FFF; color:#fff;} .layui-layer-btn-l{text-align: left;} .layui-layer-btn-c{text-align: center;} /* 定制化 */ .layui-layer-dialog{min-width: 240px;} .layui-layer-dialog .layui-layer-content{position: relative; padding: 16px; line-height: 24px; word-break: break-all; overflow:hidden; font-size:14px; overflow-x: hidden; overflow-y:auto;} .layui-layer-dialog .layui-layer-content .layui-layer-face{position: absolute; top: 18px; left: 16px; color: #959595; font-size: 32px; _left: -40px;} .layui-layer-dialog .layui-layer-content .layui-icon-tips{color: #F39B12;} .layui-layer-dialog .layui-layer-content .layui-icon-success{color: #16b777;} .layui-layer-dialog .layui-layer-content .layui-icon-error{top: 19px; color: #FF5722;} .layui-layer-dialog .layui-layer-content .layui-icon-question{color: #FFB800;} .layui-layer-dialog .layui-layer-content .layui-icon-lock{color: #787878;} .layui-layer-dialog .layui-layer-content .layui-icon-face-cry{color: #FF5722;} .layui-layer-dialog .layui-layer-content .layui-icon-face-smile{color: #16b777;} .layui-layer-rim{border:6px solid #8D8D8D; border:6px solid rgba(0,0,0,.3); border-radius:5px; box-shadow: none;} .layui-layer-msg{min-width:180px; border:1px solid #D3D4D3; box-shadow: none;} .layui-layer-hui{min-width:100px; background-color: #000; filter:alpha(opacity=60); background-color: rgba(0,0,0,0.6); color: #fff; border:none;} .layui-layer-hui .layui-layer-close{color: #fff;} .layui-layer-hui .layui-layer-content{padding: 11px 24px; text-align: center;} .layui-layer-dialog .layui-layer-padding{padding: 18px 24px 18px 58px; text-align: left;} .layui-layer-page .layui-layer-content{position:relative; overflow:auto;} .layui-layer-page .layui-layer-btn,.layui-layer-iframe .layui-layer-btn{padding-top:10px;} .layui-layer-nobg{background:none;} .layui-layer-iframe iframe{display: block; width: 100%;} .layui-layer-loading{border-radius:100%; background:none; box-shadow:none; border:none;} .layui-layer-loading .layui-layer-content{width: 76px; height: 38px; line-height: 38px; text-align: center;} .layui-layer-loading-icon{font-size: 38px; color: #959595;} .layui-layer-loading2{text-align: center;} .layui-layer-loading-2{position: relative; height: 38px;} .layui-layer-loading-2:before, .layui-layer-loading-2:after{content: ''; position: absolute; left: 50%; top: 50%; width: 38px; height: 38px; margin: -19px 0 0 -19px; border-radius: 50%; border: 3px solid #d2d2d2; box-sizing: border-box;} .layui-layer-loading-2:after{border-color: transparent; border-left-color: #1E9FFF;} .layui-layer-tips{background: none; box-shadow:none; border:none;} .layui-layer-tips .layui-layer-content{position: relative; line-height: 22px; min-width: 12px; padding: 8px 15px; font-size: 12px; _float:left; border-radius: 2px; box-shadow: 1px 1px 3px rgba(0,0,0,.2); background-color: #000; color: #fff;} .layui-layer-tips .layui-layer-close{right:-2px; top:-1px;} .layui-layer-tips i.layui-layer-TipsG{ position:absolute; width:0; height:0; border-width:8px; border-color:transparent; border-style:dashed; *overflow:hidden;} .layui-layer-tips i.layui-layer-TipsT, .layui-layer-tips i.layui-layer-TipsB{left:5px; border-right-style:solid; border-right-color: #000;} .layui-layer-tips i.layui-layer-TipsT{bottom:-8px;} .layui-layer-tips i.layui-layer-TipsB{top:-8px;} .layui-layer-tips i.layui-layer-TipsR, .layui-layer-tips i.layui-layer-TipsL{top: 5px; border-bottom-style:solid; border-bottom-color: #000;} .layui-layer-tips i.layui-layer-TipsR{left:-8px;} .layui-layer-tips i.layui-layer-TipsL{right:-8px;} /* 内置 skin */ .layui-layer-lan .layui-layer-title{background:#4476A7; color:#fff; border: none;} .layui-layer-lan .layui-layer-btn{padding: 5px 10px 10px; border-top:1px solid #E9E7E7} .layui-layer-lan .layui-layer-btn a{background: #fff; border-color: #E9E7E7; color: #333;} .layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5;} .layui-layer-molv .layui-layer-title{background: #009f95; color:#fff; border: none;} .layui-layer-molv .layui-layer-btn a{background: #009f95; border-color: #009f95;} .layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1;} .layui-layer-lan .layui-layer-setwin .layui-icon, .layui-layer-molv .layui-layer-setwin .layui-icon{color: #fff;} /* Windows 10 风格主题 */ .layui-layer-win10{border: 1px solid #aaa; box-shadow: 1px 1px 6px rgba(0,0,0,.3); border-radius: none;} .layui-layer-win10 .layui-layer-title{height: 32px; line-height: 32px; padding-left: 8px; border-bottom: none; font-size: 12px;} .layui-layer-win10 .layui-layer-setwin{right: 0; top: 0;} .layui-layer-win10 .layui-layer-setwin span{margin-left: 0; width: 32px; height: 32px; padding: 8px;} .layui-layer-win10.layui-layer-page .layui-layer-setwin span{width: 38px;} .layui-layer-win10 .layui-layer-setwin span:hover{background-color: #E5E5E5;} .layui-layer-win10 .layui-layer-setwin span.layui-icon-close:hover{background-color: #E81123; color: #fff;} .layui-layer-win10.layui-layer-dialog .layui-layer-content{padding: 8px 16px 32px; color: #0033BC;} .layui-layer-win10.layui-layer-dialog .layui-layer-padding{padding-top: 18px; padding-left: 58px;} .layui-layer-win10 .layui-layer-btn{padding: 5px 5px 10px; border-top:1px solid #DFDFDF; background-color: #F0F0F0;} .layui-layer-win10 .layui-layer-btn a{height: 20px; line-height: 18px; background-color: #E1E1E1; border-color: #ADADAD; color: #000; font-size: 12px; transition: all .3s;} .layui-layer-win10 .layui-layer-btn a:hover{border-color: #2A8EDD; background-color: #E5F1FB;} .layui-layer-win10 .layui-layer-btn .layui-layer-btn0{border-color: #0078D7;} /** @Name: layer拓展样式 */ /* prompt模式 */ .layui-layer-prompt .layui-layer-input{display: block; width: 260px; height: 36px; margin: 0 auto; line-height: 30px; padding-left: 10px; border: 1px solid #e6e6e6; color: #333;} .layui-layer-prompt textarea.layui-layer-input{width: 300px; height: 100px; line-height: 20px; padding: 6px 10px;} .layui-layer-prompt .layui-layer-content{padding: 16px;} .layui-layer-prompt .layui-layer-btn{padding-top: 0;} /* tab模式 */ .layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4);} .layui-layer-tab .layui-layer-title{padding-left:0; overflow: visible;} .layui-layer-tab .layui-layer-title span{position:relative; display: inline-block; vertical-align: top; border-left: 1px solid transparent; border-right: 1px solid transparent; min-width:80px; max-width: 300px; padding:0 16px; text-align:center; cursor:default; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; cursor: pointer;} .layui-layer-tab .layui-layer-title span.layui-this{height: 51px; border-left-color: #eee; border-right-color: #eee; background-color: #fff; z-index: 10;} .layui-layer-tab .layui-layer-title span:first-child{border-left-color: transparent;} .layui-layer-tabmain{line-height:24px; clear: both;} .layui-layer-tabmain .layui-layer-tabli{display:none;} .layui-layer-tabmain .layui-layer-tabli.layui-this{display: block;} /* photos */ .layui-layer-photos{background: none; box-shadow: none;} .layui-layer-photos .layui-layer-content{overflow: visible; text-align: center;} .layui-layer-photos .layer-layer-photos-main img{position: relative; width:100%; display: inline-block; *display:inline; *zoom:1; vertical-align:top;} .layui-layer-photos-prev, .layui-layer-photos-next{position: fixed; top: 50%; width: 52px; height: 52px; line-height: 52px; margin-top: -26px; cursor: pointer; font-size: 52px; color: #717171;} .layui-layer-photos-prev{left: 32px;} .layui-layer-photos-next{right: 32px;} .layui-layer-photos-prev:hover, .layui-layer-photos-next:hover{color: #959595;} .layui-layer-photos-toolbar{position: fixed; left: 0; right: 0; bottom: 0; width: 100%; height: 52px; line-height: 52px; background-color: #000\9; filter: Alpha(opacity=60); background-color: rgba(0,0,0,.32); color: #fff; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; font-size:0;} .layui-layer-photos-toolbar > *{display:inline-block; vertical-align: top; padding: 0 16px; font-size: 12px; color: #fff; *display:inline; *zoom: 1;} .layui-layer-photos-toolbar *{font-size: 12px;} .layui-layer-photos-header{top: 0; bottom: auto;} .layui-layer-photos-header > span{cursor: pointer;} .layui-layer-photos-header > span:hover{background-color: rgba(51,51,51,.32);} .layui-layer-photos-header .layui-icon{font-size: 18px;} .layui-layer-photos-footer > h3{max-width: 65%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;} .layui-layer-photos-footer a:hover{text-decoration: underline;} .layui-layer-photos-footer em{font-style: normal;} /* 关闭动画 */ @-webkit-keyframes layer-bounceOut { 100% {opacity: 0; -webkit-transform: scale(.7); transform: scale(.7)} 30% {-webkit-transform: scale(1.05); transform: scale(1.05)} 0% {-webkit-transform: scale(1); transform: scale(1);} } @keyframes layer-bounceOut { 100% {opacity: 0; -webkit-transform: scale(.7); -ms-transform: scale(.7); transform: scale(.7);} 30% {-webkit-transform: scale(1.05); -ms-transform: scale(1.05); transform: scale(1.05);} 0% {-webkit-transform: scale(1); -ms-transform: scale(1);transform: scale(1);} } .layer-anim-close{-webkit-animation-name: layer-bounceOut; animation-name: layer-bounceOut; -webkit-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-duration:.2s; animation-duration:.2s;} @media screen and (max-width: 1100px) { .layui-layer-iframe{overflow-y: auto; -webkit-overflow-scrolling: touch;} } /* 其他样式 */ .layui-layer-lan[type=dialog] {min-width: 280px} .layui-layer-lan .layui-layer-title {background: #4476a7; color: #fff; border: 0} .layui-layer-lan .layui-layer-btn {padding: 5px 10px 10px; text-align: right; border-top: 1px solid #e9e7e7} .layui-layer-lan .layui-layer-btn a {background: #fff; border-color: #e9e7e7; color: #333} .layui-layer-lan .layui-layer-btn .layui-layer-btn1 {background: #c9c5c5} .layui-layer-gray[type=dialog] {min-width: 280px} .layui-layer-gray .layui-layer-title {background: #f8f8f8; color: #333; border: 0} .layui-layer-gray .layui-layer-btn {padding: 5px 10px 10px; text-align: right; border-top: 1px solid #e9e7e7} .layui-layer-gray .layui-layer-btn a {background: #fff; border-color: #e9e7e7; color: #333} .layui-layer-gray .layui-layer-btn .layui-layer-btn1 {background: #c9c5c5} .layui-layer-tab .layui-layer-content {width: 100%} ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/layer/theme/moon/style.css ================================================ /** * layer皮肤 * Copyright (c) 2019 ruoyi */ html #layui_layer_skinmoonstylecss { display: none; position: absolute; width: 1989px; } body .layer-ext-moon[type="dialog"] { min-width: 320px; } body .layer-ext-moon-msg[type="dialog"]{min-width:200px;} body .layer-ext-moon .layui-layer-title { background: #F8F8F8; color: #333; font-size: 14px; height: 42px; line-height: 42px; border: none; } body .layer-ext-moon .layui-layer-content .layui-layer-face { height: 32px; width: 32px; top:18.5px; } body .layer-ext-moon .layui-icon-tips { background: url(default.png) no-repeat -96px 0; } body .layer-ext-moon .layui-icon-success { background: url(default.png) no-repeat -224px 0; } body .layer-ext-moon .layui-icon-error { background: url(default.png) no-repeat -192px 0; } body .layer-ext-moon .layui-icon-question { background: url(default.png) no-repeat -160px 0; } body .layer-ext-moon .layui-icon-lock { background: url(default.png) no-repeat -320px 0; } body .layer-ext-moon .layui-icon-face-cry { background: url(default.png) no-repeat -288px 0; } body .layer-ext-moon .layui-icon-face-smile { background: url(default.png) -256px 0; } body .layer-ext-moon .layui-layer-download { background: url(default.png) no-repeat -128px 0; } body .layer-ext-moon .layui-layer-setwin { top: 15px; right: 15px; } body .layer-ext-moon .layui-layer-setwin a { width: 16px; height: 16px; } body .layer-ext-moon .layui-layer-setwin .layui-layer-min cite:hover { background-color: #56abe4; } body .layer-ext-moon .layui-layer-setwin .layui-layer-max { background: url(default.png) no-repeat -80px 0; } body .layer-ext-moon .layui-layer-setwin .layui-layer-max:hover { background: url(default.png) no-repeat -64px 0; } body .layer-ext-moon .layui-layer-setwin .layui-layer-maxmin { background: url(default.png) no-repeat -32px 0; } body .layer-ext-moon .layui-layer-setwin .layui-layer-maxmin:hover { background: url(default.png) no-repeat -16px 0; } body .layer-ext-moon .layui-layer-setwin .layui-layer-close1,body .layer-ext-moon .layui-layer-setwin .layui-layer-close2, body .layui-layer-tab .layui-layer-setwin .layui-layer-close1,body .layui-layer-tab .layui-layer-setwin .layui-layer-close2 { background: url(default.png) 0 0; } body .layer-ext-moon .layui-layer-setwin .layui-layer-close1:hover,body .layer-ext-moon .layui-layer-setwin .layui-layer-close2:hover, body .layui-layer-tab .layui-layer-setwin .layui-layer-close1:hover,body .layui-layer-tab .layui-layer-setwin .layui-layer-close2:hover { background: url(default.png) -48px 0; } body .layer-ext-moon .layui-layer-padding{padding-top: 24px;} body .layer-ext-moon .layui-layer-btn { text-align: right; padding: 10px 15px 12px; background: #f0f4f7; border-top: 1px #c7c7c7 solid; } body .layer-ext-moon .layui-layer-btn a { font-size: 12px; font-weight: normal; margin: 0 3px; margin-right: 7px; margin-left: 7px; padding: 0 15px; color: #fff; border: 1px solid #0064b6; background: #0071ce; border-radius: 3px; display: inline-block; height: 30px; line-height: 30px; text-align: center; vertical-align: middle; background-repeat: no-repeat; text-decoration: none; outline: none; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } body .layer-ext-moon .layui-layer-btn .layui-layer-btn0 { background: #0071ce; } body .layer-ext-moon .layui-layer-btn .layui-layer-btn1 { background: #fff; color: #404a58; border: 1px solid #c0c4cd; border-radius: 3px; } body .layer-ext-moon .layui-layer-btn .layui-layer-btn2 { background: #fff; color: #404a58; border: 1px solid #c0c4cd; border-radius: 3px; } body .layer-ext-moon .layui-layer-btn .layui-layer-btn3 { background: #f00; color: #fff; border: 1px solid #f00; border-radius: 3px; } body .layer-ext-moon .layui-layer-title span.layui-layer-tabnow{ height:47px; } /** 图标字体 **/ @font-face { font-family: 'layui-icon'; src: url('../../../../../fonts/iconfont.woff?v=282') format('woff'); } .layui-icon{ font-family:"layui-icon" !important; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* font-class */ .layui-icon-left:before{content:"\e603"} .layui-icon-right:before{content:"\e602"} .layui-icon-refresh:before{content:"\e669"} .layui-icon-slider:before{content:"\e714"} .layui-icon-add-circle:before{content:"\e61f"} .layui-icon-reduce-circle:before{content:"\e616"} .layui-icon-refresh-1:before{content:"\e666"} .layui-icon-loading:before{content:"\e63d"} .layui-icon-loading-1:before{content:"\e63e"} /** 循环旋转动画 **/ .layui-anim{-webkit-animation-duration: 0.3s; -webkit-animation-fill-mode: both; animation-duration: 0.3s; animation-fill-mode: both;} .layui-anim.layui-icon{display: inline-block;} .layui-anim-loop{-webkit-animation-iteration-count: infinite; animation-iteration-count: infinite;} .layui-trans, .layui-trans a{transition: all .2s; -webkit-transition: all .2s;} @-webkit-keyframes layui-rotate{from {-webkit-transform: rotate(0deg);} to {-webkit-transform: rotate(360deg);}} @keyframes layui-rotate{from {transform: rotate(0deg);} to {transform: rotate(360deg);}} .layui-anim-rotate{-webkit-animation-name: layui-rotate; animation-name: layui-rotate; -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-timing-function: linear; animation-timing-function: linear;} ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/layui/css/modules/laydate.css ================================================ /*! laydate-v5.5.0 日期与时间组件 */ @font-face{font-family: 'laydate-icon';src: url('../../../../../fonts/iconfont.woff?v=282') format('woff');}.laydate-icon{font-family:"laydate-icon"!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}html #layuicss-laydate{display:none;position:absolute;width:1989px}.layui-laydate *{margin:0;padding:0}.layui-laydate,.layui-laydate *{box-sizing:border-box}.layui-laydate{position:absolute;z-index:66666666;margin:5px 0;border-radius:2px;font-size:14px;-webkit-animation-duration:.2s;animation-duration:.2s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-laydate-main{width:272px}.layui-laydate-header *,.layui-laydate-content td,.layui-laydate-list li{transition-duration:.3s;-webkit-transition-duration:.3s}@keyframes laydate-downbit{0%{opacity:.3;transform:translate3d(0,-5px,0)}100%{opacity:1;transform:translate3d(0,0,0)}}.layui-laydate{animation-name:laydate-downbit}.layui-laydate-static{position:relative;z-index:0;display:inline-block;margin:0;-webkit-animation:none;animation:none}.laydate-ym-show .laydate-prev-m,.laydate-ym-show .laydate-next-m{display:none!important}.laydate-ym-show .laydate-prev-y,.laydate-ym-show .laydate-next-y{display:inline-block!important}.laydate-ym-show .laydate-set-ym span[lay-type="month"]{display:none!important}.laydate-time-show .layui-laydate-header .layui-icon,.laydate-time-show .laydate-set-ym span[lay-type="year"],.laydate-time-show .laydate-set-ym span[lay-type="month"]{display:none!important}.layui-laydate-header{position:relative;line-height:30px;padding:10px 70px 5px}.layui-laydate-header *{display:inline-block;vertical-align:bottom}.layui-laydate-header i{position:absolute;top:10px;padding:0 5px;color:#999;font-size:18px;cursor:pointer}.layui-laydate-header i.laydate-prev-y{left:15px}.layui-laydate-header i.laydate-prev-m{left:45px}.layui-laydate-header i.laydate-next-y{right:15px}.layui-laydate-header i.laydate-next-m{right:45px}.laydate-set-ym{width:100%;text-align:center;box-sizing:border-box;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.laydate-set-ym span{padding:0 10px;cursor:pointer}.laydate-time-text{cursor:default!important}.layui-laydate-content{position:relative;padding:10px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-laydate-content table{border-collapse:collapse;border-spacing:0}.layui-laydate-content th,.layui-laydate-content td{width:36px;height:30px;padding:5px;text-align:center}.layui-laydate-content th{font-weight:400}.layui-laydate-content td{position:relative;cursor:pointer}.laydate-day-mark{position:absolute;left:0;top:0;width:100%;line-height:30px;font-size:12px;overflow:hidden}.laydate-day-mark::after{position:absolute;content:'';right:2px;top:2px;width:5px;height:5px;border-radius:50%}.laydate-day-holidays:before{position:absolute;left:0;top:0;font-size:12px;transform:scale(.7)}.laydate-day-holidays:before{content:'\4F11';color:#ff5722}.laydate-day-holidays[type="work"]:before{content:'\73ED';color:inherit}.layui-laydate .layui-this .laydate-day-holidays:before{color:#fff}.layui-laydate-footer{position:relative;height:46px;line-height:26px;padding:10px}.layui-laydate-footer span{display:inline-block;vertical-align:top;height:26px;line-height:24px;padding:0 10px;border:1px solid #c9c9c9;border-radius:2px;background-color:#fff;font-size:12px;cursor:pointer;white-space:nowrap;transition:all .3s}.layui-laydate-footer span:hover{color:#5fb878}.layui-laydate-footer span.layui-laydate-preview{cursor:default;border-color:transparent!important}.layui-laydate-footer span.layui-laydate-preview:hover{color:#666}.layui-laydate-footer span:first-child.layui-laydate-preview{padding-left:0}.laydate-footer-btns{position:absolute;right:10px;top:10px}.laydate-footer-btns span{margin:0 0 0 -1px}.layui-laydate-list{position:absolute;left:0;top:0;width:100%;height:100%;padding:10px;box-sizing:border-box;background-color:#fff}.layui-laydate-list>li{position:relative;display:inline-block;width:33.3%;height:36px;line-height:36px;margin:3px 0;vertical-align:middle;text-align:center;cursor:pointer}.laydate-month-list>li{width:25%;margin:17px 0}.laydate-time-list>li{height:100%;margin:0;line-height:normal;cursor:default}.laydate-time-list p{position:relative;top:-4px;line-height:29px}.laydate-time-list ol{height:181px;overflow:hidden}.laydate-time-list>li:hover ol{overflow-y:auto}.laydate-time-list ol li{width:130%;padding-left:33px;height:30px;line-height:30px;text-align:left;cursor:pointer}.layui-laydate-hint{position:absolute;top:115px;left:50%;width:250px;margin-left:-125px;line-height:20px;padding:15px;text-align:center;font-size:12px;color:#ff5722}.layui-laydate-range{width:546px}.layui-laydate-range .layui-laydate-main{display:inline-block;vertical-align:middle}.layui-laydate-range .laydate-main-list-1 .layui-laydate-header,.layui-laydate-range .laydate-main-list-1 .layui-laydate-content{border-left:1px solid #e2e2e2}.layui-laydate,.layui-laydate-hint{border:1px solid #d2d2d2;box-shadow:0 2px 4px rgba(0,0,0,.12);background-color:#fff;color:#666}.layui-laydate-header{border-bottom:1px solid #e2e2e2}.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:#5fb878}.layui-laydate-content{border-top:none 0;border-bottom:none 0}.layui-laydate-content th{color:#333}.layui-laydate-content td{color:#666}.layui-laydate-content td.laydate-selected{background-color:#b5fff8}.laydate-selected:hover{background-color:#00f7de!important}.layui-laydate-content td:hover,.layui-laydate-list li:hover{background-color:#eee;color:#333}.laydate-time-list li ol{margin:0;padding:0;border:1px solid #e2e2e2;border-left-width:0}.laydate-time-list li:first-child ol{border-left-width:1px}.laydate-time-list>li:hover{background:0}.layui-laydate-content .laydate-day-prev,.layui-laydate-content .laydate-day-next{color:#d2d2d2}.laydate-selected.laydate-day-prev,.laydate-selected.laydate-day-next{background-color:#f8f8f8!important}.layui-laydate-footer{border-top:1px solid #e2e2e2}.layui-laydate-hint{color:#ff5722}.laydate-day-mark::after{background-color:#5fb878}.layui-laydate-content td.layui-this .laydate-day-mark::after{display:none}.layui-laydate-footer span[lay-type="date"]{color:#5fb878}.layui-laydate .layui-this{background-color:#009688!important;color:#fff!important}.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{background:none!important;color:#d2d2d2!important;cursor:not-allowed!important;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.laydate-theme-molv{border:0}.laydate-theme-molv.layui-laydate-range{width:548px}.laydate-theme-molv .layui-laydate-main{width:274px}.laydate-theme-molv .layui-laydate-header{border:0;background-color:#009688}.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:#f6f6f6}.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color:#fff}.laydate-theme-molv .layui-laydate-content{border:1px solid #e2e2e2;border-top:0;border-bottom:0}.laydate-theme-molv .laydate-main-list-1 .layui-laydate-content{border-left:none}.laydate-theme-molv .layui-laydate-footer{border:1px solid #e2e2e2}.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .laydate-month-list>li{border:1px solid #e2e2e2}.laydate-theme-grid .laydate-selected,.laydate-theme-grid .laydate-selected:hover{background-color:#f2f2f2!important;color:#009688!important}.laydate-theme-grid .laydate-selected.laydate-day-prev,.laydate-theme-grid .laydate-selected.laydate-day-next{color:#d2d2d2!important}.laydate-theme-grid .laydate-year-list,.laydate-theme-grid .laydate-month-list{margin:1px 0 0 1px}.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .laydate-month-list>li{margin:0 -1px -1px 0}.laydate-theme-grid .laydate-year-list>li{height:43px;line-height:43px}.laydate-theme-grid .laydate-month-list>li{height:71px;line-height:71px} ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/layui/modules/laydate.js ================================================ /*! laydate-v5.5.0 日期与时间组件 */ !function(window){var MOD_NAME="lay";var document=window.document;var lay=function(selector){return new Class(selector)};var Class=function(selector){var that=this;var elem=typeof selector==="object"?function(){return layui.isArray(selector)?selector:[selector]}():(this.selector=selector,document.querySelectorAll(selector||null));lay.each(elem,function(index,item){that.push(elem[index])})};Array.prototype.indexOf=Array.prototype.indexOf||function(searchElement,fromIndex){var rst=-1;fromIndex=fromIndex||0;layui.each(this,function(index,val){if(searchElement===val&&index>=fromIndex){rst=index;return !0}});return rst};Class.fn=Class.prototype=[];Class.fn.constructor=Class;lay.extend=function(){var ai=1;var length;var args=arguments;var clone=function(target,obj){target=target||(layui.type(obj)==="array"?[]:{});for(var i in obj){target[i]=(obj[i]&&obj[i].constructor===Object)?clone(target[i],obj[i]):obj[i]}return target};args[0]=typeof args[0]==="object"?args[0]:{};length=args.length;for(;ai(window.innerHeight||document.documentElement.clientHeight)};lay.getStyleRules=function(style,callback){if(!style){return}var sheet=style.sheet||style.styleSheet||{};var rules=sheet.cssRules||sheet.rules;if(typeof callback==="function"){layui.each(rules,function(i,item){if(callback(item,i)){return true}})}return rules};lay.style=function(options){options=options||{};var style=lay.elem("style");var styleText=options.text||"";var target=options.target;if(!styleText){return}if("styleSheet" in style){style.setAttribute("type","text/css");style.styleSheet.cssText=styleText}else{style.innerHTML=styleText}style.id="LAY-STYLE-"+(options.id||function(index){lay.style.index++;return"DF-"+index}(lay.style.index||0));if(target){var styleElem=lay(target).find("#"+style.id);styleElem[0]&&styleElem.remove();lay(target).append(style)}return style};lay.position=function(target,elem,opts){if(!elem){return}opts=opts||{};if(target===document||target===lay("body")[0]){opts.clickType="right"}var rect=opts.clickType==="right"?function(){var e=opts.e||window.event||{};return{left:e.clientX,top:e.clientY,right:e.clientX,bottom:e.clientY}}():target.getBoundingClientRect();var elemWidth=elem.offsetWidth;var elemHeight=elem.offsetHeight;var scrollArea=function(type){type=type?"scrollLeft":"scrollTop";return document.body[type]|document.documentElement[type]};var winArea=function(type){return document.documentElement[type?"clientWidth":"clientHeight"]};var margin="margin" in opts?opts.margin:5;var left=rect.left;var top=rect.bottom;if(opts.align==="center"){left=left-(elemWidth-target.offsetWidth)/2}else{if(opts.align==="right"){left=left-elemWidth+target.offsetWidth}}if(left+elemWidth+margin>winArea("width")){left=winArea("width")-elemWidth-margin}if(leftwinArea()){if(rect.top>elemHeight+margin&&rect.top<=winArea()){top=rect.top-elemHeight-margin*2}else{if(!opts.allowBottomOut){top=winArea()-elemHeight-margin*2;if(top<0){top=0}}}}var position=opts.position;if(position){elem.style.position=position}elem.style.left=left+(position==="fixed"?0:scrollArea(1))+"px";elem.style.top=top+(position==="fixed"?0:scrollArea())+"px";if(!lay.hasScrollbar()){var rect1=elem.getBoundingClientRect();if(!opts.SYSTEM_RELOAD&&(rect1.bottom+margin)>winArea()){opts.SYSTEM_RELOAD=true;setTimeout(function(){lay.position(target,elem,opts)},50)}}};lay.options=function(elem,opts){opts=typeof opts==="object"?opts:{attr:opts};if(elem===document){return{}}var othis=lay(elem);var attrName=opts.attr||"lay-options";var attrValue=othis.attr(attrName);try{return new Function("return "+(attrValue||"{}"))()}catch(ev){layui.hint().error(opts.errorText||[attrName+'="'+attrValue+'"',"\n parseerror: "+ev].join("\n"),"error");return{}}};lay.isTopElem=function(elem){var topElems=[document,lay("body")[0]],matched=false;lay.each(topElems,function(index,item){if(item===elem){return matched=true}});return matched};lay.clipboard={writeText:function(options){var text=String(options.text);try{navigator.clipboard.writeText(text).then(options.done)["catch"](options.error)}catch(e){var elem=document.createElement("textarea");elem.value=text;elem.style.position="fixed";elem.style.opacity="0";elem.style.top="0px";elem.style.left="0px";document.body.appendChild(elem);elem.select();try{document.execCommand("copy");typeof options.done==="function"&&options.done()}catch(err){typeof options.error==="function"&&options.error(err)}finally{elem.remove?elem.remove():document.body.removeChild(elem)}}}};Class.addStr=function(str,new_str){str=str.replace(/\s+/," ");new_str=new_str.replace(/\s+/," ").split(" ");lay.each(new_str,function(ii,item){if(!new RegExp("\\b"+item+"\\b").test(str)){str=str+" "+item}});return str.replace(/^\s|\s$/,"")};Class.removeStr=function(str,new_str){str=str.replace(/\s+/," ");new_str=new_str.replace(/\s+/," ").split(" ");lay.each(new_str,function(ii,item){var exp=new RegExp("\\b"+item+"\\b");if(exp.test(str)){str=str.replace(exp,"")}});return str.replace(/\s+/," ").replace(/^\s|\s$/,"")};Class.fn.find=function(selector){var that=this;var elem=[];var isObject=typeof selector==="object";this.each(function(i,item){var children=isObject&&item.contains(selector)?selector:item.querySelectorAll(selector||null);lay.each(children,function(index,child){elem.push(child)})});return lay(elem)};Class.fn.each=function(fn){return lay.each.call(this,this,fn)};Class.fn.addClass=function(className,type){return this.each(function(index,item){item.className=Class[type?"removeStr":"addStr"](item.className,className)})};Class.fn.removeClass=function(className){return this.addClass(className,true)};Class.fn.hasClass=function(className){var has=false;this.each(function(index,item){if(new RegExp("\\b"+className+"\\b").test(item.className)){has=true}});return has};Class.fn.css=function(key,value){var that=this;var parseValue=function(v){return isNaN(v)?v:(v+"px")};return(typeof key==="string"&&value===undefined)?function(){if(that.length>0){return that[0].style[key]}}():that.each(function(index,item){typeof key==="object"?lay.each(key,function(thisKey,thisValue){item.style[thisKey]=parseValue(thisValue)}):item.style[key]=parseValue(value)})};Class.fn.width=function(value){var that=this;return value===undefined?function(){if(that.length>0){return that[0].offsetWidth}}():that.each(function(index,item){that.css("width",value)})};Class.fn.height=function(value){var that=this;return value===undefined?function(){if(that.length>0){return that[0].offsetHeight}}():that.each(function(index,item){that.css("height",value)})};Class.fn.attr=function(key,value){var that=this;return value===undefined?function(){if(that.length>0){return that[0].getAttribute(key)}}():that.each(function(index,item){item.setAttribute(key,value)})};Class.fn.removeAttr=function(key){return this.each(function(index,item){item.removeAttribute(key)})};Class.fn.html=function(html){var that=this;return html===undefined?function(){if(that.length>0){return that[0].innerHTML}}():this.each(function(index,item){item.innerHTML=html})};Class.fn.val=function(value){var that=this;return value===undefined?function(){if(that.length>0){return that[0].value}}():this.each(function(index,item){item.value=value})};Class.fn.append=function(elem){return this.each(function(index,item){typeof elem==="object"?item.appendChild(elem):item.innerHTML=item.innerHTML+elem})};Class.fn.remove=function(elem){return this.each(function(index,item){elem?item.removeChild(elem):item.parentNode.removeChild(item)})};Class.fn.on=function(eventName,fn){return this.each(function(index,item){item.attachEvent?item.attachEvent("on"+eventName,function(e){e.target=e.srcElement;fn.call(item,e)}):item.addEventListener(eventName,fn,false)})};Class.fn.off=function(eventName,fn){return this.each(function(index,item){item.detachEvent?item.detachEvent("on"+eventName,fn):item.removeEventListener(eventName,fn,false)})};window.lay=lay;if(window.layui&&layui.define){layui.define(function(exports){exports(MOD_NAME,lay)})}}(window,window.document);!function(window,document){var isLayui=window.layui&&layui.define,ready={getPath:(window.lay&&lay.getPath)?lay.getPath:"",link:function(href,fn,cssname){if(!laydate.path){return}if(window.lay&&lay.layui){lay.layui.link(laydate.path+href,fn,cssname)}}};var GLOBAL=window.LAYUI_GLOBAL||{};var MOD_NAME="laydate";var MOD_ID="layui-"+MOD_NAME+"-id";var laydate={v:"5.5.0",config:{weekStart:0,},index:(window.laydate&&window.laydate.v)?100000:0,path:GLOBAL.laydate_dir||ready.getPath,set:function(options){var that=this;that.config=lay.extend({},that.config,options);return that},ready:function(callback){var cssname="laydate";var ver="";var path=(isLayui?"modules/":"")+"laydate.css?v="+laydate.v+ver;isLayui?(layui["layui.all"]?(typeof callback==="function"&&callback()):layui.addcss(path,callback,cssname)):ready.link(path,callback,cssname);return this}};var thisModule=function(){var that=this;var options=that.config;var id=options.id;thisModule.that[id]=that;return that.inst={hint:function(content){that.hint.call(that,content)},reload:function(options){that.reload.call(that,options)},config:that.config}};var MOD_NAME="laydate";var ELEM=".layui-laydate";var THIS="layui-this";var SHOW="layui-show";var HIDE="layui-hide";var DISABLED="laydate-disabled";var LIMIT_YEAR=[100,200000];var ELEM_STATIC="layui-laydate-static";var ELEM_LIST="layui-laydate-list";var ELEM_SELECTED="laydate-selected";var ELEM_HINT="layui-laydate-hint";var ELEM_DAY_NOW="laydate-day-now";var ELEM_PREV="laydate-day-prev";var ELEM_NEXT="laydate-day-next";var ELEM_FOOTER="layui-laydate-footer";var ELEM_SHORTCUT="layui-laydate-shortcut";var ELEM_NOW=".laydate-btns-now";var ELEM_CONFIRM=".laydate-btns-confirm";var ELEM_TIME_TEXT="laydate-time-text";var ELEM_TIME_BTN="laydate-btns-time";var ELEM_PREVIEW="layui-laydate-preview";var ELEM_MAIN="layui-laydate-main";var ELEM_SHADE="layui-laydate-shade";var Class=function(options){var that=this;that.index=++laydate.index;that.config=lay.extend({},that.config,laydate.config,options);var elem=lay(options.elem||that.config.elem);if(elem.length>1){lay.each(elem,function(){laydate.render(lay.extend({},that.config,{elem:this}))});return that}options=lay.extend(that.config,lay.options(elem[0]));if(elem[0]&&elem.attr(MOD_ID)){var newThat=thisModule.getThis(elem.attr(MOD_ID));if(!newThat){return}return newThat.reload(options)}options.id="id" in options?options.id:(elem.attr("id")||that.index);options.index=that.index;laydate.ready(function(){that.init()})};var dateType="yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s";thisModule.formatArr=function(format){return(format||"").match(new RegExp(dateType+"|.","g"))||[]};Class.isLeapYear=function(year){return(year%4===0&&year%100!==0)||year%400===0};Class.prototype.config={type:"date",range:false,format:"yyyy-MM-dd",value:null,isInitValue:true,min:"1900-1-1",max:"2099-12-31",trigger:"click",show:false,showBottom:true,isPreview:true,btns:["clear","now","confirm"],lang:"cn",theme:"default",position:null,calendar:false,mark:{},holidays:null,zIndex:null,done:null,change:null,autoConfirm:true,shade:0};Class.prototype.lang=function(){var that=this,options=that.config,text={cn:{weeks:["日","一","二","三","四","五","六"],time:["时","分","秒"],timeTips:"选择时间",startTime:"开始时间",endTime:"结束时间",dateTips:"返回日期",month:["一","二","三","四","五","六","七","八","九","十","十一","十二"],tools:{confirm:"确定",clear:"清空",now:"现在"},timeout:"结束时间不能早于开始时间
          请重新选择",invalidDate:"不在有效日期或时间范围内",formatError:["日期格式不合法
          必须遵循下述格式:
          ","
          已为你重置"],preview:"当前选中的结果"},en:{weeks:["Su","Mo","Tu","We","Th","Fr","Sa"],time:["Hours","Minutes","Seconds"],timeTips:"Select Time",startTime:"Start Time",endTime:"End Time",dateTips:"Select Date",month:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],tools:{confirm:"Confirm",clear:"Clear",now:"Now"},timeout:"End time cannot be less than start Time
          Please re-select",invalidDate:"Invalid date",formatError:["The date format error
          Must be followed:
          ","
          It has been reset"],preview:"The selected result"}};return text[options.lang]||text["cn"]};Class.prototype.reload=function(options){var that=this;that.config=lay.extend({},that.config,options);that.init()};Class.prototype.init=function(){var that=this,options=that.config,isStatic=options.position==="static",format={year:"yyyy",month:"yyyy-MM",date:"yyyy-MM-dd",time:"HH:mm:ss",datetime:"yyyy-MM-dd HH:mm:ss"};options.elem=lay(options.elem);options.eventElem=lay(options.eventElem);if(!options.elem[0]){return}layui.type(options.theme)!=="array"&&(options.theme=[options.theme]);if(options.fullPanel){if(options.type!=="datetime"||options.range){delete options.fullPanel}}that.rangeStr=options.range?(typeof options.range==="string"?options.range:"-"):"";that.rangeLinked=!!(options.range&&options.rangeLinked&&(options.type==="date"||options.type==="datetime"));that.autoCalendarModel=function(){var state=that.rangeLinked;that.rangeLinked=(options.range&&(options.type==="date"||options.type==="datetime"))&&((!that.startDate||!that.endDate)||(that.startDate&&that.endDate&&that.startDate.year===that.endDate.year&&that.startDate.month===that.endDate.month));lay(that.elem)[that.rangeLinked?"addClass":"removeClass"]("layui-laydate-linkage");return that.rangeLinked!=state};that.autoCalendarModel.auto=that.rangeLinked&&options.rangeLinked==="auto";if(layui.type(options.range)==="array"){that.rangeElem=[lay(options.range[0]),lay(options.range[1])]}if(!format[options.type]){window.console&&console.error&&console.error("laydate type error:'"+options.type+"' is not supported");options.type="date"}if(options.format===format.date){options.format=format[options.type]||format.date}that.format=thisModule.formatArr(options.format);if(options.weekStart){if(!/^[0-6]$/.test(options.weekStart)){var lang=that.lang();options.weekStart=lang.weeks.indexOf(options.weekStart);if(options.weekStart===-1){options.weekStart=0}}}that.EXP_IF="";that.EXP_SPLIT="";lay.each(that.format,function(i,item){var EXP=new RegExp(dateType).test(item)?"\\d{"+function(){if(new RegExp(dateType).test(that.format[i===0?i+1:i-1]||"")){if(/^yyyy|y$/.test(item)){return 4}return item.length}if(/^yyyy$/.test(item)){return"1,4"}if(/^y$/.test(item)){return"1,308"}return"1,2"}()+"}":"\\"+item;that.EXP_IF=that.EXP_IF+EXP;that.EXP_SPLIT=that.EXP_SPLIT+"("+EXP+")"});that.EXP_IF_ONE=new RegExp("^"+that.EXP_IF+"$");that.EXP_IF=new RegExp("^"+(options.range?that.EXP_IF+"\\s\\"+that.rangeStr+"\\s"+that.EXP_IF:that.EXP_IF)+"$");that.EXP_SPLIT=new RegExp("^"+that.EXP_SPLIT+"$","");if(!that.isInput(options.elem[0])){if(options.trigger==="focus"){options.trigger="click"}}options.elem.attr("lay-key",that.index);options.eventElem.attr("lay-key",that.index);options.elem.attr(MOD_ID,options.id);options.mark=lay.extend({},(options.calendar&&options.lang==="cn")?{"0-1-1":"元旦","0-2-14":"情人","0-3-8":"妇女","0-3-12":"植树","0-4-1":"愚人","0-5-1":"劳动","0-5-4":"青年","0-6-1":"儿童","0-9-10":"教师","0-10-1":"国庆","0-12-25":"圣诞"}:{},options.mark);lay.each(["min","max"],function(i,item){var ymd=[];var hms=[];if(typeof options[item]==="number"){var day=options[item],tDate=new Date(),time=that.newDate({year:tDate.getFullYear(),month:tDate.getMonth(),date:tDate.getDate(),hours:i?23:0,minutes:i?59:0,seconds:i?59:0}).getTime(),STAMP=86400000,thisDate=new Date(day?(day0){return true}var divHeader=lay.elem("div",{"class":"layui-laydate-header"}),headerChild=[function(){var elem=lay.elem("i",{"class":"layui-icon laydate-icon laydate-prev-y"});elem.innerHTML="";return elem}(),function(){var elem=lay.elem("i",{"class":"layui-icon laydate-icon laydate-prev-m"});elem.innerHTML="";return elem}(),function(){var elem=lay.elem("div",{"class":"laydate-set-ym"}),spanY=lay.elem("span"),spanM=lay.elem("span");elem.appendChild(spanY);elem.appendChild(spanM);return elem}(),function(){var elem=lay.elem("i",{"class":"layui-icon laydate-icon laydate-next-m"});elem.innerHTML="";return elem}(),function(){var elem=lay.elem("i",{"class":"layui-icon laydate-icon laydate-next-y"});elem.innerHTML="";return elem}()],divContent=lay.elem("div",{"class":"layui-laydate-content"}),table=lay.elem("table"),thead=lay.elem("thead"),theadTr=lay.elem("tr");lay.each(headerChild,function(i,item){divHeader.appendChild(item)});thead.appendChild(theadTr);lay.each(new Array(6),function(i){var tr=table.insertRow(0);lay.each(new Array(7),function(j){if(i===0){var th=lay.elem("th");th.innerHTML=lang.weeks[(j+options.weekStart)%7];theadTr.appendChild(th)}tr.insertCell(j)})});table.insertBefore(thead,table.children[0]);divContent.appendChild(table);elemMain[i]=lay.elem("div",{"class":ELEM_MAIN+" laydate-main-list-"+i});elemMain[i].appendChild(divHeader);elemMain[i].appendChild(divContent);elemHeader.push(headerChild);elemCont.push(divContent);elemTable.push(table)});lay(divFooter).html(function(){var html=[],btns=[];if(options.type==="datetime"){html.push(''+lang.timeTips+"")}if(!(!options.range&&options.type==="datetime")||options.fullPanel){html.push('')}lay.each(options.btns,function(i,item){var title=lang.tools[item]||"btn";if(options.range&&item==="now"){return}if(isStatic&&item==="clear"){title=options.lang==="cn"?"重置":"Reset"}btns.push(''+title+"")});html.push('");return html.join("")}());if(options.shortcuts){elem.appendChild(divShortcut);lay(divShortcut).html(function(){var shortcutBtns=[];lay.each(options.shortcuts,function(i,item){shortcutBtns.push('
        • '+item.text+"
        • ")});return shortcutBtns.join("")}()).find("li").on("click",function(event){var btnSetting=options.shortcuts[this.dataset["index"]]||{};var value=(typeof btnSetting.value==="function"?btnSetting.value():btnSetting.value)||[];if(!layui.isArray(value)){value=[value]}var type=options.type;lay.each(value,function(i,item){var dateTime=[options.dateTime,that.endDate][i];if(type==="time"&&layui.type(item)!=="date"){if(that.EXP_IF.test(item)){item=(item.match(that.EXP_SPLIT)||[]).slice(1);lay.extend(dateTime,{hours:item[0]|0,minutes:item[2]|0,seconds:item[4]|0})}}else{lay.extend(dateTime,that.systemDate(layui.type(item)==="date"?item:new Date(item)))}if(type==="time"||type==="datetime"){that[["startTime","endTime"][i]]={hours:dateTime.hours,minutes:dateTime.minutes,seconds:dateTime.seconds,}}if(i===0){that.startDate=lay.extend({},dateTime)}else{that.endState=true}if(type==="year"||type==="month"||type==="time"){that.listYM[i]=[dateTime.year,dateTime.month+1]}else{if(i){that.autoCalendarModel.auto&&that.autoCalendarModel()}}});that.checkDate("limit").calendar(null,null,"init");var timeBtn=lay(that.footer).find("."+ELEM_TIME_BTN).removeClass(DISABLED);timeBtn&&timeBtn.attr("lay-type")==="date"&&timeBtn[0].click();that.done(null,"change");lay(this).addClass(THIS);if(options.position!=="static"){that.setValue(that.parse()).done().remove()}})}lay.each(elemMain,function(i,main){elem.appendChild(main)});options.showBottom&&elem.appendChild(divFooter);var style=lay.elem("style");var styleText=[];var colorTheme;var isPrimaryColor=true;lay.each(options.theme,function(index,theme){if(isPrimaryColor&&/^#/.test(theme)){colorTheme=true;isPrimaryColor=false;styleText.push(["#{{id}} .layui-laydate-header{background-color:{{theme}};}","#{{id}} li.layui-this,#{{id}} td.layui-this>div{background-color:{{theme}} !important;}",options.theme.indexOf("circle")!==-1?"":"#{{id}} .layui-this{background-color:{{theme}} !important;}","#{{id}} .laydate-day-now{color:{{theme}} !important;}","#{{id}} .laydate-day-now:after{border-color:{{theme}} !important;}"].join("").replace(/{{id}}/g,that.elemID).replace(/{{theme}}/g,theme));return}if(!isPrimaryColor&&/^#/.test(theme)){styleText.push(["#{{id}} .laydate-selected>div{background-color:{{theme}} !important;}","#{{id}} .laydate-selected:hover>div{background-color:{{theme}} !important;}"].join("").replace(/{{id}}/g,that.elemID).replace(/{{theme}}/g,theme))}});if(options.shortcuts&&options.range){styleText.push("#{{id}}.layui-laydate-range{width: 628px;}".replace(/{{id}}/g,that.elemID))}if(styleText.length){styleText=styleText.join("");if("styleSheet" in style){style.setAttribute("type","text/css");style.styleSheet.cssText=styleText}else{style.innerHTML=styleText}colorTheme&&lay(elem).addClass("laydate-theme-molv");elem.appendChild(style)}that.remove(Class.thisElemDate);laydate.thisId=options.id;isStatic?options.elem.append(elem):(document.body.appendChild(elem),that.position());var shade=options.shade?('
          '):"";elem.insertAdjacentHTML("beforebegin",shade);that.checkDate().calendar(null,0,"init");that.changeEvent();Class.thisElemDate=that.elemID;that.renderAdditional();typeof options.ready==="function"&&options.ready(lay.extend({},options.dateTime,{month:options.dateTime.month+1}));that.preview()};Class.prototype.remove=function(prev){var that=this,options=that.config,elem=lay("#"+(prev||that.elemID));if(!elem[0]){return that}if(!elem.hasClass(ELEM_STATIC)){that.checkDate(function(){elem.remove();delete that.startDate;delete that.endDate;delete that.endState;delete that.startTime;delete that.endTime;delete laydate.thisId;typeof options.close==="function"&&options.close(that)})}lay("."+ELEM_SHADE).remove();return that};Class.prototype.position=function(){var that=this,options=that.config;lay.position(options.elem[0],that.elem,{position:options.position});return that};Class.prototype.hint=function(opts){var that=this;var options=that.config;var div=lay.elem("div",{"class":ELEM_HINT});if(!that.elem){return}if(typeof opts==="object"){opts=opts||{}}else{opts={content:opts}}div.innerHTML=opts.content||"";lay(that.elem).find("."+ELEM_HINT).remove();that.elem.appendChild(div);clearTimeout(that.hinTimer);that.hinTimer=setTimeout(function(){lay(that.elem).find("."+ELEM_HINT).remove()},"ms" in opts?opts.ms:3000)};Class.prototype.getAsYM=function(Y,M,type){type?M--:M++;if(M<0){M=11;Y--}if(M>11){M=0;Y++}return[Y,M]};Class.prototype.systemDate=function(newDate){var thisDate=newDate||new Date();return{year:thisDate.getFullYear(),month:thisDate.getMonth(),date:thisDate.getDate(),hours:newDate?newDate.getHours():0,minutes:newDate?newDate.getMinutes():0,seconds:newDate?newDate.getSeconds():0}};Class.prototype.checkDate=function(fn){var that=this,thisDate=new Date(),options=that.config,lang=that.lang(),dateTime=options.dateTime=options.dateTime||that.systemDate(),thisMaxDate,error,elem=options.elem[0],valType=that.isInput(elem)?"val":"html",value=function(){if(that.rangeElem){var vals=[that.rangeElem[0].val(),that.rangeElem[1].val()];if(vals[0]&&vals[1]){return vals.join(" "+that.rangeStr+" ")}}return that.isInput(elem)?elem.value:(options.position==="static"?"":lay(elem).attr("lay-date"))}(),checkValid=function(dateTime){if(!dateTime){return}if(dateTime.year>LIMIT_YEAR[1]){dateTime.year=LIMIT_YEAR[1],error=true}if(dateTime.month>11){dateTime.month=11,error=true}if(dateTime.seconds>59){dateTime.seconds=0,dateTime.minutes++,error=true}if(dateTime.minutes>59){dateTime.minutes=0,dateTime.hours++,error=true}if(dateTime.hours>23){dateTime.hours=0,error=true}thisMaxDate=laydate.getEndDate(dateTime.month+1,dateTime.year);if(dateTime.date>thisMaxDate){dateTime.date=thisMaxDate,error=true}},initDate=function(dateTime,value,index){var startEnd=["startTime","endTime"];value=(value.match(that.EXP_SPLIT)||[]).slice(1);index=index||0;if(options.range){that[startEnd[index]]=that[startEnd[index]]||{}}lay.each(that.format,function(i,item){var thisv=parseFloat(value[i]);if(value[i].length23){thisv=23,error=true}dateTime.hours=thisv;options.range&&(that[startEnd[index]].hours=thisv)}else{if(/mm|m/.test(item)){if(thisv<0){thisv=0,error=true}if(thisv>59){thisv=59,error=true}dateTime.minutes=thisv;options.range&&(that[startEnd[index]].minutes=thisv)}else{if(/ss|s/.test(item)){if(thisv<0){thisv=0,error=true}if(thisv>59){thisv=59,error=true}dateTime.seconds=thisv;options.range&&(that[startEnd[index]].seconds=thisv)}}}}}}});checkValid(dateTime)};if(fn==="limit"){if(options.range){checkValid(that.rangeLinked?that.startDate:dateTime);that.endDate&&checkValid(that.endDate)}else{checkValid(dateTime)}return that}value=value||options.value;if(typeof value==="string"){value=value.replace(/\s+/g," ").replace(/^\s|\s$/g,"")}var getEndDate=function(){if(options.range){that.endDate=that.endDate||lay.extend({},options.dateTime,function(){var obj={},dateTime=options.dateTime,EYM=that.getAsYM(dateTime.year,dateTime.month);if(options.type==="year"){obj.year=dateTime.year+1}else{if(options.type!=="time"){obj.year=EYM[0];obj.month=EYM[1]}}if(options.type==="datetime"||options.type==="time"){obj.hours=23;obj.minutes=obj.seconds=59}return obj}())}};getEndDate();if(typeof value==="string"&&value){if(that.EXP_IF.test(value)){if(options.range){value=value.split(" "+that.rangeStr+" ");lay.each([options.dateTime,that.endDate],function(i,item){initDate(item,value[i],i)})}else{initDate(dateTime,value)}}else{that.hint(lang.formatError[0]+(options.range?(options.format+" "+that.rangeStr+" "+options.format):options.format)+lang.formatError[1]);error=true}}else{if(value&&layui.type(value)==="date"){options.dateTime=that.systemDate(value)}else{options.dateTime=that.systemDate();delete that.startTime;delete that.endDate;getEndDate();delete that.endTime}}(function(){if(that.rangeElem){var vals=[that.rangeElem[0].val(),that.rangeElem[1].val()],arrDate=[options.dateTime,that.endDate];lay.each(vals,function(_i,_v){if(that.EXP_IF_ONE.test(_v)){initDate(arrDate[_i],_v,_i)}})}})();checkValid(dateTime);if(options.range){checkValid(that.endDate)}if(error&&value){that.setValue(options.range?(that.endDate?that.parse():""):that.parse())}var minMaxError;if(that.getDateTime(dateTime)>that.getDateTime(options.max)){dateTime=options.dateTime=lay.extend({},options.max);minMaxError=true}else{if(that.getDateTime(dateTime)that.getDateTime(options.max)){that.endDate=lay.extend({},options.max);minMaxError=true}that.startTime={hours:options.dateTime.hours,minutes:options.dateTime.minutes,seconds:options.dateTime.seconds,};that.endTime={hours:that.endDate.hours,minutes:that.endDate.minutes,seconds:that.endDate.seconds,};if(options.type==="month"){options.dateTime.date=1;that.endDate.date=1}}if(minMaxError&&value){that.setValue(that.parse());that.hint("value "+lang.invalidDate+lang.formatError[1])}that.startDate=that.startDate||value&&lay.extend({},options.dateTime);that.autoCalendarModel.auto&&that.autoCalendarModel();that.endState=!options.range||!that.rangeLinked||!!(that.startDate&&that.endDate);fn&&fn();return that};Class.prototype.mark=function(td,YMD){var that=this,mark,options=that.config;lay.each(options.mark,function(key,title){var keys=key.split("-");if((keys[0]==YMD[0]||keys[0]==0)&&(keys[1]==YMD[1]||keys[1]==0)&&keys[2]==YMD[2]){mark=title||YMD[2]}});mark&&td.find("div").html(''+mark+"");return that};Class.prototype.holidays=function(td,YMD){var that=this;var options=that.config;var type=["","work"];if(layui.type(options.holidays)!=="array"){return that}lay.each(options.holidays,function(idx,item){lay.each(item,function(i,dayStr){if(dayStr===td.attr("lay-ymd")){td.find("div").html('"+YMD[2]+"")}})});return that};Class.prototype.limit=function(opts){opts=opts||{};var that=this;var options=that.config;var timestamp={};var dateTime=opts.index>(opts.time?0:41)?that.endDate:options.dateTime;var isOut;lay.each({now:lay.extend({},dateTime,opts.date||{}),min:options.min,max:options.max},function(key,item){timestamp[key]=that.newDate(lay.extend({year:item.year,month:opts.type==="year"?0:item.month,date:(opts.type==="year"||opts.type==="month")?1:item.date},function(){var hms={};lay.each(opts.time,function(i,keys){hms[keys]=item[keys]});return hms}())).getTime()});isOut=timestamp.nowtimestamp.max;opts.elem&&opts.elem[isOut?"addClass":"removeClass"](DISABLED);return isOut};Class.prototype.thisDateTime=function(index){var that=this,options=that.config;return index?that.endDate:options.dateTime};Class.prototype.calendar=function(value,index,type){index=index?1:0;var that=this,options=that.config,dateTime=value||that.thisDateTime(index),thisDate=new Date(),startWeek,prevMaxDate,thisMaxDate,lang=that.lang(),isAlone=options.type!=="date"&&options.type!=="datetime",tds=lay(that.table[index]).find("td"),elemYM=lay(that.elemHeader[index][2]).find("span");if(dateTime.yearLIMIT_YEAR[1]){dateTime.year=LIMIT_YEAR[1],that.hint(lang.invalidDate)}if(!that.firstDate){that.firstDate=lay.extend({},dateTime)}thisDate.setFullYear(dateTime.year,dateTime.month,1);startWeek=(thisDate.getDay()+(7-options.weekStart))%7;prevMaxDate=laydate.getEndDate(dateTime.month||12,dateTime.year);thisMaxDate=laydate.getEndDate(dateTime.month+1,dateTime.year);lay.each(tds,function(index_,item){var YMD=[dateTime.year,dateTime.month],st;item=lay(item);item.removeAttr("class");if(index_=startWeek&&index_"+YMD[2]+"");that.mark(item,YMD).holidays(item,YMD).limit({elem:item,date:{year:YMD[0],month:YMD[1]-1,date:YMD[2]},index:index_})});lay(elemYM[0]).attr("lay-ym",dateTime.year+"-"+(dateTime.month+1));lay(elemYM[1]).attr("lay-ym",dateTime.year+"-"+(dateTime.month+1));if(options.lang==="cn"){lay(elemYM[0]).attr("lay-type","year").html(dateTime.year+" 年");lay(elemYM[1]).attr("lay-type","month").html((dateTime.month+1)+" 月")}else{lay(elemYM[0]).attr("lay-type","month").html(lang.month[dateTime.month]);lay(elemYM[1]).attr("lay-type","year").html(dateTime.year)}if(isAlone){if(options.range){if(value||type!=="init"){that.listYM=[[(that.startDate||options.dateTime).year,(that.startDate||options.dateTime).month+1],[that.endDate.year,that.endDate.month+1]];that.list(options.type,0).list(options.type,1);options.type==="time"?that.setBtnStatus("时间",lay.extend({},that.systemDate(),that.startTime),lay.extend({},that.systemDate(),that.endTime)):that.setBtnStatus(true)}}else{that.listYM=[[dateTime.year,dateTime.month+1]];that.list(options.type,0)}}if(options.range&&type==="init"){if(that.rangeLinked){var EYM=that.getAsYM(dateTime.year,dateTime.month,index?"sub":null);that.calendar(lay.extend({},dateTime,{year:EYM[0],month:EYM[1]}),1-index)}else{that.calendar(null,1-index)}}if(!options.range){var timeParams=["hours","minutes","seconds"];that.limit({elem:lay(that.footer).find(ELEM_NOW),date:that.systemDate(/^(datetime|time)$/.test(options.type)?new Date():null),index:0,time:timeParams});that.limit({elem:lay(that.footer).find(ELEM_CONFIRM),index:0,time:timeParams})}that.setBtnStatus();lay(that.shortcut).find("li."+THIS).removeClass(THIS);if(options.range&&!isAlone&&type!=="init"){that.stampRange()}return that};Class.prototype.list=function(type,index){var that=this,options=that.config,dateTime=that.rangeLinked?options.dateTime:[options.dateTime,that.endDate][index],lang=that.lang(),isAlone=options.range&&options.type!=="date"&&options.type!=="datetime",ul=lay.elem("ul",{"class":ELEM_LIST+" "+({year:"laydate-year-list",month:"laydate-month-list",time:"laydate-time-list"})[type]}),elemHeader=that.elemHeader[index],elemYM=lay(elemHeader[2]).find("span"),elemCont=that.elemCont[index||0],haveList=lay(elemCont).find("."+ELEM_LIST)[0],isCN=options.lang==="cn",text=isCN?"年":"",listYM=that.listYM[index]||{},hms=["hours","minutes","seconds"],startEnd=["startTime","endTime"][index];if(listYM[0]<1){listYM[0]=1}if(type==="year"){var yearNum,startY=yearNum=listYM[0]-7;if(startY<1){startY=yearNum=1}lay.each(new Array(15),function(i){var li=lay.elem("li",{"lay-ym":yearNum}),ymd={year:yearNum,month:0,date:1};yearNum==listYM[0]&&lay(li).addClass(THIS);li.innerHTML=yearNum+text;ul.appendChild(li);that.limit({elem:lay(li),date:ymd,index:index,type:type});yearNum++});lay(elemYM[isCN?0:1]).attr("lay-ym",(yearNum-8)+"-"+listYM[1]).html((startY+text)+" - "+(yearNum-1+text))}else{if(type==="month"){lay.each(new Array(12),function(i){var li=lay.elem("li",{"lay-ym":i}),ymd={year:listYM[0],month:i,date:1};i+1==listYM[1]&&lay(li).addClass(THIS);li.innerHTML=lang.month[i]+(isCN?"月":"");ul.appendChild(li);that.limit({elem:lay(li),date:ymd,index:index,type:type})});lay(elemYM[isCN?0:1]).attr("lay-ym",listYM[0]+"-"+listYM[1]).html(listYM[0]+text)}else{if(type==="time"){var setTimeStatus=function(){lay(ul).find("ol").each(function(i,ol){lay(ol).find("li").each(function(ii,li){that.limit({elem:lay(li),date:[{hours:ii},{hours:that[startEnd].hours,minutes:ii},{hours:that[startEnd].hours,minutes:that[startEnd].minutes,seconds:ii}][i],index:index,time:[["hours"],["hours","minutes"],["hours","minutes","seconds"]][i]})})});if(!options.range){that.limit({elem:lay(that.footer).find(ELEM_CONFIRM),date:that[startEnd],inedx:0,time:["hours","minutes","seconds"]})}};if(options.range){if(!that[startEnd]){that[startEnd]=startEnd==="startTime"?dateTime:that.endDate}}else{that[startEnd]=dateTime}lay.each([24,60,60],function(i,item){var li=lay.elem("li"),childUL=["

          "+lang.time[i]+"

            "];lay.each(new Array(item),function(ii){childUL.push(""+lay.digit(ii,2)+"")});li.innerHTML=childUL.join("")+"
          ";ul.appendChild(li)});setTimeStatus()}}}if(haveList){elemCont.removeChild(haveList)}elemCont.appendChild(ul);if(type==="year"||type==="month"){lay(that.elemMain[index]).addClass("laydate-ym-show");lay(ul).find("li").on("click",function(){var ym=lay(this).attr("lay-ym")|0;if(lay(this).hasClass(DISABLED)){return}if(that.rangeLinked){lay.extend(dateTime,{year:type==="year"?ym:listYM[0],month:type==="year"?listYM[1]-1:ym})}else{dateTime[type]=ym}var isYearOrMonth=options.type==="year"||options.type==="month";if(isYearOrMonth){lay(ul).find("."+THIS).removeClass(THIS);lay(this).addClass(THIS);if(options.type==="month"&&type==="year"){that.listYM[index][0]=ym;isAlone&&((index?that.endDate:dateTime).year=ym);that.list("month",index)}}else{that.checkDate("limit").calendar(dateTime,index,"init");that.closeList()}that.setBtnStatus();if(!options.range&&options.autoConfirm){if((options.type==="month"&&type==="month")||(options.type==="year"&&type==="year")){that.setValue(that.parse()).done().remove()}}(that.autoCalendarModel.auto&&!that.rangeLinked)?that.choose(lay(elemCont).find("td.layui-this"),index):(that.endState&&that.done(null,"change"));lay(that.footer).find("."+ELEM_TIME_BTN).removeClass(DISABLED)})}else{var span=lay.elem("span",{"class":ELEM_TIME_TEXT}),scroll=function(){lay(ul).find("ol").each(function(i){var ol=this,li=lay(ol).find("li");ol.scrollTop=30*(that[startEnd][hms[i]]-2);if(ol.scrollTop<=0){li.each(function(ii,item){if(!lay(this).hasClass(DISABLED)){ol.scrollTop=30*(ii-2);return true}})}})},haveSpan=lay(elemHeader[2]).find("."+ELEM_TIME_TEXT);scroll();span.innerHTML=options.range?[lang.startTime,lang.endTime][index]:lang.timeTips;lay(that.elemMain[index]).addClass("laydate-time-show");if(haveSpan[0]){haveSpan.remove()}elemHeader[2].appendChild(span);lay(ul).find("ol").each(function(i){var ol=this;lay(ol).find("li").on("click",function(){var value=this.innerHTML|0;if(lay(this).hasClass(DISABLED)){return}if(options.range){that[startEnd][hms[i]]=value}else{dateTime[hms[i]]=value}lay(ol).find("."+THIS).removeClass(THIS);lay(this).addClass(THIS);setTimeStatus();scroll();(that.endDate||options.type==="time"||(options.type==="datetime"&&options.fullPanel))&&that.done(null,"change");that.setBtnStatus()})})}return that};Class.prototype.listYM=[];Class.prototype.closeList=function(){var that=this,options=that.config;lay.each(that.elemCont,function(index,item){lay(this).find("."+ELEM_LIST).remove();lay(that.elemMain[index]).removeClass("laydate-ym-show laydate-time-show")});lay(that.elem).find("."+ELEM_TIME_TEXT).remove()};Class.prototype.setBtnStatus=function(tips,start,end){var that=this,options=that.config,lang=that.lang(),isOut,elemBtn=lay(that.footer).find(ELEM_CONFIRM);if(options.range&&options.type!=="time"){start=start||(that.rangeLinked?that.startDate:options.dateTime);end=end||that.endDate;isOut=!that.endState||that.newDate(start).getTime()>that.newDate(end).getTime();(that.limit({date:start})||that.limit({date:end}))?elemBtn.addClass(DISABLED):elemBtn[isOut?"addClass":"removeClass"](DISABLED);if(tips&&isOut){that.hint(typeof tips==="string"?lang.timeout.replace(/日期/g,tips):lang.timeout)}}};Class.prototype.parse=function(state,date){var that=this;var options=that.config;var startDate=(that.rangeLinked?that.startDate:options.dateTime);var dateTime=date||(state=="end"?lay.extend({},that.endDate,that.endTime):(options.range?lay.extend({},startDate||options.dateTime,that.startTime):options.dateTime));var format=laydate.parse(dateTime,that.format,1);if(options.range&&state===undefined){return format+" "+that.rangeStr+" "+that.parse("end")}return format};Class.prototype.newDate=function(dateTime){dateTime=dateTime||{};return new Date(dateTime.year||1,dateTime.month||0,dateTime.date||1,dateTime.hours||0,dateTime.minutes||0,dateTime.seconds||0)};Class.prototype.getDateTime=function(obj){return this.newDate(obj).getTime()};Class.prototype.setValue=function(value){var that=this,options=that.config,elem=options.elem[0];if(options.position==="static"){return that}value=value||"";if(that.isInput(elem)){lay(elem).val(value)}else{var rangeElem=that.rangeElem;if(rangeElem){if(layui.type(value)!=="array"){value=value.split(" "+that.rangeStr+" ")}rangeElem[0].val(value[0]||"");rangeElem[1].val(value[1]||"")}else{if(lay(elem).find("*").length===0){lay(elem).html(value)}lay(elem).attr("lay-date",value)}}return that};Class.prototype.preview=function(){var that=this,options=that.config;if(!options.isPreview){return}var elemPreview=lay(that.elem).find("."+ELEM_PREVIEW),value=options.range?((that.rangeLinked?that.endState:that.endDate)?that.parse():""):that.parse();elemPreview.html(value);var oldValue=elemPreview.html();oldValue&&(elemPreview.css({"color":"#16b777"}),setTimeout(function(){elemPreview.css({"color":"#777"})},300))};Class.prototype.renderAdditional=function(){var that=this;var options=that.config;if(options.fullPanel){that.list("time",0)}};Class.prototype.stampRange=function(){var that=this,options=that.config,startTime=that.rangeLinked?that.startDate:options.dateTime,endTime,tds=lay(that.elem).find("td");if(options.range&&!that.endState){lay(that.footer).find(ELEM_CONFIRM).addClass(DISABLED)}startTime=startTime&&that.newDate({year:startTime.year,month:startTime.month,date:startTime.date}).getTime();endTime=that.endState&&that.endDate&&that.newDate({year:that.endDate.year,month:that.endDate.month,date:that.endDate.date}).getTime();lay.each(tds,function(i,item){var ymd=lay(item).attr("lay-ymd").split("-");var thisTime=that.newDate({year:ymd[0],month:ymd[1]-1,date:ymd[2]}).getTime();if(options.rangeLinked&&!that.startDate){if(thisTime===that.newDate(that.systemDate()).getTime()){lay(item).addClass(lay(item).hasClass(ELEM_PREV)||lay(item).hasClass(ELEM_NEXT)?"":ELEM_DAY_NOW)}}lay(item).removeClass(ELEM_SELECTED+" "+THIS);if(thisTime===startTime||thisTime===endTime){(that.rangeLinked||(!that.rangeLinked&&(i<42?thisTime===startTime:thisTime===endTime)))&&lay(item).addClass(lay(item).hasClass(ELEM_PREV)||lay(item).hasClass(ELEM_NEXT)?ELEM_SELECTED:THIS)}if(thisTime>startTime&&thisTimethat.getDateTime(options.max)){that[item]={hours:options.max.hours,minutes:options.max.minutes,seconds:options.max.seconds};lay.extend(dateTime,that[item])}}}});if(!index){that.startDate=lay.extend({},dateTime)}if(that.endState&&!that.limit({date:that.thisDateTime(1-index)})){var isChange;if(that.endState&&that.autoCalendarModel.auto){isChange=that.autoCalendarModel()}if((isChange||that.rangeLinked&&that.endState)&&that.newDate(that.startDate)>that.newDate(that.endDate)){var isSameDate=that.startDate.year===that.endDate.year&&that.startDate.month===that.endDate.month&&that.startDate.date===that.endDate.date;var startDate=that.startDate;that.startDate=lay.extend({},that.endDate,isSameDate?{}:that.startTime);options.dateTime=lay.extend({},that.startDate);that.endDate=lay.extend({},startDate,isSameDate?{}:that.endTime);isSameDate&&(startDate=that.startTime,that.startTime=that.endTime,that.endTime=startDate)}isChange&&(options.dateTime=lay.extend({},that.startDate))}if(that.rangeLinked){var dateTimeTemp=lay.extend({},dateTime);if(panelIndex&&!index&&!isChange){var YM=that.getAsYM(dateTime.year,dateTime.month,"sub");lay.extend(options.dateTime,{year:YM[0],month:YM[1]})}that.calendar(dateTimeTemp,panelIndex,isChange?"init":null)}else{that.calendar(null,index,isChange?"init":null)}that.endState&&that.done(null,"change")}else{if(options.position==="static"){that.calendar().done().done(null,"change")}else{if(options.type==="date"){options.autoConfirm?that.setValue(that.parse()).done().remove():that.calendar().done(null,"change")}else{if(options.type==="datetime"){that.calendar().done(null,"change")}}}}};Class.prototype.tool=function(btn,type){var that=this,options=that.config,lang=that.lang(),dateTime=options.dateTime,isStatic=options.position==="static",active={datetime:function(){if(lay(btn).hasClass(DISABLED)){return}that.list("time",0);options.range&&that.list("time",1);lay(btn).attr("lay-type","date").html(that.lang().dateTips)},date:function(){that.closeList();lay(btn).attr("lay-type","datetime").html(that.lang().timeTips)},clear:function(){isStatic&&(lay.extend(dateTime,that.firstDate),that.calendar());options.range&&(delete options.dateTime,delete that.endDate,delete that.startTime,delete that.endTime);that.setValue("");that.done(null,"onClear").done(["",{},{}]).remove()},now:function(){var thisDate=new Date();if(lay(btn).hasClass(DISABLED)){return that.hint(lang.tools.now+", "+lang.invalidDate)}lay.extend(dateTime,that.systemDate(),{hours:thisDate.getHours(),minutes:thisDate.getMinutes(),seconds:thisDate.getSeconds()});that.setValue(that.parse());isStatic&&that.calendar();that.done(null,"onNow").done().remove()},confirm:function(){if(options.range){if(lay(btn).hasClass(DISABLED)){return that.hint(options.type==="time"?lang.timeout.replace(/日期/g,"时间"):lang.timeout)}}else{if(lay(btn).hasClass(DISABLED)){return that.hint(lang.invalidDate)}}that.setValue(that.parse());that.done(null,"onConfirm").done().remove()}};active[type]&&active[type]()};Class.prototype.change=function(index){var that=this,options=that.config,dateTime=that.thisDateTime(index),isAlone=options.range&&(options.type==="year"||options.type==="month"),elemCont=that.elemCont[index||0],listYM=that.listYM[index],addSubYear=function(type){var isYear=lay(elemCont).find(".laydate-year-list")[0],isMonth=lay(elemCont).find(".laydate-month-list")[0];if(isYear){listYM[0]=type?listYM[0]-15:listYM[0]+15;that.list("year",index)}if(isMonth){type?listYM[0]--:listYM[0]++;that.list("month",index)}if(isYear||isMonth){lay.extend(dateTime,{year:listYM[0]});if(isAlone){dateTime.year=listYM[0]}options.range||that.done(null,"change");options.range||that.limit({elem:lay(that.footer).find(ELEM_CONFIRM),date:{year:listYM[0]}})}that.setBtnStatus();return isYear||isMonth};return{prevYear:function(){if(addSubYear("sub")){return}if(that.rangeLinked){options.dateTime.year--;that.checkDate("limit").calendar(null,null,"init")}else{dateTime.year--;that.checkDate("limit").calendar(null,index);that.autoCalendarModel.auto?that.choose(lay(elemCont).find("td.layui-this"),index):that.done(null,"change")}},prevMonth:function(){if(that.rangeLinked){dateTime=options.dateTime}var YM=that.getAsYM(dateTime.year,dateTime.month,"sub");lay.extend(dateTime,{year:YM[0],month:YM[1]});that.checkDate("limit").calendar(null,null,"init");if(!that.rangeLinked){that.autoCalendarModel.auto?that.choose(lay(elemCont).find("td.layui-this"),index):that.done(null,"change")}},nextMonth:function(){if(that.rangeLinked){dateTime=options.dateTime}var YM=that.getAsYM(dateTime.year,dateTime.month);lay.extend(dateTime,{year:YM[0],month:YM[1]});that.checkDate("limit").calendar(null,null,"init");if(!that.rangeLinked){that.autoCalendarModel.auto?that.choose(lay(elemCont).find("td.layui-this"),index):that.done(null,"change")}},nextYear:function(){if(addSubYear()){return}if(that.rangeLinked){options.dateTime.year++;that.checkDate("limit").calendar(null,0,"init")}else{dateTime.year++;that.checkDate("limit").calendar(null,index);that.autoCalendarModel.auto?that.choose(lay(elemCont).find("td.layui-this"),index):that.done(null,"change")}}}};Class.prototype.changeEvent=function(){var that=this,options=that.config;lay(that.elem).on("click",function(e){lay.stope(e)}).on("mousedown",function(e){lay.stope(e)});lay.each(that.elemHeader,function(i,header){lay(header[0]).on("click",function(e){that.change(i).prevYear()});lay(header[1]).on("click",function(e){that.change(i).prevMonth()});lay(header[2]).find("span").on("click",function(e){var othis=lay(this),layYM=othis.attr("lay-ym"),layType=othis.attr("lay-type");if(!layYM){return}layYM=layYM.split("-");that.listYM[i]=[layYM[0]|0,layYM[1]|0];that.list(layType,i);lay(that.footer).find("."+ELEM_TIME_BTN).addClass(DISABLED)});lay(header[3]).on("click",function(e){that.change(i).nextMonth()});lay(header[4]).on("click",function(e){that.change(i).nextYear()})});lay.each(that.table,function(i,table){var tds=lay(table).find("td");tds.on("click",function(){that.choose(lay(this),i)})});lay(that.footer).find("span").on("click",function(){var type=lay(this).attr("lay-type");that.tool(this,type)})};Class.prototype.isInput=function(elem){return/input|textarea/.test(elem.tagName.toLocaleLowerCase())||/INPUT|TEXTAREA/.test(elem.tagName)};Class.prototype.events=function(){var that=this;var options=that.config;if(!options.elem[0]||options.elem[0].eventHandler){return}var showEvent=function(){if(laydate.thisId===options.id){return}that.render()};options.elem.on(options.trigger,showEvent);options.elem[0].eventHandler=true;options.eventElem.on(options.trigger,showEvent);that.unbind=function(){that.remove();options.elem.off(options.trigger,showEvent);options.elem.removeAttr("lay-key");options.elem.removeAttr(MOD_ID);options.elem[0].eventHandler=false;options.eventElem.off(options.trigger,showEvent);options.eventElem.removeAttr("lay-key");delete thisModule.that[options.id]}};thisModule.that={};thisModule.getThis=function(id){var that=thisModule.that[id];if(!that&&isLayui){layui.hint().error(id?(MOD_NAME+" instance with ID '"+id+"' not found"):"ID argument required")}return that};ready.run=function(lay){lay(document).on("mousedown",function(e){if(!laydate.thisId){return}var that=thisModule.getThis(laydate.thisId);if(!that){return}var options=that.config;if(e.target===options.elem[0]||e.target===options.eventElem[0]||e.target===lay(options.closeStop)[0]||(options.elem[0]&&options.elem[0].contains(e.target))){return}that.remove()}).on("keydown",function(e){if(!laydate.thisId){return}var that=thisModule.getThis(laydate.thisId);if(!that){return}if(that.config.position==="static"){return}if(e.keyCode===13){if(lay("#"+that.elemID)[0]&&that.elemID===Class.thisElemDate){e.preventDefault();lay(that.footer).find(ELEM_CONFIRM)[0].click()}}});lay(window).on("resize",function(){if(!laydate.thisId){return}var that=thisModule.getThis(laydate.thisId);if(!that){return}if(!that.elem||!lay(ELEM)[0]){return false}that.position()})};laydate.render=function(options){var inst=new Class(options);return thisModule.call(inst)};laydate.reload=function(id,options){var that=thisModule.getThis(id);if(!that){return}return that.reload(options)};laydate.getInst=function(id){var that=thisModule.getThis(id);if(that){return that.inst}};laydate.hint=function(id,opts){var that=thisModule.getThis(id);if(!that){return}return that.hint(opts)};laydate.unbind=function(id){var that=thisModule.getThis(id);if(!that){return}return that.unbind()};laydate.close=function(id){var that=thisModule.getThis(id||laydate.thisId);if(!that){return}return that.remove()};laydate.parse=function(dateTime,format,one){dateTime=dateTime||{};if(typeof format==="string"){format=thisModule.formatArr(format)}format=(format||[]).concat();lay.each(format,function(i,item){if(/yyyy|y/.test(item)){format[i]=lay.digit(dateTime.year,item.length)}else{if(/MM|M/.test(item)){format[i]=lay.digit(dateTime.month+(one||0),item.length)}else{if(/dd|d/.test(item)){format[i]=lay.digit(dateTime.date,item.length)}else{if(/HH|H/.test(item)){format[i]=lay.digit(dateTime.hours,item.length)}else{if(/mm|m/.test(item)){format[i]=lay.digit(dateTime.minutes,item.length)}else{if(/ss|s/.test(item)){format[i]=lay.digit(dateTime.seconds,item.length)}}}}}}});return format.join("")};laydate.getEndDate=function(month,year){var thisDate=new Date();thisDate.setFullYear(year||thisDate.getFullYear(),month||(thisDate.getMonth()+1),1);return new Date(thisDate.getTime()-1000*60*60*24).getDate()};isLayui?(laydate.ready(),layui.define("lay",function(exports){laydate.path=layui.cache.dir;ready.run(lay);exports(MOD_NAME,laydate)})):((typeof define==="function"&&define.amd)?define(function(){ready.run(lay);return laydate}):function(){laydate.ready();ready.run(window.lay);window.laydate=laydate}())}(window,window.document); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.css ================================================ .select2-container { box-sizing: border-box; display: inline-block; margin: 0; position: relative; vertical-align: middle; } .select2-container .select2-selection--single { box-sizing: border-box; cursor: pointer; display: block; height: 28px; user-select: none; -webkit-user-select: none; } .select2-container .select2-selection--single .select2-selection__rendered { display: block; padding-left: 8px; padding-right: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .select2-container .select2-selection--single .select2-selection__clear { position: relative; } .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered { padding-right: 8px; padding-left: 20px; } .select2-container .select2-selection--multiple { box-sizing: border-box; cursor: pointer; display: block; min-height: 32px; user-select: none; -webkit-user-select: none; } .select2-container .select2-selection--multiple .select2-selection__rendered { display: inline-block; overflow: hidden; padding-left: 8px; text-overflow: ellipsis; white-space: nowrap; } .select2-container .select2-search--inline { float: left; } .select2-container .select2-search--inline .select2-search__field { box-sizing: border-box; border: none; font-size: 100%; margin-top: 5px; padding: 0; } .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button { -webkit-appearance: none; } .select2-dropdown { background-color: white; border: 1px solid #aaa; border-radius: 4px; box-sizing: border-box; display: block; position: absolute; left: -100000px; width: 100%; z-index: 1051; } .select2-results { display: block; } .select2-results__options { list-style: none; margin: 0; padding: 0; } .select2-results__option { padding: 6px; user-select: none; -webkit-user-select: none; } .select2-results__option[aria-selected] { cursor: pointer; } .select2-container--open .select2-dropdown { left: 0; } .select2-container--open .select2-dropdown--above { border-bottom: none; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .select2-container--open .select2-dropdown--below { border-top: none; border-top-left-radius: 0; border-top-right-radius: 0; } .select2-search--dropdown { display: block; padding: 4px; } .select2-search--dropdown .select2-search__field { padding: 4px; width: 100%; box-sizing: border-box; } .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button { -webkit-appearance: none; } .select2-search--dropdown.select2-search--hide { display: none; } .select2-close-mask { border: 0; margin: 0; padding: 0; display: block; position: fixed; left: 0; top: 0; min-height: 100%; min-width: 100%; height: auto; width: auto; opacity: 0; z-index: 99; background-color: #fff; filter: alpha(opacity=0); } .select2-hidden-accessible { border: 0 !important; clip: rect(0 0 0 0) !important; -webkit-clip-path: inset(50%) !important; clip-path: inset(50%) !important; height: 1px !important; overflow: hidden !important; padding: 0 !important; position: absolute !important; width: 1px !important; white-space: nowrap !important; } .select2-container--default .select2-selection--single { background-color: #fff; border: 1px solid #aaa; border-radius: 4px; } .select2-container--default .select2-selection--single .select2-selection__rendered { color: #444; line-height: 28px; } .select2-container--default .select2-selection--single .select2-selection__clear { cursor: pointer; float: right; font-weight: bold; } .select2-container--default .select2-selection--single .select2-selection__placeholder { color: #999; } .select2-container--default .select2-selection--single .select2-selection__arrow { height: 26px; position: absolute; top: 1px; right: 1px; width: 20px; } .select2-container--default .select2-selection--single .select2-selection__arrow b { border-color: #888 transparent transparent transparent; border-style: solid; border-width: 5px 4px 0 4px; height: 0; left: 50%; margin-left: -4px; margin-top: -2px; position: absolute; top: 50%; width: 0; } .select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear { float: left; } .select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow { left: 1px; right: auto; } .select2-container--default.select2-container--disabled .select2-selection--single { background-color: #eee; cursor: default; } .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear { display: none; } .select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b { border-color: transparent transparent #888 transparent; border-width: 0 4px 5px 4px; } .select2-container--default .select2-selection--multiple { background-color: white; border: 1px solid #aaa; border-radius: 4px; cursor: text; } .select2-container--default .select2-selection--multiple .select2-selection__rendered { box-sizing: border-box; list-style: none; margin: 0; padding: 0 5px; width: 100%; } .select2-container--default .select2-selection--multiple .select2-selection__rendered li { list-style: none; } .select2-container--default .select2-selection--multiple .select2-selection__clear { cursor: pointer; float: right; font-weight: bold; margin-top: 5px; margin-right: 10px; padding: 1px; } .select2-container--default .select2-selection--multiple .select2-selection__choice { background-color: #e4e4e4; border: 1px solid #aaa; border-radius: 4px; cursor: default; float: left; margin-right: 5px; margin-top: 5px; padding: 0 5px; } .select2-container--default .select2-selection--multiple .select2-selection__choice__remove { color: #999; cursor: pointer; display: inline-block; font-weight: bold; margin-right: 2px; } .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover { color: #333; } .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline { float: right; } .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice { margin-left: 5px; margin-right: auto; } .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { margin-left: 2px; margin-right: auto; } .select2-container--default.select2-container--focus .select2-selection--multiple { border: solid black 1px; outline: 0; } .select2-container--default.select2-container--disabled .select2-selection--multiple { background-color: #eee; cursor: default; } .select2-container--default.select2-container--disabled .select2-selection__choice__remove { display: none; } .select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple { border-top-left-radius: 0; border-top-right-radius: 0; } .select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .select2-container--default .select2-search--dropdown .select2-search__field { border: 1px solid #aaa; } .select2-container--default .select2-search--inline .select2-search__field { background: transparent; border: none; outline: 0; box-shadow: none; -webkit-appearance: textfield; } .select2-container--default .select2-results > .select2-results__options { max-height: 200px; overflow-y: auto; } .select2-container--default .select2-results__option[role=group] { padding: 0; } .select2-container--default .select2-results__option[aria-disabled=true] { color: #999; } .select2-container--default .select2-results__option[aria-selected=true] { background-color: #ddd; } .select2-container--default .select2-results__option .select2-results__option { padding-left: 1em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__group { padding-left: 0; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option { margin-left: -1em; padding-left: 2em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option { margin-left: -2em; padding-left: 3em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { margin-left: -3em; padding-left: 4em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { margin-left: -4em; padding-left: 5em; } .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { margin-left: -5em; padding-left: 6em; } .select2-container--default .select2-results__option--highlighted[aria-selected] { background-color: #5897fb; color: white; } .select2-container--default .select2-results__group { cursor: default; display: block; padding: 6px; } .select2-container--classic .select2-selection--single { background-color: #f7f7f7; border: 1px solid #aaa; border-radius: 4px; outline: 0; background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%); background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%); background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } .select2-container--classic .select2-selection--single:focus { border: 1px solid #5897fb; } .select2-container--classic .select2-selection--single .select2-selection__rendered { color: #444; line-height: 28px; } .select2-container--classic .select2-selection--single .select2-selection__clear { cursor: pointer; float: right; font-weight: bold; margin-right: 10px; } .select2-container--classic .select2-selection--single .select2-selection__placeholder { color: #999; } .select2-container--classic .select2-selection--single .select2-selection__arrow { background-color: #ddd; border: none; border-left: 1px solid #aaa; border-top-right-radius: 4px; border-bottom-right-radius: 4px; height: 26px; position: absolute; top: 1px; right: 1px; width: 20px; background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%); background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%); background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); } .select2-container--classic .select2-selection--single .select2-selection__arrow b { border-color: #888 transparent transparent transparent; border-style: solid; border-width: 5px 4px 0 4px; height: 0; left: 50%; margin-left: -4px; margin-top: -2px; position: absolute; top: 50%; width: 0; } .select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear { float: left; } .select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow { border: none; border-right: 1px solid #aaa; border-radius: 0; border-top-left-radius: 4px; border-bottom-left-radius: 4px; left: 1px; right: auto; } .select2-container--classic.select2-container--open .select2-selection--single { border: 1px solid #5897fb; } .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow { background: transparent; border: none; } .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b { border-color: transparent transparent #888 transparent; border-width: 0 4px 5px 4px; } .select2-container--classic.select2-container--open.select2-container--above .select2-selection--single { border-top: none; border-top-left-radius: 0; border-top-right-radius: 0; background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%); background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%); background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); } .select2-container--classic.select2-container--open.select2-container--below .select2-selection--single { border-bottom: none; border-bottom-left-radius: 0; border-bottom-right-radius: 0; background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%); background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%); background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); } .select2-container--classic .select2-selection--multiple { background-color: white; border: 1px solid #aaa; border-radius: 4px; cursor: text; outline: 0; } .select2-container--classic .select2-selection--multiple:focus { border: 1px solid #5897fb; } .select2-container--classic .select2-selection--multiple .select2-selection__rendered { list-style: none; margin: 0; padding: 0 5px; } .select2-container--classic .select2-selection--multiple .select2-selection__clear { display: none; } .select2-container--classic .select2-selection--multiple .select2-selection__choice { background-color: #e4e4e4; border: 1px solid #aaa; border-radius: 4px; cursor: default; float: left; margin-right: 5px; margin-top: 5px; padding: 0 5px; } .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove { color: #888; cursor: pointer; display: inline-block; font-weight: bold; margin-right: 2px; } .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover { color: #555; } .select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { float: right; margin-left: 5px; margin-right: auto; } .select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { margin-left: 2px; margin-right: auto; } .select2-container--classic.select2-container--open .select2-selection--multiple { border: 1px solid #5897fb; } .select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple { border-top: none; border-top-left-radius: 0; border-top-right-radius: 0; } .select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple { border-bottom: none; border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .select2-container--classic .select2-search--dropdown .select2-search__field { border: 1px solid #aaa; outline: 0; } .select2-container--classic .select2-search--inline .select2-search__field { outline: 0; box-shadow: none; } .select2-container--classic .select2-dropdown { background-color: white; border: 1px solid transparent; } .select2-container--classic .select2-dropdown--above { border-bottom: none; } .select2-container--classic .select2-dropdown--below { border-top: none; } .select2-container--classic .select2-results > .select2-results__options { max-height: 200px; overflow-y: auto; } .select2-container--classic .select2-results__option[role=group] { padding: 0; } .select2-container--classic .select2-results__option[aria-disabled=true] { color: grey; } .select2-container--classic .select2-results__option--highlighted[aria-selected] { background-color: #3875d7; color: white; } .select2-container--classic .select2-results__group { cursor: default; display: block; padding: 6px; } .select2-container--classic.select2-container--open .select2-dropdown { border-color: #5897fb; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/select2/select2.js ================================================ /*! * Select2 4.0.13 * https://select2.github.io * * Released under the MIT license * https://github.com/select2/select2/blob/master/LICENSE.md */ ;(function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); } else if (typeof module === 'object' && module.exports) { // Node/CommonJS module.exports = function (root, jQuery) { if (jQuery === undefined) { // require('jQuery') returns a factory that requires window to // build a jQuery instance, we normalize how we use modules // that require this pattern but the window provided is a noop // if it's defined (how jquery works) if (typeof window !== 'undefined') { jQuery = require('jquery'); } else { jQuery = require('jquery')(root); } } factory(jQuery); return jQuery; }; } else { // Browser globals factory(jQuery); } } (function (jQuery) { // This is needed so we can catch the AMD loader configuration and use it // The inner file should be wrapped (by `banner.start.js`) in a function that // returns the AMD loader references. var S2 =(function () { // Restore the Select2 AMD loader so it can be used // Needed mostly in the language files, where the loader is not inserted if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { var S2 = jQuery.fn.select2.amd; } var S2;(function () { if (!S2 || !S2.requirejs) { if (!S2) { S2 = {}; } else { require = S2; } /** * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. * Released under MIT license, http://github.com/requirejs/almond/LICENSE */ //Going sloppy to avoid 'use strict' string cost, but strict practices should //be followed. /*global setTimeout: false */ var requirejs, require, define; (function (undef) { var main, req, makeMap, handlers, defined = {}, waiting = {}, config = {}, defining = {}, hasOwn = Object.prototype.hasOwnProperty, aps = [].slice, jsSuffixRegExp = /\.js$/; function hasProp(obj, prop) { return hasOwn.call(obj, prop); } /** * Given a relative module name, like ./something, normalize it to * a real name that can be mapped to a path. * @param {String} name the relative name * @param {String} baseName a real name that the name arg is relative * to. * @returns {String} normalized name */ function normalize(name, baseName) { var nameParts, nameSegment, mapValue, foundMap, lastIndex, foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, baseParts = baseName && baseName.split("/"), map = config.map, starMap = (map && map['*']) || {}; //Adjust any relative paths. if (name) { name = name.split('/'); lastIndex = name.length - 1; // If wanting node ID compatibility, strip .js from end // of IDs. Have to do this here, and not in nameToUrl // because node allows either .js or non .js to map // to same file. if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); } // Starts with a '.' so need the baseName if (name[0].charAt(0) === '.' && baseParts) { //Convert baseName to array, and lop off the last part, //so that . matches that 'directory' and not name of the baseName's //module. For instance, baseName of 'one/two/three', maps to //'one/two/three.js', but we want the directory, 'one/two' for //this normalization. normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); name = normalizedBaseParts.concat(name); } //start trimDots for (i = 0; i < name.length; i++) { part = name[i]; if (part === '.') { name.splice(i, 1); i -= 1; } else if (part === '..') { // If at the start, or previous value is still .., // keep them so that when converted to a path it may // still work when converted to a path, even though // as an ID it is less than ideal. In larger point // releases, may be better to just kick out an error. if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') { continue; } else if (i > 0) { name.splice(i - 1, 2); i -= 2; } } } //end trimDots name = name.join('/'); } //Apply map config if available. if ((baseParts || starMap) && map) { nameParts = name.split('/'); for (i = nameParts.length; i > 0; i -= 1) { nameSegment = nameParts.slice(0, i).join("/"); if (baseParts) { //Find the longest baseName segment match in the config. //So, do joins on the biggest to smallest lengths of baseParts. for (j = baseParts.length; j > 0; j -= 1) { mapValue = map[baseParts.slice(0, j).join('/')]; //baseName segment has config, find if it has one for //this name. if (mapValue) { mapValue = mapValue[nameSegment]; if (mapValue) { //Match, update name to the new value. foundMap = mapValue; foundI = i; break; } } } } if (foundMap) { break; } //Check for a star map match, but just hold on to it, //if there is a shorter segment match later in a matching //config, then favor over this star map. if (!foundStarMap && starMap && starMap[nameSegment]) { foundStarMap = starMap[nameSegment]; starI = i; } } if (!foundMap && foundStarMap) { foundMap = foundStarMap; foundI = starI; } if (foundMap) { nameParts.splice(0, foundI, foundMap); name = nameParts.join('/'); } } return name; } function makeRequire(relName, forceSync) { return function () { //A version of a require function that passes a moduleName //value for items that may need to //look up paths relative to the moduleName var args = aps.call(arguments, 0); //If first arg is not require('string'), and there is only //one arg, it is the array form without a callback. Insert //a null so that the following concat is correct. if (typeof args[0] !== 'string' && args.length === 1) { args.push(null); } return req.apply(undef, args.concat([relName, forceSync])); }; } function makeNormalize(relName) { return function (name) { return normalize(name, relName); }; } function makeLoad(depName) { return function (value) { defined[depName] = value; }; } function callDep(name) { if (hasProp(waiting, name)) { var args = waiting[name]; delete waiting[name]; defining[name] = true; main.apply(undef, args); } if (!hasProp(defined, name) && !hasProp(defining, name)) { throw new Error('No ' + name); } return defined[name]; } //Turns a plugin!resource to [plugin, resource] //with the plugin being undefined if the name //did not have a plugin prefix. function splitPrefix(name) { var prefix, index = name ? name.indexOf('!') : -1; if (index > -1) { prefix = name.substring(0, index); name = name.substring(index + 1, name.length); } return [prefix, name]; } //Creates a parts array for a relName where first part is plugin ID, //second part is resource ID. Assumes relName has already been normalized. function makeRelParts(relName) { return relName ? splitPrefix(relName) : []; } /** * Makes a name map, normalizing the name, and using a plugin * for normalization if necessary. Grabs a ref to plugin * too, as an optimization. */ makeMap = function (name, relParts) { var plugin, parts = splitPrefix(name), prefix = parts[0], relResourceName = relParts[1]; name = parts[1]; if (prefix) { prefix = normalize(prefix, relResourceName); plugin = callDep(prefix); } //Normalize according if (prefix) { if (plugin && plugin.normalize) { name = plugin.normalize(name, makeNormalize(relResourceName)); } else { name = normalize(name, relResourceName); } } else { name = normalize(name, relResourceName); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; if (prefix) { plugin = callDep(prefix); } } //Using ridiculous property names for space reasons return { f: prefix ? prefix + '!' + name : name, //fullName n: name, pr: prefix, p: plugin }; }; function makeConfig(name) { return function () { return (config && config.config && config.config[name]) || {}; }; } handlers = { require: function (name) { return makeRequire(name); }, exports: function (name) { var e = defined[name]; if (typeof e !== 'undefined') { return e; } else { return (defined[name] = {}); } }, module: function (name) { return { id: name, uri: '', exports: defined[name], config: makeConfig(name) }; } }; main = function (name, deps, callback, relName) { var cjsModule, depName, ret, map, i, relParts, args = [], callbackType = typeof callback, usingExports; //Use name if no relName relName = relName || name; relParts = makeRelParts(relName); //Call the callback to define the module, if necessary. if (callbackType === 'undefined' || callbackType === 'function') { //Pull out the defined dependencies and pass the ordered //values to the callback. //Default to [require, exports, module] if no deps deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; for (i = 0; i < deps.length; i += 1) { map = makeMap(deps[i], relParts); depName = map.f; //Fast path CommonJS standard dependencies. if (depName === "require") { args[i] = handlers.require(name); } else if (depName === "exports") { //CommonJS module spec 1.1 args[i] = handlers.exports(name); usingExports = true; } else if (depName === "module") { //CommonJS module spec 1.1 cjsModule = args[i] = handlers.module(name); } else if (hasProp(defined, depName) || hasProp(waiting, depName) || hasProp(defining, depName)) { args[i] = callDep(depName); } else if (map.p) { map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); args[i] = defined[depName]; } else { throw new Error(name + ' missing ' + depName); } } ret = callback ? callback.apply(defined[name], args) : undefined; if (name) { //If setting exports via "module" is in play, //favor that over return value and exports. After that, //favor a non-undefined return value over exports use. if (cjsModule && cjsModule.exports !== undef && cjsModule.exports !== defined[name]) { defined[name] = cjsModule.exports; } else if (ret !== undef || !usingExports) { //Use the return value from the function. defined[name] = ret; } } } else if (name) { //May just be an object definition for the module. Only //worry about defining if have a module name. defined[name] = callback; } }; requirejs = require = req = function (deps, callback, relName, forceSync, alt) { if (typeof deps === "string") { if (handlers[deps]) { //callback in this case is really relName return handlers[deps](callback); } //Just return the module wanted. In this scenario, the //deps arg is the module name, and second arg (if passed) //is just the relName. //Normalize module name, if it contains . or .. return callDep(makeMap(deps, makeRelParts(callback)).f); } else if (!deps.splice) { //deps is a config object, not an array. config = deps; if (config.deps) { req(config.deps, config.callback); } if (!callback) { return; } if (callback.splice) { //callback is an array, which means it is a dependency list. //Adjust args if there are dependencies deps = callback; callback = relName; relName = null; } else { deps = undef; } } //Support require(['a']) callback = callback || function () {}; //If relName is a function, it is an errback handler, //so remove it. if (typeof relName === 'function') { relName = forceSync; forceSync = alt; } //Simulate async callback; if (forceSync) { main(undef, deps, callback, relName); } else { //Using a non-zero value because of concern for what old browsers //do, and latest browsers "upgrade" to 4 if lower value is used: //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: //If want a value immediately, use require('id') instead -- something //that works in almond on the global level, but not guaranteed and //unlikely to work in other AMD implementations. setTimeout(function () { main(undef, deps, callback, relName); }, 4); } return req; }; /** * Just drops the config on the floor, but returns req in case * the config return value is used. */ req.config = function (cfg) { return req(cfg); }; /** * Expose module registry for debugging and tooling */ requirejs._defined = defined; define = function (name, deps, callback) { if (typeof name !== 'string') { throw new Error('See almond README: incorrect module build, no module name'); } //This module may not have dependencies if (!deps.splice) { //deps is not an array, so probably means //an object literal or factory function for //the value. Adjust args. callback = deps; deps = []; } if (!hasProp(defined, name) && !hasProp(waiting, name)) { waiting[name] = [name, deps, callback]; } }; define.amd = { jQuery: true }; }()); S2.requirejs = requirejs;S2.require = require;S2.define = define; } }()); S2.define("almond", function(){}); /* global jQuery:false, $:false */ S2.define('jquery',[],function () { var _$ = jQuery || $; if (_$ == null && console && console.error) { console.error( 'Select2: An instance of jQuery or a jQuery-compatible library was not ' + 'found. Make sure that you are including jQuery before Select2 on your ' + 'web page.' ); } return _$; }); S2.define('select2/utils',[ 'jquery' ], function ($) { var Utils = {}; Utils.Extend = function (ChildClass, SuperClass) { var __hasProp = {}.hasOwnProperty; function BaseConstructor () { this.constructor = ChildClass; } for (var key in SuperClass) { if (__hasProp.call(SuperClass, key)) { ChildClass[key] = SuperClass[key]; } } BaseConstructor.prototype = SuperClass.prototype; ChildClass.prototype = new BaseConstructor(); ChildClass.__super__ = SuperClass.prototype; return ChildClass; }; function getMethods (theClass) { var proto = theClass.prototype; var methods = []; for (var methodName in proto) { var m = proto[methodName]; if (typeof m !== 'function') { continue; } if (methodName === 'constructor') { continue; } methods.push(methodName); } return methods; } Utils.Decorate = function (SuperClass, DecoratorClass) { var decoratedMethods = getMethods(DecoratorClass); var superMethods = getMethods(SuperClass); function DecoratedClass () { var unshift = Array.prototype.unshift; var argCount = DecoratorClass.prototype.constructor.length; var calledConstructor = SuperClass.prototype.constructor; if (argCount > 0) { unshift.call(arguments, SuperClass.prototype.constructor); calledConstructor = DecoratorClass.prototype.constructor; } calledConstructor.apply(this, arguments); } DecoratorClass.displayName = SuperClass.displayName; function ctr () { this.constructor = DecoratedClass; } DecoratedClass.prototype = new ctr(); for (var m = 0; m < superMethods.length; m++) { var superMethod = superMethods[m]; DecoratedClass.prototype[superMethod] = SuperClass.prototype[superMethod]; } var calledMethod = function (methodName) { // Stub out the original method if it's not decorating an actual method var originalMethod = function () {}; if (methodName in DecoratedClass.prototype) { originalMethod = DecoratedClass.prototype[methodName]; } var decoratedMethod = DecoratorClass.prototype[methodName]; return function () { var unshift = Array.prototype.unshift; unshift.call(arguments, originalMethod); return decoratedMethod.apply(this, arguments); }; }; for (var d = 0; d < decoratedMethods.length; d++) { var decoratedMethod = decoratedMethods[d]; DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod); } return DecoratedClass; }; var Observable = function () { this.listeners = {}; }; Observable.prototype.on = function (event, callback) { this.listeners = this.listeners || {}; if (event in this.listeners) { this.listeners[event].push(callback); } else { this.listeners[event] = [callback]; } }; Observable.prototype.trigger = function (event) { var slice = Array.prototype.slice; var params = slice.call(arguments, 1); this.listeners = this.listeners || {}; // Params should always come in as an array if (params == null) { params = []; } // If there are no arguments to the event, use a temporary object if (params.length === 0) { params.push({}); } // Set the `_type` of the first object to the event params[0]._type = event; if (event in this.listeners) { this.invoke(this.listeners[event], slice.call(arguments, 1)); } if ('*' in this.listeners) { this.invoke(this.listeners['*'], arguments); } }; Observable.prototype.invoke = function (listeners, params) { for (var i = 0, len = listeners.length; i < len; i++) { listeners[i].apply(this, params); } }; Utils.Observable = Observable; Utils.generateChars = function (length) { var chars = ''; for (var i = 0; i < length; i++) { var randomChar = Math.floor(Math.random() * 36); chars += randomChar.toString(36); } return chars; }; Utils.bind = function (func, context) { return function () { func.apply(context, arguments); }; }; Utils._convertData = function (data) { for (var originalKey in data) { var keys = originalKey.split('-'); var dataLevel = data; if (keys.length === 1) { continue; } for (var k = 0; k < keys.length; k++) { var key = keys[k]; // Lowercase the first letter // By default, dash-separated becomes camelCase key = key.substring(0, 1).toLowerCase() + key.substring(1); if (!(key in dataLevel)) { dataLevel[key] = {}; } if (k == keys.length - 1) { dataLevel[key] = data[originalKey]; } dataLevel = dataLevel[key]; } delete data[originalKey]; } return data; }; Utils.hasScroll = function (index, el) { // Adapted from the function created by @ShadowScripter // and adapted by @BillBarry on the Stack Exchange Code Review website. // The original code can be found at // http://codereview.stackexchange.com/q/13338 // and was designed to be used with the Sizzle selector engine. var $el = $(el); var overflowX = el.style.overflowX; var overflowY = el.style.overflowY; //Check both x and y declarations if (overflowX === overflowY && (overflowY === 'hidden' || overflowY === 'visible')) { return false; } if (overflowX === 'scroll' || overflowY === 'scroll') { return true; } return ($el.innerHeight() < el.scrollHeight || $el.innerWidth() < el.scrollWidth); }; Utils.escapeMarkup = function (markup) { var replaceMap = { '\\': '\', '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''', '/': '/' }; // Do not try to escape the markup if it's not a string if (typeof markup !== 'string') { return markup; } return String(markup).replace(/[&<>"'\/\\]/g, function (match) { return replaceMap[match]; }); }; // Append an array of jQuery nodes to a given element. Utils.appendMany = function ($element, $nodes) { // jQuery 1.7.x does not support $.fn.append() with an array // Fall back to a jQuery object collection using $.fn.add() if ($.fn.jquery.substr(0, 3) === '1.7') { var $jqNodes = $(); $.map($nodes, function (node) { $jqNodes = $jqNodes.add(node); }); $nodes = $jqNodes; } $element.append($nodes); }; // Cache objects in Utils.__cache instead of $.data (see #4346) Utils.__cache = {}; var id = 0; Utils.GetUniqueElementId = function (element) { // Get a unique element Id. If element has no id, // creates a new unique number, stores it in the id // attribute and returns the new id. // If an id already exists, it simply returns it. var select2Id = element.getAttribute('data-select2-id'); if (select2Id == null) { // If element has id, use it. if (element.id) { select2Id = element.id; element.setAttribute('data-select2-id', select2Id); } else { element.setAttribute('data-select2-id', ++id); select2Id = id.toString(); } } return select2Id; }; Utils.StoreData = function (element, name, value) { // Stores an item in the cache for a specified element. // name is the cache key. var id = Utils.GetUniqueElementId(element); if (!Utils.__cache[id]) { Utils.__cache[id] = {}; } Utils.__cache[id][name] = value; }; Utils.GetData = function (element, name) { // Retrieves a value from the cache by its key (name) // name is optional. If no name specified, return // all cache items for the specified element. // and for a specified element. var id = Utils.GetUniqueElementId(element); if (name) { if (Utils.__cache[id]) { if (Utils.__cache[id][name] != null) { return Utils.__cache[id][name]; } return $(element).data(name); // Fallback to HTML5 data attribs. } return $(element).data(name); // Fallback to HTML5 data attribs. } else { return Utils.__cache[id]; } }; Utils.RemoveData = function (element) { // Removes all cached items for a specified element. var id = Utils.GetUniqueElementId(element); if (Utils.__cache[id] != null) { delete Utils.__cache[id]; } element.removeAttribute('data-select2-id'); }; return Utils; }); S2.define('select2/results',[ 'jquery', './utils' ], function ($, Utils) { function Results ($element, options, dataAdapter) { this.$element = $element; this.data = dataAdapter; this.options = options; Results.__super__.constructor.call(this); } Utils.Extend(Results, Utils.Observable); Results.prototype.render = function () { var $results = $( '
            ' ); if (this.options.get('multiple')) { $results.attr('aria-multiselectable', 'true'); } this.$results = $results; return $results; }; Results.prototype.clear = function () { this.$results.empty(); }; Results.prototype.displayMessage = function (params) { var escapeMarkup = this.options.get('escapeMarkup'); this.clear(); this.hideLoading(); var $message = $( '' ); var message = this.options.get('translations').get(params.message); $message.append( escapeMarkup( message(params.args) ) ); $message[0].className += ' select2-results__message'; this.$results.append($message); }; Results.prototype.hideMessages = function () { this.$results.find('.select2-results__message').remove(); }; Results.prototype.append = function (data) { this.hideLoading(); var $options = []; if (data.results == null || data.results.length === 0) { if (this.$results.children().length === 0) { this.trigger('results:message', { message: 'noResults' }); } return; } data.results = this.sort(data.results); for (var d = 0; d < data.results.length; d++) { var item = data.results[d]; var $option = this.option(item); $options.push($option); } this.$results.append($options); }; Results.prototype.position = function ($results, $dropdown) { var $resultsContainer = $dropdown.find('.select2-results'); $resultsContainer.append($results); }; Results.prototype.sort = function (data) { var sorter = this.options.get('sorter'); return sorter(data); }; Results.prototype.highlightFirstItem = function () { var $options = this.$results .find('.select2-results__option[aria-selected]'); var $selected = $options.filter('[aria-selected=true]'); // Check if there are any selected options if ($selected.length > 0) { // If there are selected options, highlight the first $selected.first().trigger('mouseenter'); } else { // If there are no selected options, highlight the first option // in the dropdown $options.first().trigger('mouseenter'); } this.ensureHighlightVisible(); }; Results.prototype.setClasses = function () { var self = this; this.data.current(function (selected) { var selectedIds = $.map(selected, function (s) { return s.id.toString(); }); var $options = self.$results .find('.select2-results__option[aria-selected]'); $options.each(function () { var $option = $(this); var item = Utils.GetData(this, 'data'); // id needs to be converted to a string when comparing var id = '' + item.id; if ((item.element != null && item.element.selected) || (item.element == null && $.inArray(id, selectedIds) > -1)) { $option.attr('aria-selected', 'true'); } else { $option.attr('aria-selected', 'false'); } }); }); }; Results.prototype.showLoading = function (params) { this.hideLoading(); var loadingMore = this.options.get('translations').get('searching'); var loading = { disabled: true, loading: true, text: loadingMore(params) }; var $loading = this.option(loading); $loading.className += ' loading-results'; this.$results.prepend($loading); }; Results.prototype.hideLoading = function () { this.$results.find('.loading-results').remove(); }; Results.prototype.option = function (data) { var option = document.createElement('li'); option.className = 'select2-results__option'; var attrs = { 'role': 'option', 'aria-selected': 'false' }; var matches = window.Element.prototype.matches || window.Element.prototype.msMatchesSelector || window.Element.prototype.webkitMatchesSelector; if ((data.element != null && matches.call(data.element, ':disabled')) || (data.element == null && data.disabled)) { delete attrs['aria-selected']; attrs['aria-disabled'] = 'true'; } if (data.id == null) { delete attrs['aria-selected']; } if (data._resultId != null) { option.id = data._resultId; } if (data.title) { option.title = data.title; } if (data.children) { attrs.role = 'group'; attrs['aria-label'] = data.text; delete attrs['aria-selected']; } for (var attr in attrs) { var val = attrs[attr]; option.setAttribute(attr, val); } if (data.children) { var $option = $(option); var label = document.createElement('strong'); label.className = 'select2-results__group'; var $label = $(label); this.template(data, label); var $children = []; for (var c = 0; c < data.children.length; c++) { var child = data.children[c]; var $child = this.option(child); $children.push($child); } var $childrenContainer = $('
              ', { 'class': 'select2-results__options select2-results__options--nested' }); $childrenContainer.append($children); $option.append(label); $option.append($childrenContainer); } else { this.template(data, option); } Utils.StoreData(option, 'data', data); return option; }; Results.prototype.bind = function (container, $container) { var self = this; var id = container.id + '-results'; this.$results.attr('id', id); container.on('results:all', function (params) { self.clear(); self.append(params.data); if (container.isOpen()) { self.setClasses(); self.highlightFirstItem(); } }); container.on('results:append', function (params) { self.append(params.data); if (container.isOpen()) { self.setClasses(); } }); container.on('query', function (params) { self.hideMessages(); self.showLoading(params); }); container.on('select', function () { if (!container.isOpen()) { return; } self.setClasses(); if (self.options.get('scrollAfterSelect')) { self.highlightFirstItem(); } }); container.on('unselect', function () { if (!container.isOpen()) { return; } self.setClasses(); if (self.options.get('scrollAfterSelect')) { self.highlightFirstItem(); } }); container.on('open', function () { // When the dropdown is open, aria-expended="true" self.$results.attr('aria-expanded', 'true'); self.$results.attr('aria-hidden', 'false'); self.setClasses(); self.ensureHighlightVisible(); }); container.on('close', function () { // When the dropdown is closed, aria-expended="false" self.$results.attr('aria-expanded', 'false'); self.$results.attr('aria-hidden', 'true'); self.$results.removeAttr('aria-activedescendant'); }); container.on('results:toggle', function () { var $highlighted = self.getHighlightedResults(); if ($highlighted.length === 0) { return; } $highlighted.trigger('mouseup'); }); container.on('results:select', function () { var $highlighted = self.getHighlightedResults(); if ($highlighted.length === 0) { return; } var data = Utils.GetData($highlighted[0], 'data'); if ($highlighted.attr('aria-selected') == 'true') { self.trigger('close', {}); } else { self.trigger('select', { data: data }); } }); container.on('results:previous', function () { var $highlighted = self.getHighlightedResults(); var $options = self.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); // If we are already at the top, don't move further // If no options, currentIndex will be -1 if (currentIndex <= 0) { return; } var nextIndex = currentIndex - 1; // If none are highlighted, highlight the first if ($highlighted.length === 0) { nextIndex = 0; } var $next = $options.eq(nextIndex); $next.trigger('mouseenter'); var currentOffset = self.$results.offset().top; var nextTop = $next.offset().top; var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset); if (nextIndex === 0) { self.$results.scrollTop(0); } else if (nextTop - currentOffset < 0) { self.$results.scrollTop(nextOffset); } }); container.on('results:next', function () { var $highlighted = self.getHighlightedResults(); var $options = self.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); var nextIndex = currentIndex + 1; // If we are at the last option, stay there if (nextIndex >= $options.length) { return; } var $next = $options.eq(nextIndex); $next.trigger('mouseenter'); var currentOffset = self.$results.offset().top + self.$results.outerHeight(false); var nextBottom = $next.offset().top + $next.outerHeight(false); var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset; if (nextIndex === 0) { self.$results.scrollTop(0); } else if (nextBottom > currentOffset) { self.$results.scrollTop(nextOffset); } }); container.on('results:focus', function (params) { params.element.addClass('select2-results__option--highlighted'); }); container.on('results:message', function (params) { self.displayMessage(params); }); if ($.fn.mousewheel) { this.$results.on('mousewheel', function (e) { var top = self.$results.scrollTop(); var bottom = self.$results.get(0).scrollHeight - top + e.deltaY; var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0; var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height(); if (isAtTop) { self.$results.scrollTop(0); e.preventDefault(); e.stopPropagation(); } else if (isAtBottom) { self.$results.scrollTop( self.$results.get(0).scrollHeight - self.$results.height() ); e.preventDefault(); e.stopPropagation(); } }); } this.$results.on('mouseup', '.select2-results__option[aria-selected]', function (evt) { var $this = $(this); var data = Utils.GetData(this, 'data'); if ($this.attr('aria-selected') === 'true') { if (self.options.get('multiple')) { self.trigger('unselect', { originalEvent: evt, data: data }); } else { self.trigger('close', {}); } return; } self.trigger('select', { originalEvent: evt, data: data }); }); this.$results.on('mouseenter', '.select2-results__option[aria-selected]', function (evt) { var data = Utils.GetData(this, 'data'); self.getHighlightedResults() .removeClass('select2-results__option--highlighted'); self.trigger('results:focus', { data: data, element: $(this) }); }); }; Results.prototype.getHighlightedResults = function () { var $highlighted = this.$results .find('.select2-results__option--highlighted'); return $highlighted; }; Results.prototype.destroy = function () { this.$results.remove(); }; Results.prototype.ensureHighlightVisible = function () { var $highlighted = this.getHighlightedResults(); if ($highlighted.length === 0) { return; } var $options = this.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); var currentOffset = this.$results.offset().top; var nextTop = $highlighted.offset().top; var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset); var offsetDelta = nextTop - currentOffset; nextOffset -= $highlighted.outerHeight(false) * 2; if (currentIndex <= 2) { this.$results.scrollTop(0); } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) { this.$results.scrollTop(nextOffset); } }; Results.prototype.template = function (result, container) { var template = this.options.get('templateResult'); var escapeMarkup = this.options.get('escapeMarkup'); var content = template(result, container); if (content == null) { container.style.display = 'none'; } else if (typeof content === 'string') { container.innerHTML = escapeMarkup(content); } else { $(container).append(content); } }; return Results; }); S2.define('select2/keys',[ ], function () { var KEYS = { BACKSPACE: 8, TAB: 9, ENTER: 13, SHIFT: 16, CTRL: 17, ALT: 18, ESC: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, DELETE: 46 }; return KEYS; }); S2.define('select2/selection/base',[ 'jquery', '../utils', '../keys' ], function ($, Utils, KEYS) { function BaseSelection ($element, options) { this.$element = $element; this.options = options; BaseSelection.__super__.constructor.call(this); } Utils.Extend(BaseSelection, Utils.Observable); BaseSelection.prototype.render = function () { var $selection = $( '' ); this._tabindex = 0; if (Utils.GetData(this.$element[0], 'old-tabindex') != null) { this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex'); } else if (this.$element.attr('tabindex') != null) { this._tabindex = this.$element.attr('tabindex'); } $selection.attr('title', this.$element.attr('title')); $selection.attr('tabindex', this._tabindex); $selection.attr('aria-disabled', 'false'); this.$selection = $selection; return $selection; }; BaseSelection.prototype.bind = function (container, $container) { var self = this; var resultsId = container.id + '-results'; this.container = container; this.$selection.on('focus', function (evt) { self.trigger('focus', evt); }); this.$selection.on('blur', function (evt) { self._handleBlur(evt); }); this.$selection.on('keydown', function (evt) { self.trigger('keypress', evt); if (evt.which === KEYS.SPACE) { evt.preventDefault(); } }); container.on('results:focus', function (params) { self.$selection.attr('aria-activedescendant', params.data._resultId); }); container.on('selection:update', function (params) { self.update(params.data); }); container.on('open', function () { // When the dropdown is open, aria-expanded="true" self.$selection.attr('aria-expanded', 'true'); self.$selection.attr('aria-owns', resultsId); self._attachCloseHandler(container); }); container.on('close', function () { // When the dropdown is closed, aria-expanded="false" self.$selection.attr('aria-expanded', 'false'); self.$selection.removeAttr('aria-activedescendant'); self.$selection.removeAttr('aria-owns'); self.$selection.trigger('focus'); self._detachCloseHandler(container); }); container.on('enable', function () { self.$selection.attr('tabindex', self._tabindex); self.$selection.attr('aria-disabled', 'false'); }); container.on('disable', function () { self.$selection.attr('tabindex', '-1'); self.$selection.attr('aria-disabled', 'true'); }); }; BaseSelection.prototype._handleBlur = function (evt) { var self = this; // This needs to be delayed as the active element is the body when the tab // key is pressed, possibly along with others. window.setTimeout(function () { // Don't trigger `blur` if the focus is still in the selection if ( (document.activeElement == self.$selection[0]) || ($.contains(self.$selection[0], document.activeElement)) ) { return; } self.trigger('blur', evt); }, 1); }; BaseSelection.prototype._attachCloseHandler = function (container) { $(document.body).on('mousedown.select2.' + container.id, function (e) { var $target = $(e.target); var $select = $target.closest('.select2'); var $all = $('.select2.select2-container--open'); $all.each(function () { if (this == $select[0]) { return; } var $element = Utils.GetData(this, 'element'); $element.select2('close'); }); }); }; BaseSelection.prototype._detachCloseHandler = function (container) { $(document.body).off('mousedown.select2.' + container.id); }; BaseSelection.prototype.position = function ($selection, $container) { var $selectionContainer = $container.find('.selection'); $selectionContainer.append($selection); }; BaseSelection.prototype.destroy = function () { this._detachCloseHandler(this.container); }; BaseSelection.prototype.update = function (data) { throw new Error('The `update` method must be defined in child classes.'); }; /** * Helper method to abstract the "enabled" (not "disabled") state of this * object. * * @return {true} if the instance is not disabled. * @return {false} if the instance is disabled. */ BaseSelection.prototype.isEnabled = function () { return !this.isDisabled(); }; /** * Helper method to abstract the "disabled" state of this object. * * @return {true} if the disabled option is true. * @return {false} if the disabled option is false. */ BaseSelection.prototype.isDisabled = function () { return this.options.get('disabled'); }; return BaseSelection; }); S2.define('select2/selection/single',[ 'jquery', './base', '../utils', '../keys' ], function ($, BaseSelection, Utils, KEYS) { function SingleSelection () { SingleSelection.__super__.constructor.apply(this, arguments); } Utils.Extend(SingleSelection, BaseSelection); SingleSelection.prototype.render = function () { var $selection = SingleSelection.__super__.render.call(this); $selection.addClass('select2-selection--single'); $selection.html( '' + '' + '' + '' ); return $selection; }; SingleSelection.prototype.bind = function (container, $container) { var self = this; SingleSelection.__super__.bind.apply(this, arguments); var id = container.id + '-container'; this.$selection.find('.select2-selection__rendered') .attr('id', id) .attr('role', 'textbox') .attr('aria-readonly', 'true'); this.$selection.attr('aria-labelledby', id); this.$selection.on('mousedown', function (evt) { // Only respond to left clicks if (evt.which !== 1) { return; } self.trigger('toggle', { originalEvent: evt }); }); this.$selection.on('focus', function (evt) { // User focuses on the container }); this.$selection.on('blur', function (evt) { // User exits the container }); container.on('focus', function (evt) { if (!container.isOpen()) { self.$selection.trigger('focus'); } }); }; SingleSelection.prototype.clear = function () { var $rendered = this.$selection.find('.select2-selection__rendered'); $rendered.empty(); $rendered.removeAttr('title'); // clear tooltip on empty }; SingleSelection.prototype.display = function (data, container) { var template = this.options.get('templateSelection'); var escapeMarkup = this.options.get('escapeMarkup'); return escapeMarkup(template(data, container)); }; SingleSelection.prototype.selectionContainer = function () { return $(''); }; SingleSelection.prototype.update = function (data) { if (data.length === 0) { this.clear(); return; } var selection = data[0]; var $rendered = this.$selection.find('.select2-selection__rendered'); var formatted = this.display(selection, $rendered); $rendered.empty().append(formatted); var title = selection.title || selection.text; if (title) { $rendered.attr('title', title); } else { $rendered.removeAttr('title'); } }; return SingleSelection; }); S2.define('select2/selection/multiple',[ 'jquery', './base', '../utils' ], function ($, BaseSelection, Utils) { function MultipleSelection ($element, options) { MultipleSelection.__super__.constructor.apply(this, arguments); } Utils.Extend(MultipleSelection, BaseSelection); MultipleSelection.prototype.render = function () { var $selection = MultipleSelection.__super__.render.call(this); $selection.addClass('select2-selection--multiple'); $selection.html( '
                ' ); return $selection; }; MultipleSelection.prototype.bind = function (container, $container) { var self = this; MultipleSelection.__super__.bind.apply(this, arguments); this.$selection.on('click', function (evt) { self.trigger('toggle', { originalEvent: evt }); }); this.$selection.on( 'click', '.select2-selection__choice__remove', function (evt) { // Ignore the event if it is disabled if (self.isDisabled()) { return; } var $remove = $(this); var $selection = $remove.parent(); var data = Utils.GetData($selection[0], 'data'); self.trigger('unselect', { originalEvent: evt, data: data }); } ); }; MultipleSelection.prototype.clear = function () { var $rendered = this.$selection.find('.select2-selection__rendered'); $rendered.empty(); $rendered.removeAttr('title'); }; MultipleSelection.prototype.display = function (data, container) { var template = this.options.get('templateSelection'); var escapeMarkup = this.options.get('escapeMarkup'); return escapeMarkup(template(data, container)); }; MultipleSelection.prototype.selectionContainer = function () { var $container = $( '
              • ' + '' + '×' + '' + '
              • ' ); return $container; }; MultipleSelection.prototype.update = function (data) { this.clear(); if (data.length === 0) { return; } var $selections = []; for (var d = 0; d < data.length; d++) { var selection = data[d]; var $selection = this.selectionContainer(); var formatted = this.display(selection, $selection); $selection.append(formatted); var title = selection.title || selection.text; if (title) { $selection.attr('title', title); } Utils.StoreData($selection[0], 'data', selection); $selections.push($selection); } var $rendered = this.$selection.find('.select2-selection__rendered'); Utils.appendMany($rendered, $selections); }; return MultipleSelection; }); S2.define('select2/selection/placeholder',[ '../utils' ], function (Utils) { function Placeholder (decorated, $element, options) { this.placeholder = this.normalizePlaceholder(options.get('placeholder')); decorated.call(this, $element, options); } Placeholder.prototype.normalizePlaceholder = function (_, placeholder) { if (typeof placeholder === 'string') { placeholder = { id: '', text: placeholder }; } return placeholder; }; Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { var $placeholder = this.selectionContainer(); $placeholder.html(this.display(placeholder)); $placeholder.addClass('select2-selection__placeholder') .removeClass('select2-selection__choice'); return $placeholder; }; Placeholder.prototype.update = function (decorated, data) { var singlePlaceholder = ( data.length == 1 && data[0].id != this.placeholder.id ); var multipleSelections = data.length > 1; if (multipleSelections || singlePlaceholder) { return decorated.call(this, data); } this.clear(); var $placeholder = this.createPlaceholder(this.placeholder); this.$selection.find('.select2-selection__rendered').append($placeholder); }; return Placeholder; }); S2.define('select2/selection/allowClear',[ 'jquery', '../keys', '../utils' ], function ($, KEYS, Utils) { function AllowClear () { } AllowClear.prototype.bind = function (decorated, container, $container) { var self = this; decorated.call(this, container, $container); if (this.placeholder == null) { if (this.options.get('debug') && window.console && console.error) { console.error( 'Select2: The `allowClear` option should be used in combination ' + 'with the `placeholder` option.' ); } } this.$selection.on('mousedown', '.select2-selection__clear', function (evt) { self._handleClear(evt); }); container.on('keypress', function (evt) { self._handleKeyboardClear(evt, container); }); }; AllowClear.prototype._handleClear = function (_, evt) { // Ignore the event if it is disabled if (this.isDisabled()) { return; } var $clear = this.$selection.find('.select2-selection__clear'); // Ignore the event if nothing has been selected if ($clear.length === 0) { return; } evt.stopPropagation(); var data = Utils.GetData($clear[0], 'data'); var previousVal = this.$element.val(); this.$element.val(this.placeholder.id); var unselectData = { data: data }; this.trigger('clear', unselectData); if (unselectData.prevented) { this.$element.val(previousVal); return; } for (var d = 0; d < data.length; d++) { unselectData = { data: data[d] }; // Trigger the `unselect` event, so people can prevent it from being // cleared. this.trigger('unselect', unselectData); // If the event was prevented, don't clear it out. if (unselectData.prevented) { this.$element.val(previousVal); return; } } this.$element.trigger('input').trigger('change'); this.trigger('toggle', {}); }; AllowClear.prototype._handleKeyboardClear = function (_, evt, container) { if (container.isOpen()) { return; } if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) { this._handleClear(evt); } }; AllowClear.prototype.update = function (decorated, data) { decorated.call(this, data); if (this.$selection.find('.select2-selection__placeholder').length > 0 || data.length === 0) { return; } var removeAll = this.options.get('translations').get('removeAllItems'); var $remove = $( '' + '×' + '' ); Utils.StoreData($remove[0], 'data', data); this.$selection.find('.select2-selection__rendered').prepend($remove); }; return AllowClear; }); S2.define('select2/selection/search',[ 'jquery', '../utils', '../keys' ], function ($, Utils, KEYS) { function Search (decorated, $element, options) { decorated.call(this, $element, options); } Search.prototype.render = function (decorated) { var $search = $( '' ); this.$searchContainer = $search; this.$search = $search.find('input'); var $rendered = decorated.call(this); this._transferTabIndex(); return $rendered; }; Search.prototype.bind = function (decorated, container, $container) { var self = this; var resultsId = container.id + '-results'; decorated.call(this, container, $container); container.on('open', function () { self.$search.attr('aria-controls', resultsId); self.$search.trigger('focus'); }); container.on('close', function () { self.$search.val(''); self.$search.removeAttr('aria-controls'); self.$search.removeAttr('aria-activedescendant'); self.$search.trigger('focus'); }); container.on('enable', function () { self.$search.prop('disabled', false); self._transferTabIndex(); }); container.on('disable', function () { self.$search.prop('disabled', true); }); container.on('focus', function (evt) { self.$search.trigger('focus'); }); container.on('results:focus', function (params) { if (params.data._resultId) { self.$search.attr('aria-activedescendant', params.data._resultId); } else { self.$search.removeAttr('aria-activedescendant'); } }); this.$selection.on('focusin', '.select2-search--inline', function (evt) { self.trigger('focus', evt); }); this.$selection.on('focusout', '.select2-search--inline', function (evt) { self._handleBlur(evt); }); this.$selection.on('keydown', '.select2-search--inline', function (evt) { evt.stopPropagation(); self.trigger('keypress', evt); self._keyUpPrevented = evt.isDefaultPrevented(); var key = evt.which; if (key === KEYS.BACKSPACE && self.$search.val() === '') { var $previousChoice = self.$searchContainer .prev('.select2-selection__choice'); if ($previousChoice.length > 0) { var item = Utils.GetData($previousChoice[0], 'data'); self.searchRemoveChoice(item); evt.preventDefault(); } } }); this.$selection.on('click', '.select2-search--inline', function (evt) { if (self.$search.val()) { evt.stopPropagation(); } }); // Try to detect the IE version should the `documentMode` property that // is stored on the document. This is only implemented in IE and is // slightly cleaner than doing a user agent check. // This property is not available in Edge, but Edge also doesn't have // this bug. var msie = document.documentMode; var disableInputEvents = msie && msie <= 11; // Workaround for browsers which do not support the `input` event // This will prevent double-triggering of events for browsers which support // both the `keyup` and `input` events. this.$selection.on( 'input.searchcheck', '.select2-search--inline', function (evt) { // IE will trigger the `input` event when a placeholder is used on a // search box. To get around this issue, we are forced to ignore all // `input` events in IE and keep using `keyup`. if (disableInputEvents) { self.$selection.off('input.search input.searchcheck'); return; } // Unbind the duplicated `keyup` event self.$selection.off('keyup.search'); } ); this.$selection.on( 'keyup.search input.search', '.select2-search--inline', function (evt) { // IE will trigger the `input` event when a placeholder is used on a // search box. To get around this issue, we are forced to ignore all // `input` events in IE and keep using `keyup`. if (disableInputEvents && evt.type === 'input') { self.$selection.off('input.search input.searchcheck'); return; } var key = evt.which; // We can freely ignore events from modifier keys if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) { return; } // Tabbing will be handled during the `keydown` phase if (key == KEYS.TAB) { return; } self.handleSearch(evt); } ); }; /** * This method will transfer the tabindex attribute from the rendered * selection to the search box. This allows for the search box to be used as * the primary focus instead of the selection container. * * @private */ Search.prototype._transferTabIndex = function (decorated) { this.$search.attr('tabindex', this.$selection.attr('tabindex')); this.$selection.attr('tabindex', '-1'); }; Search.prototype.createPlaceholder = function (decorated, placeholder) { this.$search.attr('placeholder', placeholder.text); }; Search.prototype.update = function (decorated, data) { var searchHadFocus = this.$search[0] == document.activeElement; this.$search.attr('placeholder', ''); decorated.call(this, data); this.$selection.find('.select2-selection__rendered') .append(this.$searchContainer); this.resizeSearch(); if (searchHadFocus) { this.$search.trigger('focus'); } }; Search.prototype.handleSearch = function () { this.resizeSearch(); if (!this._keyUpPrevented) { var input = this.$search.val(); this.trigger('query', { term: input }); } this._keyUpPrevented = false; }; Search.prototype.searchRemoveChoice = function (decorated, item) { this.trigger('unselect', { data: item }); this.$search.val(item.text); this.handleSearch(); }; Search.prototype.resizeSearch = function () { this.$search.css('width', '25px'); var width = ''; if (this.$search.attr('placeholder') !== '') { width = this.$selection.find('.select2-selection__rendered').width(); } else { var minimumWidth = this.$search.val().length + 1; width = (minimumWidth * 0.75) + 'em'; } this.$search.css('width', width); }; return Search; }); S2.define('select2/selection/eventRelay',[ 'jquery' ], function ($) { function EventRelay () { } EventRelay.prototype.bind = function (decorated, container, $container) { var self = this; var relayEvents = [ 'open', 'opening', 'close', 'closing', 'select', 'selecting', 'unselect', 'unselecting', 'clear', 'clearing' ]; var preventableEvents = [ 'opening', 'closing', 'selecting', 'unselecting', 'clearing' ]; decorated.call(this, container, $container); container.on('*', function (name, params) { // Ignore events that should not be relayed if ($.inArray(name, relayEvents) === -1) { return; } // The parameters should always be an object params = params || {}; // Generate the jQuery event for the Select2 event var evt = $.Event('select2:' + name, { params: params }); self.$element.trigger(evt); // Only handle preventable events if it was one if ($.inArray(name, preventableEvents) === -1) { return; } params.prevented = evt.isDefaultPrevented(); }); }; return EventRelay; }); S2.define('select2/translation',[ 'jquery', 'require' ], function ($, require) { function Translation (dict) { this.dict = dict || {}; } Translation.prototype.all = function () { return this.dict; }; Translation.prototype.get = function (key) { return this.dict[key]; }; Translation.prototype.extend = function (translation) { this.dict = $.extend({}, translation.all(), this.dict); }; // Static functions Translation._cache = {}; Translation.loadPath = function (path) { if (!(path in Translation._cache)) { var translations = require(path); Translation._cache[path] = translations; } return new Translation(Translation._cache[path]); }; return Translation; }); S2.define('select2/diacritics',[ ], function () { var diacritics = { '\u24B6': 'A', '\uFF21': 'A', '\u00C0': 'A', '\u00C1': 'A', '\u00C2': 'A', '\u1EA6': 'A', '\u1EA4': 'A', '\u1EAA': 'A', '\u1EA8': 'A', '\u00C3': 'A', '\u0100': 'A', '\u0102': 'A', '\u1EB0': 'A', '\u1EAE': 'A', '\u1EB4': 'A', '\u1EB2': 'A', '\u0226': 'A', '\u01E0': 'A', '\u00C4': 'A', '\u01DE': 'A', '\u1EA2': 'A', '\u00C5': 'A', '\u01FA': 'A', '\u01CD': 'A', '\u0200': 'A', '\u0202': 'A', '\u1EA0': 'A', '\u1EAC': 'A', '\u1EB6': 'A', '\u1E00': 'A', '\u0104': 'A', '\u023A': 'A', '\u2C6F': 'A', '\uA732': 'AA', '\u00C6': 'AE', '\u01FC': 'AE', '\u01E2': 'AE', '\uA734': 'AO', '\uA736': 'AU', '\uA738': 'AV', '\uA73A': 'AV', '\uA73C': 'AY', '\u24B7': 'B', '\uFF22': 'B', '\u1E02': 'B', '\u1E04': 'B', '\u1E06': 'B', '\u0243': 'B', '\u0182': 'B', '\u0181': 'B', '\u24B8': 'C', '\uFF23': 'C', '\u0106': 'C', '\u0108': 'C', '\u010A': 'C', '\u010C': 'C', '\u00C7': 'C', '\u1E08': 'C', '\u0187': 'C', '\u023B': 'C', '\uA73E': 'C', '\u24B9': 'D', '\uFF24': 'D', '\u1E0A': 'D', '\u010E': 'D', '\u1E0C': 'D', '\u1E10': 'D', '\u1E12': 'D', '\u1E0E': 'D', '\u0110': 'D', '\u018B': 'D', '\u018A': 'D', '\u0189': 'D', '\uA779': 'D', '\u01F1': 'DZ', '\u01C4': 'DZ', '\u01F2': 'Dz', '\u01C5': 'Dz', '\u24BA': 'E', '\uFF25': 'E', '\u00C8': 'E', '\u00C9': 'E', '\u00CA': 'E', '\u1EC0': 'E', '\u1EBE': 'E', '\u1EC4': 'E', '\u1EC2': 'E', '\u1EBC': 'E', '\u0112': 'E', '\u1E14': 'E', '\u1E16': 'E', '\u0114': 'E', '\u0116': 'E', '\u00CB': 'E', '\u1EBA': 'E', '\u011A': 'E', '\u0204': 'E', '\u0206': 'E', '\u1EB8': 'E', '\u1EC6': 'E', '\u0228': 'E', '\u1E1C': 'E', '\u0118': 'E', '\u1E18': 'E', '\u1E1A': 'E', '\u0190': 'E', '\u018E': 'E', '\u24BB': 'F', '\uFF26': 'F', '\u1E1E': 'F', '\u0191': 'F', '\uA77B': 'F', '\u24BC': 'G', '\uFF27': 'G', '\u01F4': 'G', '\u011C': 'G', '\u1E20': 'G', '\u011E': 'G', '\u0120': 'G', '\u01E6': 'G', '\u0122': 'G', '\u01E4': 'G', '\u0193': 'G', '\uA7A0': 'G', '\uA77D': 'G', '\uA77E': 'G', '\u24BD': 'H', '\uFF28': 'H', '\u0124': 'H', '\u1E22': 'H', '\u1E26': 'H', '\u021E': 'H', '\u1E24': 'H', '\u1E28': 'H', '\u1E2A': 'H', '\u0126': 'H', '\u2C67': 'H', '\u2C75': 'H', '\uA78D': 'H', '\u24BE': 'I', '\uFF29': 'I', '\u00CC': 'I', '\u00CD': 'I', '\u00CE': 'I', '\u0128': 'I', '\u012A': 'I', '\u012C': 'I', '\u0130': 'I', '\u00CF': 'I', '\u1E2E': 'I', '\u1EC8': 'I', '\u01CF': 'I', '\u0208': 'I', '\u020A': 'I', '\u1ECA': 'I', '\u012E': 'I', '\u1E2C': 'I', '\u0197': 'I', '\u24BF': 'J', '\uFF2A': 'J', '\u0134': 'J', '\u0248': 'J', '\u24C0': 'K', '\uFF2B': 'K', '\u1E30': 'K', '\u01E8': 'K', '\u1E32': 'K', '\u0136': 'K', '\u1E34': 'K', '\u0198': 'K', '\u2C69': 'K', '\uA740': 'K', '\uA742': 'K', '\uA744': 'K', '\uA7A2': 'K', '\u24C1': 'L', '\uFF2C': 'L', '\u013F': 'L', '\u0139': 'L', '\u013D': 'L', '\u1E36': 'L', '\u1E38': 'L', '\u013B': 'L', '\u1E3C': 'L', '\u1E3A': 'L', '\u0141': 'L', '\u023D': 'L', '\u2C62': 'L', '\u2C60': 'L', '\uA748': 'L', '\uA746': 'L', '\uA780': 'L', '\u01C7': 'LJ', '\u01C8': 'Lj', '\u24C2': 'M', '\uFF2D': 'M', '\u1E3E': 'M', '\u1E40': 'M', '\u1E42': 'M', '\u2C6E': 'M', '\u019C': 'M', '\u24C3': 'N', '\uFF2E': 'N', '\u01F8': 'N', '\u0143': 'N', '\u00D1': 'N', '\u1E44': 'N', '\u0147': 'N', '\u1E46': 'N', '\u0145': 'N', '\u1E4A': 'N', '\u1E48': 'N', '\u0220': 'N', '\u019D': 'N', '\uA790': 'N', '\uA7A4': 'N', '\u01CA': 'NJ', '\u01CB': 'Nj', '\u24C4': 'O', '\uFF2F': 'O', '\u00D2': 'O', '\u00D3': 'O', '\u00D4': 'O', '\u1ED2': 'O', '\u1ED0': 'O', '\u1ED6': 'O', '\u1ED4': 'O', '\u00D5': 'O', '\u1E4C': 'O', '\u022C': 'O', '\u1E4E': 'O', '\u014C': 'O', '\u1E50': 'O', '\u1E52': 'O', '\u014E': 'O', '\u022E': 'O', '\u0230': 'O', '\u00D6': 'O', '\u022A': 'O', '\u1ECE': 'O', '\u0150': 'O', '\u01D1': 'O', '\u020C': 'O', '\u020E': 'O', '\u01A0': 'O', '\u1EDC': 'O', '\u1EDA': 'O', '\u1EE0': 'O', '\u1EDE': 'O', '\u1EE2': 'O', '\u1ECC': 'O', '\u1ED8': 'O', '\u01EA': 'O', '\u01EC': 'O', '\u00D8': 'O', '\u01FE': 'O', '\u0186': 'O', '\u019F': 'O', '\uA74A': 'O', '\uA74C': 'O', '\u0152': 'OE', '\u01A2': 'OI', '\uA74E': 'OO', '\u0222': 'OU', '\u24C5': 'P', '\uFF30': 'P', '\u1E54': 'P', '\u1E56': 'P', '\u01A4': 'P', '\u2C63': 'P', '\uA750': 'P', '\uA752': 'P', '\uA754': 'P', '\u24C6': 'Q', '\uFF31': 'Q', '\uA756': 'Q', '\uA758': 'Q', '\u024A': 'Q', '\u24C7': 'R', '\uFF32': 'R', '\u0154': 'R', '\u1E58': 'R', '\u0158': 'R', '\u0210': 'R', '\u0212': 'R', '\u1E5A': 'R', '\u1E5C': 'R', '\u0156': 'R', '\u1E5E': 'R', '\u024C': 'R', '\u2C64': 'R', '\uA75A': 'R', '\uA7A6': 'R', '\uA782': 'R', '\u24C8': 'S', '\uFF33': 'S', '\u1E9E': 'S', '\u015A': 'S', '\u1E64': 'S', '\u015C': 'S', '\u1E60': 'S', '\u0160': 'S', '\u1E66': 'S', '\u1E62': 'S', '\u1E68': 'S', '\u0218': 'S', '\u015E': 'S', '\u2C7E': 'S', '\uA7A8': 'S', '\uA784': 'S', '\u24C9': 'T', '\uFF34': 'T', '\u1E6A': 'T', '\u0164': 'T', '\u1E6C': 'T', '\u021A': 'T', '\u0162': 'T', '\u1E70': 'T', '\u1E6E': 'T', '\u0166': 'T', '\u01AC': 'T', '\u01AE': 'T', '\u023E': 'T', '\uA786': 'T', '\uA728': 'TZ', '\u24CA': 'U', '\uFF35': 'U', '\u00D9': 'U', '\u00DA': 'U', '\u00DB': 'U', '\u0168': 'U', '\u1E78': 'U', '\u016A': 'U', '\u1E7A': 'U', '\u016C': 'U', '\u00DC': 'U', '\u01DB': 'U', '\u01D7': 'U', '\u01D5': 'U', '\u01D9': 'U', '\u1EE6': 'U', '\u016E': 'U', '\u0170': 'U', '\u01D3': 'U', '\u0214': 'U', '\u0216': 'U', '\u01AF': 'U', '\u1EEA': 'U', '\u1EE8': 'U', '\u1EEE': 'U', '\u1EEC': 'U', '\u1EF0': 'U', '\u1EE4': 'U', '\u1E72': 'U', '\u0172': 'U', '\u1E76': 'U', '\u1E74': 'U', '\u0244': 'U', '\u24CB': 'V', '\uFF36': 'V', '\u1E7C': 'V', '\u1E7E': 'V', '\u01B2': 'V', '\uA75E': 'V', '\u0245': 'V', '\uA760': 'VY', '\u24CC': 'W', '\uFF37': 'W', '\u1E80': 'W', '\u1E82': 'W', '\u0174': 'W', '\u1E86': 'W', '\u1E84': 'W', '\u1E88': 'W', '\u2C72': 'W', '\u24CD': 'X', '\uFF38': 'X', '\u1E8A': 'X', '\u1E8C': 'X', '\u24CE': 'Y', '\uFF39': 'Y', '\u1EF2': 'Y', '\u00DD': 'Y', '\u0176': 'Y', '\u1EF8': 'Y', '\u0232': 'Y', '\u1E8E': 'Y', '\u0178': 'Y', '\u1EF6': 'Y', '\u1EF4': 'Y', '\u01B3': 'Y', '\u024E': 'Y', '\u1EFE': 'Y', '\u24CF': 'Z', '\uFF3A': 'Z', '\u0179': 'Z', '\u1E90': 'Z', '\u017B': 'Z', '\u017D': 'Z', '\u1E92': 'Z', '\u1E94': 'Z', '\u01B5': 'Z', '\u0224': 'Z', '\u2C7F': 'Z', '\u2C6B': 'Z', '\uA762': 'Z', '\u24D0': 'a', '\uFF41': 'a', '\u1E9A': 'a', '\u00E0': 'a', '\u00E1': 'a', '\u00E2': 'a', '\u1EA7': 'a', '\u1EA5': 'a', '\u1EAB': 'a', '\u1EA9': 'a', '\u00E3': 'a', '\u0101': 'a', '\u0103': 'a', '\u1EB1': 'a', '\u1EAF': 'a', '\u1EB5': 'a', '\u1EB3': 'a', '\u0227': 'a', '\u01E1': 'a', '\u00E4': 'a', '\u01DF': 'a', '\u1EA3': 'a', '\u00E5': 'a', '\u01FB': 'a', '\u01CE': 'a', '\u0201': 'a', '\u0203': 'a', '\u1EA1': 'a', '\u1EAD': 'a', '\u1EB7': 'a', '\u1E01': 'a', '\u0105': 'a', '\u2C65': 'a', '\u0250': 'a', '\uA733': 'aa', '\u00E6': 'ae', '\u01FD': 'ae', '\u01E3': 'ae', '\uA735': 'ao', '\uA737': 'au', '\uA739': 'av', '\uA73B': 'av', '\uA73D': 'ay', '\u24D1': 'b', '\uFF42': 'b', '\u1E03': 'b', '\u1E05': 'b', '\u1E07': 'b', '\u0180': 'b', '\u0183': 'b', '\u0253': 'b', '\u24D2': 'c', '\uFF43': 'c', '\u0107': 'c', '\u0109': 'c', '\u010B': 'c', '\u010D': 'c', '\u00E7': 'c', '\u1E09': 'c', '\u0188': 'c', '\u023C': 'c', '\uA73F': 'c', '\u2184': 'c', '\u24D3': 'd', '\uFF44': 'd', '\u1E0B': 'd', '\u010F': 'd', '\u1E0D': 'd', '\u1E11': 'd', '\u1E13': 'd', '\u1E0F': 'd', '\u0111': 'd', '\u018C': 'd', '\u0256': 'd', '\u0257': 'd', '\uA77A': 'd', '\u01F3': 'dz', '\u01C6': 'dz', '\u24D4': 'e', '\uFF45': 'e', '\u00E8': 'e', '\u00E9': 'e', '\u00EA': 'e', '\u1EC1': 'e', '\u1EBF': 'e', '\u1EC5': 'e', '\u1EC3': 'e', '\u1EBD': 'e', '\u0113': 'e', '\u1E15': 'e', '\u1E17': 'e', '\u0115': 'e', '\u0117': 'e', '\u00EB': 'e', '\u1EBB': 'e', '\u011B': 'e', '\u0205': 'e', '\u0207': 'e', '\u1EB9': 'e', '\u1EC7': 'e', '\u0229': 'e', '\u1E1D': 'e', '\u0119': 'e', '\u1E19': 'e', '\u1E1B': 'e', '\u0247': 'e', '\u025B': 'e', '\u01DD': 'e', '\u24D5': 'f', '\uFF46': 'f', '\u1E1F': 'f', '\u0192': 'f', '\uA77C': 'f', '\u24D6': 'g', '\uFF47': 'g', '\u01F5': 'g', '\u011D': 'g', '\u1E21': 'g', '\u011F': 'g', '\u0121': 'g', '\u01E7': 'g', '\u0123': 'g', '\u01E5': 'g', '\u0260': 'g', '\uA7A1': 'g', '\u1D79': 'g', '\uA77F': 'g', '\u24D7': 'h', '\uFF48': 'h', '\u0125': 'h', '\u1E23': 'h', '\u1E27': 'h', '\u021F': 'h', '\u1E25': 'h', '\u1E29': 'h', '\u1E2B': 'h', '\u1E96': 'h', '\u0127': 'h', '\u2C68': 'h', '\u2C76': 'h', '\u0265': 'h', '\u0195': 'hv', '\u24D8': 'i', '\uFF49': 'i', '\u00EC': 'i', '\u00ED': 'i', '\u00EE': 'i', '\u0129': 'i', '\u012B': 'i', '\u012D': 'i', '\u00EF': 'i', '\u1E2F': 'i', '\u1EC9': 'i', '\u01D0': 'i', '\u0209': 'i', '\u020B': 'i', '\u1ECB': 'i', '\u012F': 'i', '\u1E2D': 'i', '\u0268': 'i', '\u0131': 'i', '\u24D9': 'j', '\uFF4A': 'j', '\u0135': 'j', '\u01F0': 'j', '\u0249': 'j', '\u24DA': 'k', '\uFF4B': 'k', '\u1E31': 'k', '\u01E9': 'k', '\u1E33': 'k', '\u0137': 'k', '\u1E35': 'k', '\u0199': 'k', '\u2C6A': 'k', '\uA741': 'k', '\uA743': 'k', '\uA745': 'k', '\uA7A3': 'k', '\u24DB': 'l', '\uFF4C': 'l', '\u0140': 'l', '\u013A': 'l', '\u013E': 'l', '\u1E37': 'l', '\u1E39': 'l', '\u013C': 'l', '\u1E3D': 'l', '\u1E3B': 'l', '\u017F': 'l', '\u0142': 'l', '\u019A': 'l', '\u026B': 'l', '\u2C61': 'l', '\uA749': 'l', '\uA781': 'l', '\uA747': 'l', '\u01C9': 'lj', '\u24DC': 'm', '\uFF4D': 'm', '\u1E3F': 'm', '\u1E41': 'm', '\u1E43': 'm', '\u0271': 'm', '\u026F': 'm', '\u24DD': 'n', '\uFF4E': 'n', '\u01F9': 'n', '\u0144': 'n', '\u00F1': 'n', '\u1E45': 'n', '\u0148': 'n', '\u1E47': 'n', '\u0146': 'n', '\u1E4B': 'n', '\u1E49': 'n', '\u019E': 'n', '\u0272': 'n', '\u0149': 'n', '\uA791': 'n', '\uA7A5': 'n', '\u01CC': 'nj', '\u24DE': 'o', '\uFF4F': 'o', '\u00F2': 'o', '\u00F3': 'o', '\u00F4': 'o', '\u1ED3': 'o', '\u1ED1': 'o', '\u1ED7': 'o', '\u1ED5': 'o', '\u00F5': 'o', '\u1E4D': 'o', '\u022D': 'o', '\u1E4F': 'o', '\u014D': 'o', '\u1E51': 'o', '\u1E53': 'o', '\u014F': 'o', '\u022F': 'o', '\u0231': 'o', '\u00F6': 'o', '\u022B': 'o', '\u1ECF': 'o', '\u0151': 'o', '\u01D2': 'o', '\u020D': 'o', '\u020F': 'o', '\u01A1': 'o', '\u1EDD': 'o', '\u1EDB': 'o', '\u1EE1': 'o', '\u1EDF': 'o', '\u1EE3': 'o', '\u1ECD': 'o', '\u1ED9': 'o', '\u01EB': 'o', '\u01ED': 'o', '\u00F8': 'o', '\u01FF': 'o', '\u0254': 'o', '\uA74B': 'o', '\uA74D': 'o', '\u0275': 'o', '\u0153': 'oe', '\u01A3': 'oi', '\u0223': 'ou', '\uA74F': 'oo', '\u24DF': 'p', '\uFF50': 'p', '\u1E55': 'p', '\u1E57': 'p', '\u01A5': 'p', '\u1D7D': 'p', '\uA751': 'p', '\uA753': 'p', '\uA755': 'p', '\u24E0': 'q', '\uFF51': 'q', '\u024B': 'q', '\uA757': 'q', '\uA759': 'q', '\u24E1': 'r', '\uFF52': 'r', '\u0155': 'r', '\u1E59': 'r', '\u0159': 'r', '\u0211': 'r', '\u0213': 'r', '\u1E5B': 'r', '\u1E5D': 'r', '\u0157': 'r', '\u1E5F': 'r', '\u024D': 'r', '\u027D': 'r', '\uA75B': 'r', '\uA7A7': 'r', '\uA783': 'r', '\u24E2': 's', '\uFF53': 's', '\u00DF': 's', '\u015B': 's', '\u1E65': 's', '\u015D': 's', '\u1E61': 's', '\u0161': 's', '\u1E67': 's', '\u1E63': 's', '\u1E69': 's', '\u0219': 's', '\u015F': 's', '\u023F': 's', '\uA7A9': 's', '\uA785': 's', '\u1E9B': 's', '\u24E3': 't', '\uFF54': 't', '\u1E6B': 't', '\u1E97': 't', '\u0165': 't', '\u1E6D': 't', '\u021B': 't', '\u0163': 't', '\u1E71': 't', '\u1E6F': 't', '\u0167': 't', '\u01AD': 't', '\u0288': 't', '\u2C66': 't', '\uA787': 't', '\uA729': 'tz', '\u24E4': 'u', '\uFF55': 'u', '\u00F9': 'u', '\u00FA': 'u', '\u00FB': 'u', '\u0169': 'u', '\u1E79': 'u', '\u016B': 'u', '\u1E7B': 'u', '\u016D': 'u', '\u00FC': 'u', '\u01DC': 'u', '\u01D8': 'u', '\u01D6': 'u', '\u01DA': 'u', '\u1EE7': 'u', '\u016F': 'u', '\u0171': 'u', '\u01D4': 'u', '\u0215': 'u', '\u0217': 'u', '\u01B0': 'u', '\u1EEB': 'u', '\u1EE9': 'u', '\u1EEF': 'u', '\u1EED': 'u', '\u1EF1': 'u', '\u1EE5': 'u', '\u1E73': 'u', '\u0173': 'u', '\u1E77': 'u', '\u1E75': 'u', '\u0289': 'u', '\u24E5': 'v', '\uFF56': 'v', '\u1E7D': 'v', '\u1E7F': 'v', '\u028B': 'v', '\uA75F': 'v', '\u028C': 'v', '\uA761': 'vy', '\u24E6': 'w', '\uFF57': 'w', '\u1E81': 'w', '\u1E83': 'w', '\u0175': 'w', '\u1E87': 'w', '\u1E85': 'w', '\u1E98': 'w', '\u1E89': 'w', '\u2C73': 'w', '\u24E7': 'x', '\uFF58': 'x', '\u1E8B': 'x', '\u1E8D': 'x', '\u24E8': 'y', '\uFF59': 'y', '\u1EF3': 'y', '\u00FD': 'y', '\u0177': 'y', '\u1EF9': 'y', '\u0233': 'y', '\u1E8F': 'y', '\u00FF': 'y', '\u1EF7': 'y', '\u1E99': 'y', '\u1EF5': 'y', '\u01B4': 'y', '\u024F': 'y', '\u1EFF': 'y', '\u24E9': 'z', '\uFF5A': 'z', '\u017A': 'z', '\u1E91': 'z', '\u017C': 'z', '\u017E': 'z', '\u1E93': 'z', '\u1E95': 'z', '\u01B6': 'z', '\u0225': 'z', '\u0240': 'z', '\u2C6C': 'z', '\uA763': 'z', '\u0386': '\u0391', '\u0388': '\u0395', '\u0389': '\u0397', '\u038A': '\u0399', '\u03AA': '\u0399', '\u038C': '\u039F', '\u038E': '\u03A5', '\u03AB': '\u03A5', '\u038F': '\u03A9', '\u03AC': '\u03B1', '\u03AD': '\u03B5', '\u03AE': '\u03B7', '\u03AF': '\u03B9', '\u03CA': '\u03B9', '\u0390': '\u03B9', '\u03CC': '\u03BF', '\u03CD': '\u03C5', '\u03CB': '\u03C5', '\u03B0': '\u03C5', '\u03CE': '\u03C9', '\u03C2': '\u03C3', '\u2019': '\'' }; return diacritics; }); S2.define('select2/data/base',[ '../utils' ], function (Utils) { function BaseAdapter ($element, options) { BaseAdapter.__super__.constructor.call(this); } Utils.Extend(BaseAdapter, Utils.Observable); BaseAdapter.prototype.current = function (callback) { throw new Error('The `current` method must be defined in child classes.'); }; BaseAdapter.prototype.query = function (params, callback) { throw new Error('The `query` method must be defined in child classes.'); }; BaseAdapter.prototype.bind = function (container, $container) { // Can be implemented in subclasses }; BaseAdapter.prototype.destroy = function () { // Can be implemented in subclasses }; BaseAdapter.prototype.generateResultId = function (container, data) { var id = container.id + '-result-'; id += Utils.generateChars(4); if (data.id != null) { id += '-' + data.id.toString(); } else { id += '-' + Utils.generateChars(4); } return id; }; return BaseAdapter; }); S2.define('select2/data/select',[ './base', '../utils', 'jquery' ], function (BaseAdapter, Utils, $) { function SelectAdapter ($element, options) { this.$element = $element; this.options = options; SelectAdapter.__super__.constructor.call(this); } Utils.Extend(SelectAdapter, BaseAdapter); SelectAdapter.prototype.current = function (callback) { var data = []; var self = this; this.$element.find(':selected').each(function () { var $option = $(this); var option = self.item($option); data.push(option); }); callback(data); }; SelectAdapter.prototype.select = function (data) { var self = this; data.selected = true; // If data.element is a DOM node, use it instead if ($(data.element).is('option')) { data.element.selected = true; this.$element.trigger('input').trigger('change'); return; } if (this.$element.prop('multiple')) { this.current(function (currentData) { var val = []; data = [data]; data.push.apply(data, currentData); for (var d = 0; d < data.length; d++) { var id = data[d].id; if ($.inArray(id, val) === -1) { val.push(id); } } self.$element.val(val); self.$element.trigger('input').trigger('change'); }); } else { var val = data.id; this.$element.val(val); this.$element.trigger('input').trigger('change'); } }; SelectAdapter.prototype.unselect = function (data) { var self = this; if (!this.$element.prop('multiple')) { return; } data.selected = false; if ($(data.element).is('option')) { data.element.selected = false; this.$element.trigger('input').trigger('change'); return; } this.current(function (currentData) { var val = []; for (var d = 0; d < currentData.length; d++) { var id = currentData[d].id; if (id !== data.id && $.inArray(id, val) === -1) { val.push(id); } } self.$element.val(val); self.$element.trigger('input').trigger('change'); }); }; SelectAdapter.prototype.bind = function (container, $container) { var self = this; this.container = container; container.on('select', function (params) { self.select(params.data); }); container.on('unselect', function (params) { self.unselect(params.data); }); }; SelectAdapter.prototype.destroy = function () { // Remove anything added to child elements this.$element.find('*').each(function () { // Remove any custom data set by Select2 Utils.RemoveData(this); }); }; SelectAdapter.prototype.query = function (params, callback) { var data = []; var self = this; var $options = this.$element.children(); $options.each(function () { var $option = $(this); if (!$option.is('option') && !$option.is('optgroup')) { return; } var option = self.item($option); var matches = self.matches(params, option); if (matches !== null) { data.push(matches); } }); callback({ results: data }); }; SelectAdapter.prototype.addOptions = function ($options) { Utils.appendMany(this.$element, $options); }; SelectAdapter.prototype.option = function (data) { var option; if (data.children) { option = document.createElement('optgroup'); option.label = data.text; } else { option = document.createElement('option'); if (option.textContent !== undefined) { option.textContent = data.text; } else { option.innerText = data.text; } } if (data.id !== undefined) { option.value = data.id; } if (data.disabled) { option.disabled = true; } if (data.selected) { option.selected = true; } if (data.title) { option.title = data.title; } var $option = $(option); var normalizedData = this._normalizeItem(data); normalizedData.element = option; // Override the option's data with the combined data Utils.StoreData(option, 'data', normalizedData); return $option; }; SelectAdapter.prototype.item = function ($option) { var data = {}; data = Utils.GetData($option[0], 'data'); if (data != null) { return data; } if ($option.is('option')) { data = { id: $option.val(), text: $option.text(), disabled: $option.prop('disabled'), selected: $option.prop('selected'), title: $option.prop('title') }; } else if ($option.is('optgroup')) { data = { text: $option.prop('label'), children: [], title: $option.prop('title') }; var $children = $option.children('option'); var children = []; for (var c = 0; c < $children.length; c++) { var $child = $($children[c]); var child = this.item($child); children.push(child); } data.children = children; } data = this._normalizeItem(data); data.element = $option[0]; Utils.StoreData($option[0], 'data', data); return data; }; SelectAdapter.prototype._normalizeItem = function (item) { if (item !== Object(item)) { item = { id: item, text: item }; } item = $.extend({}, { text: '' }, item); var defaults = { selected: false, disabled: false }; if (item.id != null) { item.id = item.id.toString(); } if (item.text != null) { item.text = item.text.toString(); } if (item._resultId == null && item.id && this.container != null) { item._resultId = this.generateResultId(this.container, item); } return $.extend({}, defaults, item); }; SelectAdapter.prototype.matches = function (params, data) { var matcher = this.options.get('matcher'); return matcher(params, data); }; return SelectAdapter; }); S2.define('select2/data/array',[ './select', '../utils', 'jquery' ], function (SelectAdapter, Utils, $) { function ArrayAdapter ($element, options) { this._dataToConvert = options.get('data') || []; ArrayAdapter.__super__.constructor.call(this, $element, options); } Utils.Extend(ArrayAdapter, SelectAdapter); ArrayAdapter.prototype.bind = function (container, $container) { ArrayAdapter.__super__.bind.call(this, container, $container); this.addOptions(this.convertToOptions(this._dataToConvert)); }; ArrayAdapter.prototype.select = function (data) { var $option = this.$element.find('option').filter(function (i, elm) { return elm.value == data.id.toString(); }); if ($option.length === 0) { $option = this.option(data); this.addOptions($option); } ArrayAdapter.__super__.select.call(this, data); }; ArrayAdapter.prototype.convertToOptions = function (data) { var self = this; var $existing = this.$element.find('option'); var existingIds = $existing.map(function () { return self.item($(this)).id; }).get(); var $options = []; // Filter out all items except for the one passed in the argument function onlyItem (item) { return function () { return $(this).val() == item.id; }; } for (var d = 0; d < data.length; d++) { var item = this._normalizeItem(data[d]); // Skip items which were pre-loaded, only merge the data if ($.inArray(item.id, existingIds) >= 0) { var $existingOption = $existing.filter(onlyItem(item)); var existingData = this.item($existingOption); var newData = $.extend(true, {}, item, existingData); var $newOption = this.option(newData); $existingOption.replaceWith($newOption); continue; } var $option = this.option(item); if (item.children) { var $children = this.convertToOptions(item.children); Utils.appendMany($option, $children); } $options.push($option); } return $options; }; return ArrayAdapter; }); S2.define('select2/data/ajax',[ './array', '../utils', 'jquery' ], function (ArrayAdapter, Utils, $) { function AjaxAdapter ($element, options) { this.ajaxOptions = this._applyDefaults(options.get('ajax')); if (this.ajaxOptions.processResults != null) { this.processResults = this.ajaxOptions.processResults; } AjaxAdapter.__super__.constructor.call(this, $element, options); } Utils.Extend(AjaxAdapter, ArrayAdapter); AjaxAdapter.prototype._applyDefaults = function (options) { var defaults = { data: function (params) { return $.extend({}, params, { q: params.term }); }, transport: function (params, success, failure) { var $request = $.ajax(params); $request.then(success); $request.fail(failure); return $request; } }; return $.extend({}, defaults, options, true); }; AjaxAdapter.prototype.processResults = function (results) { return results; }; AjaxAdapter.prototype.query = function (params, callback) { var matches = []; var self = this; if (this._request != null) { // JSONP requests cannot always be aborted if ($.isFunction(this._request.abort)) { this._request.abort(); } this._request = null; } var options = $.extend({ type: 'GET' }, this.ajaxOptions); if (typeof options.url === 'function') { options.url = options.url.call(this.$element, params); } if (typeof options.data === 'function') { options.data = options.data.call(this.$element, params); } function request () { var $request = options.transport(options, function (data) { var results = self.processResults(data, params); if (self.options.get('debug') && window.console && console.error) { // Check to make sure that the response included a `results` key. if (!results || !results.results || !$.isArray(results.results)) { console.error( 'Select2: The AJAX results did not return an array in the ' + '`results` key of the response.' ); } } callback(results); }, function () { // Attempt to detect if a request was aborted // Only works if the transport exposes a status property if ('status' in $request && ($request.status === 0 || $request.status === '0')) { return; } self.trigger('results:message', { message: 'errorLoading' }); }); self._request = $request; } if (this.ajaxOptions.delay && params.term != null) { if (this._queryTimeout) { window.clearTimeout(this._queryTimeout); } this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay); } else { request(); } }; return AjaxAdapter; }); S2.define('select2/data/tags',[ 'jquery' ], function ($) { function Tags (decorated, $element, options) { var tags = options.get('tags'); var createTag = options.get('createTag'); if (createTag !== undefined) { this.createTag = createTag; } var insertTag = options.get('insertTag'); if (insertTag !== undefined) { this.insertTag = insertTag; } decorated.call(this, $element, options); if ($.isArray(tags)) { for (var t = 0; t < tags.length; t++) { var tag = tags[t]; var item = this._normalizeItem(tag); var $option = this.option(item); this.$element.append($option); } } } Tags.prototype.query = function (decorated, params, callback) { var self = this; this._removeOldTags(); if (params.term == null || params.page != null) { decorated.call(this, params, callback); return; } function wrapper (obj, child) { var data = obj.results; for (var i = 0; i < data.length; i++) { var option = data[i]; var checkChildren = ( option.children != null && !wrapper({ results: option.children }, true) ); var optionText = (option.text || '').toUpperCase(); var paramsTerm = (params.term || '').toUpperCase(); var checkText = optionText === paramsTerm; if (checkText || checkChildren) { if (child) { return false; } obj.data = data; callback(obj); return; } } if (child) { return true; } var tag = self.createTag(params); if (tag != null) { var $option = self.option(tag); $option.attr('data-select2-tag', true); self.addOptions([$option]); self.insertTag(data, tag); } obj.results = data; callback(obj); } decorated.call(this, params, wrapper); }; Tags.prototype.createTag = function (decorated, params) { var term = $.trim(params.term); if (term === '') { return null; } return { id: term, text: term }; }; Tags.prototype.insertTag = function (_, data, tag) { data.unshift(tag); }; Tags.prototype._removeOldTags = function (_) { var $options = this.$element.find('option[data-select2-tag]'); $options.each(function () { if (this.selected) { return; } $(this).remove(); }); }; return Tags; }); S2.define('select2/data/tokenizer',[ 'jquery' ], function ($) { function Tokenizer (decorated, $element, options) { var tokenizer = options.get('tokenizer'); if (tokenizer !== undefined) { this.tokenizer = tokenizer; } decorated.call(this, $element, options); } Tokenizer.prototype.bind = function (decorated, container, $container) { decorated.call(this, container, $container); this.$search = container.dropdown.$search || container.selection.$search || $container.find('.select2-search__field'); }; Tokenizer.prototype.query = function (decorated, params, callback) { var self = this; function createAndSelect (data) { // Normalize the data object so we can use it for checks var item = self._normalizeItem(data); // Check if the data object already exists as a tag // Select it if it doesn't var $existingOptions = self.$element.find('option').filter(function () { return $(this).val() === item.id; }); // If an existing option wasn't found for it, create the option if (!$existingOptions.length) { var $option = self.option(item); $option.attr('data-select2-tag', true); self._removeOldTags(); self.addOptions([$option]); } // Select the item, now that we know there is an option for it select(item); } function select (data) { self.trigger('select', { data: data }); } params.term = params.term || ''; var tokenData = this.tokenizer(params, this.options, createAndSelect); if (tokenData.term !== params.term) { // Replace the search term if we have the search box if (this.$search.length) { this.$search.val(tokenData.term); this.$search.trigger('focus'); } params.term = tokenData.term; } decorated.call(this, params, callback); }; Tokenizer.prototype.tokenizer = function (_, params, options, callback) { var separators = options.get('tokenSeparators') || []; var term = params.term; var i = 0; var createTag = this.createTag || function (params) { return { id: params.term, text: params.term }; }; while (i < term.length) { var termChar = term[i]; if ($.inArray(termChar, separators) === -1) { i++; continue; } var part = term.substr(0, i); var partParams = $.extend({}, params, { term: part }); var data = createTag(partParams); if (data == null) { i++; continue; } callback(data); // Reset the term to not include the tokenized portion term = term.substr(i + 1) || ''; i = 0; } return { term: term }; }; return Tokenizer; }); S2.define('select2/data/minimumInputLength',[ ], function () { function MinimumInputLength (decorated, $e, options) { this.minimumInputLength = options.get('minimumInputLength'); decorated.call(this, $e, options); } MinimumInputLength.prototype.query = function (decorated, params, callback) { params.term = params.term || ''; if (params.term.length < this.minimumInputLength) { this.trigger('results:message', { message: 'inputTooShort', args: { minimum: this.minimumInputLength, input: params.term, params: params } }); return; } decorated.call(this, params, callback); }; return MinimumInputLength; }); S2.define('select2/data/maximumInputLength',[ ], function () { function MaximumInputLength (decorated, $e, options) { this.maximumInputLength = options.get('maximumInputLength'); decorated.call(this, $e, options); } MaximumInputLength.prototype.query = function (decorated, params, callback) { params.term = params.term || ''; if (this.maximumInputLength > 0 && params.term.length > this.maximumInputLength) { this.trigger('results:message', { message: 'inputTooLong', args: { maximum: this.maximumInputLength, input: params.term, params: params } }); return; } decorated.call(this, params, callback); }; return MaximumInputLength; }); S2.define('select2/data/maximumSelectionLength',[ ], function (){ function MaximumSelectionLength (decorated, $e, options) { this.maximumSelectionLength = options.get('maximumSelectionLength'); decorated.call(this, $e, options); } MaximumSelectionLength.prototype.bind = function (decorated, container, $container) { var self = this; decorated.call(this, container, $container); container.on('select', function () { self._checkIfMaximumSelected(); }); }; MaximumSelectionLength.prototype.query = function (decorated, params, callback) { var self = this; this._checkIfMaximumSelected(function () { decorated.call(self, params, callback); }); }; MaximumSelectionLength.prototype._checkIfMaximumSelected = function (_, successCallback) { var self = this; this.current(function (currentData) { var count = currentData != null ? currentData.length : 0; if (self.maximumSelectionLength > 0 && count >= self.maximumSelectionLength) { self.trigger('results:message', { message: 'maximumSelected', args: { maximum: self.maximumSelectionLength } }); return; } if (successCallback) { successCallback(); } }); }; return MaximumSelectionLength; }); S2.define('select2/dropdown',[ 'jquery', './utils' ], function ($, Utils) { function Dropdown ($element, options) { this.$element = $element; this.options = options; Dropdown.__super__.constructor.call(this); } Utils.Extend(Dropdown, Utils.Observable); Dropdown.prototype.render = function () { var $dropdown = $( '' + '' + '' ); $dropdown.attr('dir', this.options.get('dir')); this.$dropdown = $dropdown; return $dropdown; }; Dropdown.prototype.bind = function () { // Should be implemented in subclasses }; Dropdown.prototype.position = function ($dropdown, $container) { // Should be implemented in subclasses }; Dropdown.prototype.destroy = function () { // Remove the dropdown from the DOM this.$dropdown.remove(); }; return Dropdown; }); S2.define('select2/dropdown/search',[ 'jquery', '../utils' ], function ($, Utils) { function Search () { } Search.prototype.render = function (decorated) { var $rendered = decorated.call(this); var $search = $( '' + '' + '' ); this.$searchContainer = $search; this.$search = $search.find('input'); $rendered.prepend($search); return $rendered; }; Search.prototype.bind = function (decorated, container, $container) { var self = this; var resultsId = container.id + '-results'; decorated.call(this, container, $container); this.$search.on('keydown', function (evt) { self.trigger('keypress', evt); self._keyUpPrevented = evt.isDefaultPrevented(); }); // Workaround for browsers which do not support the `input` event // This will prevent double-triggering of events for browsers which support // both the `keyup` and `input` events. this.$search.on('input', function (evt) { // Unbind the duplicated `keyup` event $(this).off('keyup'); }); this.$search.on('keyup input', function (evt) { self.handleSearch(evt); }); container.on('open', function () { self.$search.attr('tabindex', 0); self.$search.attr('aria-controls', resultsId); self.$search.trigger('focus'); window.setTimeout(function () { self.$search.trigger('focus'); }, 0); }); container.on('close', function () { self.$search.attr('tabindex', -1); self.$search.removeAttr('aria-controls'); self.$search.removeAttr('aria-activedescendant'); self.$search.val(''); self.$search.trigger('blur'); }); container.on('focus', function () { if (!container.isOpen()) { self.$search.trigger('focus'); } }); container.on('results:all', function (params) { if (params.query.term == null || params.query.term === '') { var showSearch = self.showSearch(params); if (showSearch) { self.$searchContainer.removeClass('select2-search--hide'); } else { self.$searchContainer.addClass('select2-search--hide'); } } }); container.on('results:focus', function (params) { if (params.data._resultId) { self.$search.attr('aria-activedescendant', params.data._resultId); } else { self.$search.removeAttr('aria-activedescendant'); } }); }; Search.prototype.handleSearch = function (evt) { if (!this._keyUpPrevented) { var input = this.$search.val(); this.trigger('query', { term: input }); } this._keyUpPrevented = false; }; Search.prototype.showSearch = function (_, params) { return true; }; return Search; }); S2.define('select2/dropdown/hidePlaceholder',[ ], function () { function HidePlaceholder (decorated, $element, options, dataAdapter) { this.placeholder = this.normalizePlaceholder(options.get('placeholder')); decorated.call(this, $element, options, dataAdapter); } HidePlaceholder.prototype.append = function (decorated, data) { data.results = this.removePlaceholder(data.results); decorated.call(this, data); }; HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) { if (typeof placeholder === 'string') { placeholder = { id: '', text: placeholder }; } return placeholder; }; HidePlaceholder.prototype.removePlaceholder = function (_, data) { var modifiedData = data.slice(0); for (var d = data.length - 1; d >= 0; d--) { var item = data[d]; if (this.placeholder.id === item.id) { modifiedData.splice(d, 1); } } return modifiedData; }; return HidePlaceholder; }); S2.define('select2/dropdown/infiniteScroll',[ 'jquery' ], function ($) { function InfiniteScroll (decorated, $element, options, dataAdapter) { this.lastParams = {}; decorated.call(this, $element, options, dataAdapter); this.$loadingMore = this.createLoadingMore(); this.loading = false; } InfiniteScroll.prototype.append = function (decorated, data) { this.$loadingMore.remove(); this.loading = false; decorated.call(this, data); if (this.showLoadingMore(data)) { this.$results.append(this.$loadingMore); this.loadMoreIfNeeded(); } }; InfiniteScroll.prototype.bind = function (decorated, container, $container) { var self = this; decorated.call(this, container, $container); container.on('query', function (params) { self.lastParams = params; self.loading = true; }); container.on('query:append', function (params) { self.lastParams = params; self.loading = true; }); this.$results.on('scroll', this.loadMoreIfNeeded.bind(this)); }; InfiniteScroll.prototype.loadMoreIfNeeded = function () { var isLoadMoreVisible = $.contains( document.documentElement, this.$loadingMore[0] ); if (this.loading || !isLoadMoreVisible) { return; } var currentOffset = this.$results.offset().top + this.$results.outerHeight(false); var loadingMoreOffset = this.$loadingMore.offset().top + this.$loadingMore.outerHeight(false); if (currentOffset + 50 >= loadingMoreOffset) { this.loadMore(); } }; InfiniteScroll.prototype.loadMore = function () { this.loading = true; var params = $.extend({}, {page: 1}, this.lastParams); params.page++; this.trigger('query:append', params); }; InfiniteScroll.prototype.showLoadingMore = function (_, data) { return data.pagination && data.pagination.more; }; InfiniteScroll.prototype.createLoadingMore = function () { var $option = $( '
              • ' ); var message = this.options.get('translations').get('loadingMore'); $option.html(message(this.lastParams)); return $option; }; return InfiniteScroll; }); S2.define('select2/dropdown/attachBody',[ 'jquery', '../utils' ], function ($, Utils) { function AttachBody (decorated, $element, options) { this.$dropdownParent = $(options.get('dropdownParent') || document.body); decorated.call(this, $element, options); } AttachBody.prototype.bind = function (decorated, container, $container) { var self = this; decorated.call(this, container, $container); container.on('open', function () { self._showDropdown(); self._attachPositioningHandler(container); // Must bind after the results handlers to ensure correct sizing self._bindContainerResultHandlers(container); }); container.on('close', function () { self._hideDropdown(); self._detachPositioningHandler(container); }); this.$dropdownContainer.on('mousedown', function (evt) { evt.stopPropagation(); }); }; AttachBody.prototype.destroy = function (decorated) { decorated.call(this); this.$dropdownContainer.remove(); }; AttachBody.prototype.position = function (decorated, $dropdown, $container) { // Clone all of the container classes $dropdown.attr('class', $container.attr('class')); $dropdown.removeClass('select2'); $dropdown.addClass('select2-container--open'); $dropdown.css({ position: 'absolute', top: -999999 }); this.$container = $container; }; AttachBody.prototype.render = function (decorated) { var $container = $(''); var $dropdown = decorated.call(this); $container.append($dropdown); this.$dropdownContainer = $container; return $container; }; AttachBody.prototype._hideDropdown = function (decorated) { this.$dropdownContainer.detach(); }; AttachBody.prototype._bindContainerResultHandlers = function (decorated, container) { // These should only be bound once if (this._containerResultsHandlersBound) { return; } var self = this; container.on('results:all', function () { self._positionDropdown(); self._resizeDropdown(); }); container.on('results:append', function () { self._positionDropdown(); self._resizeDropdown(); }); container.on('results:message', function () { self._positionDropdown(); self._resizeDropdown(); }); container.on('select', function () { self._positionDropdown(); self._resizeDropdown(); }); container.on('unselect', function () { self._positionDropdown(); self._resizeDropdown(); }); this._containerResultsHandlersBound = true; }; AttachBody.prototype._attachPositioningHandler = function (decorated, container) { var self = this; var scrollEvent = 'scroll.select2.' + container.id; var resizeEvent = 'resize.select2.' + container.id; var orientationEvent = 'orientationchange.select2.' + container.id; var $watchers = this.$container.parents().filter(Utils.hasScroll); $watchers.each(function () { Utils.StoreData(this, 'select2-scroll-position', { x: $(this).scrollLeft(), y: $(this).scrollTop() }); }); $watchers.on(scrollEvent, function (ev) { var position = Utils.GetData(this, 'select2-scroll-position'); $(this).scrollTop(position.y); }); $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent, function (e) { self._positionDropdown(); self._resizeDropdown(); }); }; AttachBody.prototype._detachPositioningHandler = function (decorated, container) { var scrollEvent = 'scroll.select2.' + container.id; var resizeEvent = 'resize.select2.' + container.id; var orientationEvent = 'orientationchange.select2.' + container.id; var $watchers = this.$container.parents().filter(Utils.hasScroll); $watchers.off(scrollEvent); $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent); }; AttachBody.prototype._positionDropdown = function () { var $window = $(window); var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above'); var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below'); var newDirection = null; var offset = this.$container.offset(); offset.bottom = offset.top + this.$container.outerHeight(false); var container = { height: this.$container.outerHeight(false) }; container.top = offset.top; container.bottom = offset.top + container.height; var dropdown = { height: this.$dropdown.outerHeight(false) }; var viewport = { top: $window.scrollTop(), bottom: $window.scrollTop() + $window.height() }; var enoughRoomAbove = viewport.top < (offset.top - dropdown.height); var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height); var css = { left: offset.left, top: container.bottom }; // Determine what the parent element is to use for calculating the offset var $offsetParent = this.$dropdownParent; // For statically positioned elements, we need to get the element // that is determining the offset if ($offsetParent.css('position') === 'static') { $offsetParent = $offsetParent.offsetParent(); } var parentOffset = { top: 0, left: 0 }; if ( $.contains(document.body, $offsetParent[0]) || $offsetParent[0].isConnected ) { parentOffset = $offsetParent.offset(); } css.top -= parentOffset.top; css.left -= parentOffset.left; if (!isCurrentlyAbove && !isCurrentlyBelow) { newDirection = 'below'; } if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) { newDirection = 'above'; } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) { newDirection = 'below'; } if (newDirection == 'above' || (isCurrentlyAbove && newDirection !== 'below')) { css.top = container.top - parentOffset.top - dropdown.height; } if (newDirection != null) { this.$dropdown .removeClass('select2-dropdown--below select2-dropdown--above') .addClass('select2-dropdown--' + newDirection); this.$container .removeClass('select2-container--below select2-container--above') .addClass('select2-container--' + newDirection); } this.$dropdownContainer.css(css); }; AttachBody.prototype._resizeDropdown = function () { var css = { width: this.$container.outerWidth(false) + 'px' }; if (this.options.get('dropdownAutoWidth')) { css.minWidth = css.width; css.position = 'relative'; css.width = 'auto'; } this.$dropdown.css(css); }; AttachBody.prototype._showDropdown = function (decorated) { this.$dropdownContainer.appendTo(this.$dropdownParent); this._positionDropdown(); this._resizeDropdown(); }; return AttachBody; }); S2.define('select2/dropdown/minimumResultsForSearch',[ ], function () { function countResults (data) { var count = 0; for (var d = 0; d < data.length; d++) { var item = data[d]; if (item.children) { count += countResults(item.children); } else { count++; } } return count; } function MinimumResultsForSearch (decorated, $element, options, dataAdapter) { this.minimumResultsForSearch = options.get('minimumResultsForSearch'); if (this.minimumResultsForSearch < 0) { this.minimumResultsForSearch = Infinity; } decorated.call(this, $element, options, dataAdapter); } MinimumResultsForSearch.prototype.showSearch = function (decorated, params) { if (countResults(params.data.results) < this.minimumResultsForSearch) { return false; } return decorated.call(this, params); }; return MinimumResultsForSearch; }); S2.define('select2/dropdown/selectOnClose',[ '../utils' ], function (Utils) { function SelectOnClose () { } SelectOnClose.prototype.bind = function (decorated, container, $container) { var self = this; decorated.call(this, container, $container); container.on('close', function (params) { self._handleSelectOnClose(params); }); }; SelectOnClose.prototype._handleSelectOnClose = function (_, params) { if (params && params.originalSelect2Event != null) { var event = params.originalSelect2Event; // Don't select an item if the close event was triggered from a select or // unselect event if (event._type === 'select' || event._type === 'unselect') { return; } } var $highlightedResults = this.getHighlightedResults(); // Only select highlighted results if ($highlightedResults.length < 1) { return; } var data = Utils.GetData($highlightedResults[0], 'data'); // Don't re-select already selected resulte if ( (data.element != null && data.element.selected) || (data.element == null && data.selected) ) { return; } this.trigger('select', { data: data }); }; return SelectOnClose; }); S2.define('select2/dropdown/closeOnSelect',[ ], function () { function CloseOnSelect () { } CloseOnSelect.prototype.bind = function (decorated, container, $container) { var self = this; decorated.call(this, container, $container); container.on('select', function (evt) { self._selectTriggered(evt); }); container.on('unselect', function (evt) { self._selectTriggered(evt); }); }; CloseOnSelect.prototype._selectTriggered = function (_, evt) { var originalEvent = evt.originalEvent; // Don't close if the control key is being held if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) { return; } this.trigger('close', { originalEvent: originalEvent, originalSelect2Event: evt }); }; return CloseOnSelect; }); S2.define('select2/i18n/en',[],function () { // English return { errorLoading: function () { return '无法载入结果。'; }, inputTooLong: function (args) { var overChars = args.input.length - args.maximum; var message = '请删除' + overChars + '个字符'; if (overChars != 1) { message += 's'; } return message; }, inputTooShort: function (args) { var remainingChars = args.minimum - args.input.length; var message = '请再输入至少' + remainingChars + '个字符'; return message; }, loadingMore: function () { return '载入更多结果…'; }, maximumSelected: function (args) { var message = '最多只能' + args.maximum + '个选项'; if (args.maximum != 1) { message += 's'; } return message; }, noResults: function () { return '未找到结果'; }, searching: function () { return '搜索中…'; }, removeAllItems: function () { return '删除所有项目'; } }; }); S2.define('select2/defaults',[ 'jquery', 'require', './results', './selection/single', './selection/multiple', './selection/placeholder', './selection/allowClear', './selection/search', './selection/eventRelay', './utils', './translation', './diacritics', './data/select', './data/array', './data/ajax', './data/tags', './data/tokenizer', './data/minimumInputLength', './data/maximumInputLength', './data/maximumSelectionLength', './dropdown', './dropdown/search', './dropdown/hidePlaceholder', './dropdown/infiniteScroll', './dropdown/attachBody', './dropdown/minimumResultsForSearch', './dropdown/selectOnClose', './dropdown/closeOnSelect', './i18n/en' ], function ($, require, ResultsList, SingleSelection, MultipleSelection, Placeholder, AllowClear, SelectionSearch, EventRelay, Utils, Translation, DIACRITICS, SelectData, ArrayData, AjaxData, Tags, Tokenizer, MinimumInputLength, MaximumInputLength, MaximumSelectionLength, Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll, AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect, EnglishTranslation) { function Defaults () { this.reset(); } Defaults.prototype.apply = function (options) { options = $.extend(true, {}, this.defaults, options); if (options.dataAdapter == null) { if (options.ajax != null) { options.dataAdapter = AjaxData; } else if (options.data != null) { options.dataAdapter = ArrayData; } else { options.dataAdapter = SelectData; } if (options.minimumInputLength > 0) { options.dataAdapter = Utils.Decorate( options.dataAdapter, MinimumInputLength ); } if (options.maximumInputLength > 0) { options.dataAdapter = Utils.Decorate( options.dataAdapter, MaximumInputLength ); } if (options.maximumSelectionLength > 0) { options.dataAdapter = Utils.Decorate( options.dataAdapter, MaximumSelectionLength ); } if (options.tags) { options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags); } if (options.tokenSeparators != null || options.tokenizer != null) { options.dataAdapter = Utils.Decorate( options.dataAdapter, Tokenizer ); } if (options.query != null) { var Query = require(options.amdBase + 'compat/query'); options.dataAdapter = Utils.Decorate( options.dataAdapter, Query ); } if (options.initSelection != null) { var InitSelection = require(options.amdBase + 'compat/initSelection'); options.dataAdapter = Utils.Decorate( options.dataAdapter, InitSelection ); } } if (options.resultsAdapter == null) { options.resultsAdapter = ResultsList; if (options.ajax != null) { options.resultsAdapter = Utils.Decorate( options.resultsAdapter, InfiniteScroll ); } if (options.placeholder != null) { options.resultsAdapter = Utils.Decorate( options.resultsAdapter, HidePlaceholder ); } if (options.selectOnClose) { options.resultsAdapter = Utils.Decorate( options.resultsAdapter, SelectOnClose ); } } if (options.dropdownAdapter == null) { if (options.multiple) { options.dropdownAdapter = Dropdown; } else { var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch); options.dropdownAdapter = SearchableDropdown; } if (options.minimumResultsForSearch !== 0) { options.dropdownAdapter = Utils.Decorate( options.dropdownAdapter, MinimumResultsForSearch ); } if (options.closeOnSelect) { options.dropdownAdapter = Utils.Decorate( options.dropdownAdapter, CloseOnSelect ); } if ( options.dropdownCssClass != null || options.dropdownCss != null || options.adaptDropdownCssClass != null ) { var DropdownCSS = require(options.amdBase + 'compat/dropdownCss'); options.dropdownAdapter = Utils.Decorate( options.dropdownAdapter, DropdownCSS ); } options.dropdownAdapter = Utils.Decorate( options.dropdownAdapter, AttachBody ); } if (options.selectionAdapter == null) { if (options.multiple) { options.selectionAdapter = MultipleSelection; } else { options.selectionAdapter = SingleSelection; } // Add the placeholder mixin if a placeholder was specified if (options.placeholder != null) { options.selectionAdapter = Utils.Decorate( options.selectionAdapter, Placeholder ); } if (options.allowClear) { options.selectionAdapter = Utils.Decorate( options.selectionAdapter, AllowClear ); } if (options.multiple) { options.selectionAdapter = Utils.Decorate( options.selectionAdapter, SelectionSearch ); } if ( options.containerCssClass != null || options.containerCss != null || options.adaptContainerCssClass != null ) { var ContainerCSS = require(options.amdBase + 'compat/containerCss'); options.selectionAdapter = Utils.Decorate( options.selectionAdapter, ContainerCSS ); } options.selectionAdapter = Utils.Decorate( options.selectionAdapter, EventRelay ); } // If the defaults were not previously applied from an element, it is // possible for the language option to have not been resolved options.language = this._resolveLanguage(options.language); // Always fall back to English since it will always be complete options.language.push('en'); var uniqueLanguages = []; for (var l = 0; l < options.language.length; l++) { var language = options.language[l]; if (uniqueLanguages.indexOf(language) === -1) { uniqueLanguages.push(language); } } options.language = uniqueLanguages; options.translations = this._processTranslations( options.language, options.debug ); return options; }; Defaults.prototype.reset = function () { function stripDiacritics (text) { // Used 'uni range + named function' from http://jsperf.com/diacritics/18 function match(a) { return DIACRITICS[a] || a; } return text.replace(/[^\u0000-\u007E]/g, match); } function matcher (params, data) { // Always return the object if there is nothing to compare if ($.trim(params.term) === '') { return data; } // Do a recursive check for options with children if (data.children && data.children.length > 0) { // Clone the data object if there are children // This is required as we modify the object to remove any non-matches var match = $.extend(true, {}, data); // Check each child of the option for (var c = data.children.length - 1; c >= 0; c--) { var child = data.children[c]; var matches = matcher(params, child); // If there wasn't a match, remove the object in the array if (matches == null) { match.children.splice(c, 1); } } // If any children matched, return the new object if (match.children.length > 0) { return match; } // If there were no matching children, check just the plain object return matcher(params, match); } var original = stripDiacritics(data.text).toUpperCase(); var term = stripDiacritics(params.term).toUpperCase(); // Check if the text contains the term if (original.indexOf(term) > -1) { return data; } // If it doesn't contain the term, don't return anything return null; } this.defaults = { amdBase: './', amdLanguageBase: './i18n/', closeOnSelect: true, debug: false, dropdownAutoWidth: false, escapeMarkup: Utils.escapeMarkup, language: {}, matcher: matcher, minimumInputLength: 0, maximumInputLength: 0, maximumSelectionLength: 0, minimumResultsForSearch: 0, selectOnClose: false, scrollAfterSelect: false, sorter: function (data) { return data; }, templateResult: function (result) { return result.text; }, templateSelection: function (selection) { return selection.text; }, theme: 'default', width: 'resolve' }; }; Defaults.prototype.applyFromElement = function (options, $element) { var optionLanguage = options.language; var defaultLanguage = this.defaults.language; var elementLanguage = $element.prop('lang'); var parentLanguage = $element.closest('[lang]').prop('lang'); var languages = Array.prototype.concat.call( this._resolveLanguage(elementLanguage), this._resolveLanguage(optionLanguage), this._resolveLanguage(defaultLanguage), this._resolveLanguage(parentLanguage) ); options.language = languages; return options; }; Defaults.prototype._resolveLanguage = function (language) { if (!language) { return []; } if ($.isEmptyObject(language)) { return []; } if ($.isPlainObject(language)) { return [language]; } var languages; if (!$.isArray(language)) { languages = [language]; } else { languages = language; } var resolvedLanguages = []; for (var l = 0; l < languages.length; l++) { resolvedLanguages.push(languages[l]); if (typeof languages[l] === 'string' && languages[l].indexOf('-') > 0) { // Extract the region information if it is included var languageParts = languages[l].split('-'); var baseLanguage = languageParts[0]; resolvedLanguages.push(baseLanguage); } } return resolvedLanguages; }; Defaults.prototype._processTranslations = function (languages, debug) { var translations = new Translation(); for (var l = 0; l < languages.length; l++) { var languageData = new Translation(); var language = languages[l]; if (typeof language === 'string') { try { // Try to load it with the original name languageData = Translation.loadPath(language); } catch (e) { try { // If we couldn't load it, check if it wasn't the full path language = this.defaults.amdLanguageBase + language; languageData = Translation.loadPath(language); } catch (ex) { // The translation could not be loaded at all. Sometimes this is // because of a configuration problem, other times this can be // because of how Select2 helps load all possible translation files if (debug && window.console && console.warn) { console.warn( 'Select2: The language file for "' + language + '" could ' + 'not be automatically loaded. A fallback will be used instead.' ); } } } } else if ($.isPlainObject(language)) { languageData = new Translation(language); } else { languageData = language; } translations.extend(languageData); } return translations; }; Defaults.prototype.set = function (key, value) { var camelKey = $.camelCase(key); var data = {}; data[camelKey] = value; var convertedData = Utils._convertData(data); $.extend(true, this.defaults, convertedData); }; var defaults = new Defaults(); return defaults; }); S2.define('select2/options',[ 'require', 'jquery', './defaults', './utils' ], function (require, $, Defaults, Utils) { function Options (options, $element) { this.options = options; if ($element != null) { this.fromElement($element); } if ($element != null) { this.options = Defaults.applyFromElement(this.options, $element); } this.options = Defaults.apply(this.options); if ($element && $element.is('input')) { var InputCompat = require(this.get('amdBase') + 'compat/inputData'); this.options.dataAdapter = Utils.Decorate( this.options.dataAdapter, InputCompat ); } } Options.prototype.fromElement = function ($e) { var excludedData = ['select2']; if (this.options.multiple == null) { this.options.multiple = $e.prop('multiple'); } if (this.options.disabled == null) { this.options.disabled = $e.prop('disabled'); } if (this.options.dir == null) { if ($e.prop('dir')) { this.options.dir = $e.prop('dir'); } else if ($e.closest('[dir]').prop('dir')) { this.options.dir = $e.closest('[dir]').prop('dir'); } else { this.options.dir = 'ltr'; } } $e.prop('disabled', this.options.disabled); $e.prop('multiple', this.options.multiple); if (Utils.GetData($e[0], 'select2Tags')) { if (this.options.debug && window.console && console.warn) { console.warn( 'Select2: The `data-select2-tags` attribute has been changed to ' + 'use the `data-data` and `data-tags="true"` attributes and will be ' + 'removed in future versions of Select2.' ); } Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags')); Utils.StoreData($e[0], 'tags', true); } if (Utils.GetData($e[0], 'ajaxUrl')) { if (this.options.debug && window.console && console.warn) { console.warn( 'Select2: The `data-ajax-url` attribute has been changed to ' + '`data-ajax--url` and support for the old attribute will be removed' + ' in future versions of Select2.' ); } $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl')); } var dataset = {}; function upperCaseLetter(_, letter) { return letter.toUpperCase(); } // Pre-load all of the attributes which are prefixed with `data-` for (var attr = 0; attr < $e[0].attributes.length; attr++) { var attributeName = $e[0].attributes[attr].name; var prefix = 'data-'; if (attributeName.substr(0, prefix.length) == prefix) { // Get the contents of the attribute after `data-` var dataName = attributeName.substring(prefix.length); // Get the data contents from the consistent source // This is more than likely the jQuery data helper var dataValue = Utils.GetData($e[0], dataName); // camelCase the attribute name to match the spec var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter); // Store the data attribute contents into the dataset since dataset[camelDataName] = dataValue; } } // Prefer the element's `dataset` attribute if it exists // jQuery 1.x does not correctly handle data attributes with multiple dashes if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) { dataset = $.extend(true, {}, $e[0].dataset, dataset); } // Prefer our internal data cache if it exists var data = $.extend(true, {}, Utils.GetData($e[0]), dataset); data = Utils._convertData(data); for (var key in data) { if ($.inArray(key, excludedData) > -1) { continue; } if ($.isPlainObject(this.options[key])) { $.extend(this.options[key], data[key]); } else { this.options[key] = data[key]; } } return this; }; Options.prototype.get = function (key) { return this.options[key]; }; Options.prototype.set = function (key, val) { this.options[key] = val; }; return Options; }); S2.define('select2/core',[ 'jquery', './options', './utils', './keys' ], function ($, Options, Utils, KEYS) { var Select2 = function ($element, options) { if (Utils.GetData($element[0], 'select2') != null) { Utils.GetData($element[0], 'select2').destroy(); } this.$element = $element; this.id = this._generateId($element); options = options || {}; this.options = new Options(options, $element); Select2.__super__.constructor.call(this); // Set up the tabindex var tabindex = $element.attr('tabindex') || 0; Utils.StoreData($element[0], 'old-tabindex', tabindex); $element.attr('tabindex', '-1'); // Set up containers and adapters var DataAdapter = this.options.get('dataAdapter'); this.dataAdapter = new DataAdapter($element, this.options); var $container = this.render(); this._placeContainer($container); var SelectionAdapter = this.options.get('selectionAdapter'); this.selection = new SelectionAdapter($element, this.options); this.$selection = this.selection.render(); this.selection.position(this.$selection, $container); var DropdownAdapter = this.options.get('dropdownAdapter'); this.dropdown = new DropdownAdapter($element, this.options); this.$dropdown = this.dropdown.render(); this.dropdown.position(this.$dropdown, $container); var ResultsAdapter = this.options.get('resultsAdapter'); this.results = new ResultsAdapter($element, this.options, this.dataAdapter); this.$results = this.results.render(); this.results.position(this.$results, this.$dropdown); // Bind events var self = this; // Bind the container to all of the adapters this._bindAdapters(); // Register any DOM event handlers this._registerDomEvents(); // Register any internal event handlers this._registerDataEvents(); this._registerSelectionEvents(); this._registerDropdownEvents(); this._registerResultsEvents(); this._registerEvents(); // Set the initial state this.dataAdapter.current(function (initialData) { self.trigger('selection:update', { data: initialData }); }); // Hide the original select $element.addClass('select2-hidden-accessible'); $element.attr('aria-hidden', 'true'); // Synchronize any monitored attributes this._syncAttributes(); Utils.StoreData($element[0], 'select2', this); // Ensure backwards compatibility with $element.data('select2'). $element.data('select2', this); }; Utils.Extend(Select2, Utils.Observable); Select2.prototype._generateId = function ($element) { var id = ''; if ($element.attr('id') != null) { id = $element.attr('id'); } else if ($element.attr('name') != null) { id = $element.attr('name') + '-' + Utils.generateChars(2); } else { id = Utils.generateChars(4); } id = id.replace(/(:|\.|\[|\]|,)/g, ''); id = 'select2-' + id; return id; }; Select2.prototype._placeContainer = function ($container) { $container.insertAfter(this.$element); var width = this._resolveWidth(this.$element, this.options.get('width')); if (width != null) { $container.css('width', width); } }; Select2.prototype._resolveWidth = function ($element, method) { var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i; if (method == 'resolve') { var styleWidth = this._resolveWidth($element, 'style'); if (styleWidth != null) { return styleWidth; } return this._resolveWidth($element, 'element'); } if (method == 'element') { var elementWidth = $element.outerWidth(false); if (elementWidth <= 0) { return 'auto'; } return elementWidth + 'px'; } if (method == 'style') { var style = $element.attr('style'); if (typeof(style) !== 'string') { return null; } var attrs = style.split(';'); for (var i = 0, l = attrs.length; i < l; i = i + 1) { var attr = attrs[i].replace(/\s/g, ''); var matches = attr.match(WIDTH); if (matches !== null && matches.length >= 1) { return matches[1]; } } return null; } if (method == 'computedstyle') { var computedStyle = window.getComputedStyle($element[0]); return computedStyle.width; } return method; }; Select2.prototype._bindAdapters = function () { this.dataAdapter.bind(this, this.$container); this.selection.bind(this, this.$container); this.dropdown.bind(this, this.$container); this.results.bind(this, this.$container); }; Select2.prototype._registerDomEvents = function () { var self = this; this.$element.on('change.select2', function () { self.dataAdapter.current(function (data) { self.trigger('selection:update', { data: data }); }); }); this.$element.on('focus.select2', function (evt) { self.trigger('focus', evt); }); this._syncA = Utils.bind(this._syncAttributes, this); this._syncS = Utils.bind(this._syncSubtree, this); if (this.$element[0].attachEvent) { this.$element[0].attachEvent('onpropertychange', this._syncA); } var observer = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver ; if (observer != null) { this._observer = new observer(function (mutations) { self._syncA(); self._syncS(null, mutations); }); this._observer.observe(this.$element[0], { attributes: true, childList: true, subtree: false }); } else if (this.$element[0].addEventListener) { this.$element[0].addEventListener( 'DOMAttrModified', self._syncA, false ); this.$element[0].addEventListener( 'DOMNodeInserted', self._syncS, false ); this.$element[0].addEventListener( 'DOMNodeRemoved', self._syncS, false ); } }; Select2.prototype._registerDataEvents = function () { var self = this; this.dataAdapter.on('*', function (name, params) { self.trigger(name, params); }); }; Select2.prototype._registerSelectionEvents = function () { var self = this; var nonRelayEvents = ['toggle', 'focus']; this.selection.on('toggle', function () { self.toggleDropdown(); }); this.selection.on('focus', function (params) { self.focus(params); }); this.selection.on('*', function (name, params) { if ($.inArray(name, nonRelayEvents) !== -1) { return; } self.trigger(name, params); }); }; Select2.prototype._registerDropdownEvents = function () { var self = this; this.dropdown.on('*', function (name, params) { self.trigger(name, params); }); }; Select2.prototype._registerResultsEvents = function () { var self = this; this.results.on('*', function (name, params) { self.trigger(name, params); }); }; Select2.prototype._registerEvents = function () { var self = this; this.on('open', function () { self.$container.addClass('select2-container--open'); }); this.on('close', function () { self.$container.removeClass('select2-container--open'); }); this.on('enable', function () { self.$container.removeClass('select2-container--disabled'); }); this.on('disable', function () { self.$container.addClass('select2-container--disabled'); }); this.on('blur', function () { self.$container.removeClass('select2-container--focus'); }); this.on('query', function (params) { if (!self.isOpen()) { self.trigger('open', {}); } this.dataAdapter.query(params, function (data) { self.trigger('results:all', { data: data, query: params }); }); }); this.on('query:append', function (params) { this.dataAdapter.query(params, function (data) { self.trigger('results:append', { data: data, query: params }); }); }); this.on('keypress', function (evt) { var key = evt.which; if (self.isOpen()) { if (key === KEYS.ESC || key === KEYS.TAB || (key === KEYS.UP && evt.altKey)) { self.close(evt); evt.preventDefault(); } else if (key === KEYS.ENTER) { self.trigger('results:select', {}); evt.preventDefault(); } else if ((key === KEYS.SPACE && evt.ctrlKey)) { self.trigger('results:toggle', {}); evt.preventDefault(); } else if (key === KEYS.UP) { self.trigger('results:previous', {}); evt.preventDefault(); } else if (key === KEYS.DOWN) { self.trigger('results:next', {}); evt.preventDefault(); } } else { if (key === KEYS.ENTER || key === KEYS.SPACE || (key === KEYS.DOWN && evt.altKey)) { self.open(); evt.preventDefault(); } } }); }; Select2.prototype._syncAttributes = function () { this.options.set('disabled', this.$element.prop('disabled')); if (this.isDisabled()) { if (this.isOpen()) { this.close(); } this.trigger('disable', {}); } else { this.trigger('enable', {}); } }; Select2.prototype._isChangeMutation = function (evt, mutations) { var changed = false; var self = this; // Ignore any mutation events raised for elements that aren't options or // optgroups. This handles the case when the select element is destroyed if ( evt && evt.target && ( evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP' ) ) { return; } if (!mutations) { // If mutation events aren't supported, then we can only assume that the // change affected the selections changed = true; } else if (mutations.addedNodes && mutations.addedNodes.length > 0) { for (var n = 0; n < mutations.addedNodes.length; n++) { var node = mutations.addedNodes[n]; if (node.selected) { changed = true; } } } else if (mutations.removedNodes && mutations.removedNodes.length > 0) { changed = true; } else if ($.isArray(mutations)) { $.each(mutations, function(evt, mutation) { if (self._isChangeMutation(evt, mutation)) { // We've found a change mutation. // Let's escape from the loop and continue changed = true; return false; } }); } return changed; }; Select2.prototype._syncSubtree = function (evt, mutations) { var changed = this._isChangeMutation(evt, mutations); var self = this; // Only re-pull the data if we think there is a change if (changed) { this.dataAdapter.current(function (currentData) { self.trigger('selection:update', { data: currentData }); }); } }; /** * Override the trigger method to automatically trigger pre-events when * there are events that can be prevented. */ Select2.prototype.trigger = function (name, args) { var actualTrigger = Select2.__super__.trigger; var preTriggerMap = { 'open': 'opening', 'close': 'closing', 'select': 'selecting', 'unselect': 'unselecting', 'clear': 'clearing' }; if (args === undefined) { args = {}; } if (name in preTriggerMap) { var preTriggerName = preTriggerMap[name]; var preTriggerArgs = { prevented: false, name: name, args: args }; actualTrigger.call(this, preTriggerName, preTriggerArgs); if (preTriggerArgs.prevented) { args.prevented = true; return; } } actualTrigger.call(this, name, args); }; Select2.prototype.toggleDropdown = function () { if (this.isDisabled()) { return; } if (this.isOpen()) { this.close(); } else { this.open(); } }; Select2.prototype.open = function () { if (this.isOpen()) { return; } if (this.isDisabled()) { return; } this.trigger('query', {}); }; Select2.prototype.close = function (evt) { if (!this.isOpen()) { return; } this.trigger('close', { originalEvent : evt }); }; /** * Helper method to abstract the "enabled" (not "disabled") state of this * object. * * @return {true} if the instance is not disabled. * @return {false} if the instance is disabled. */ Select2.prototype.isEnabled = function () { return !this.isDisabled(); }; /** * Helper method to abstract the "disabled" state of this object. * * @return {true} if the disabled option is true. * @return {false} if the disabled option is false. */ Select2.prototype.isDisabled = function () { return this.options.get('disabled'); }; Select2.prototype.isOpen = function () { return this.$container.hasClass('select2-container--open'); }; Select2.prototype.hasFocus = function () { return this.$container.hasClass('select2-container--focus'); }; Select2.prototype.focus = function (data) { // No need to re-trigger focus events if we are already focused if (this.hasFocus()) { return; } this.$container.addClass('select2-container--focus'); this.trigger('focus', {}); }; Select2.prototype.enable = function (args) { if (this.options.get('debug') && window.console && console.warn) { console.warn( 'Select2: The `select2("enable")` method has been deprecated and will' + ' be removed in later Select2 versions. Use $element.prop("disabled")' + ' instead.' ); } if (args == null || args.length === 0) { args = [true]; } var disabled = !args[0]; this.$element.prop('disabled', disabled); }; Select2.prototype.data = function () { if (this.options.get('debug') && arguments.length > 0 && window.console && console.warn) { console.warn( 'Select2: Data can no longer be set using `select2("data")`. You ' + 'should consider setting the value instead using `$element.val()`.' ); } var data = []; this.dataAdapter.current(function (currentData) { data = currentData; }); return data; }; Select2.prototype.val = function (args) { if (this.options.get('debug') && window.console && console.warn) { console.warn( 'Select2: The `select2("val")` method has been deprecated and will be' + ' removed in later Select2 versions. Use $element.val() instead.' ); } if (args == null || args.length === 0) { return this.$element.val(); } var newVal = args[0]; if ($.isArray(newVal)) { newVal = $.map(newVal, function (obj) { return obj.toString(); }); } this.$element.val(newVal).trigger('input').trigger('change'); }; Select2.prototype.destroy = function () { this.$container.remove(); if (this.$element[0].detachEvent) { this.$element[0].detachEvent('onpropertychange', this._syncA); } if (this._observer != null) { this._observer.disconnect(); this._observer = null; } else if (this.$element[0].removeEventListener) { this.$element[0] .removeEventListener('DOMAttrModified', this._syncA, false); this.$element[0] .removeEventListener('DOMNodeInserted', this._syncS, false); this.$element[0] .removeEventListener('DOMNodeRemoved', this._syncS, false); } this._syncA = null; this._syncS = null; this.$element.off('.select2'); this.$element.attr('tabindex', Utils.GetData(this.$element[0], 'old-tabindex')); this.$element.removeClass('select2-hidden-accessible'); this.$element.attr('aria-hidden', 'false'); Utils.RemoveData(this.$element[0]); this.$element.removeData('select2'); this.dataAdapter.destroy(); this.selection.destroy(); this.dropdown.destroy(); this.results.destroy(); this.dataAdapter = null; this.selection = null; this.dropdown = null; this.results = null; }; Select2.prototype.render = function () { var $container = $( '' + '' + '' + '' ); $container.attr('dir', this.options.get('dir')); this.$container = $container; this.$container.addClass('select2-container--' + this.options.get('theme')); Utils.StoreData($container[0], 'element', this.$element); return $container; }; return Select2; }); S2.define('jquery-mousewheel',[ 'jquery' ], function ($) { // Used to shim jQuery.mousewheel for non-full builds. return $; }); S2.define('jquery.select2',[ 'jquery', 'jquery-mousewheel', './select2/core', './select2/defaults', './select2/utils' ], function ($, _, Select2, Defaults, Utils) { if ($.fn.select2 == null) { // All methods that should return the element var thisMethods = ['open', 'close', 'destroy']; $.fn.select2 = function (options) { options = options || {}; if (typeof options === 'object') { this.each(function () { var instanceOptions = $.extend(true, {}, options); var instance = new Select2($(this), instanceOptions); }); return this; } else if (typeof options === 'string') { var ret; var args = Array.prototype.slice.call(arguments, 1); this.each(function () { var instance = Utils.GetData(this, 'select2'); if (instance == null && window.console && console.error) { console.error( 'The select2(\'' + options + '\') method was called on an ' + 'element that is not using Select2.' ); } ret = instance[options].apply(instance, args); }); // Check if we should be returning `this` if ($.inArray(options, thisMethods) > -1) { return this; } return ret; } else { throw new Error('Invalid arguments for Select2: ' + options); } }; } if ($.fn.select2.defaults == null) { $.fn.select2.defaults = Defaults; } return Select2; }); // Return the AMD loader configuration so it can be used outside of this file return { define: S2.define, require: S2.require }; }()); // Autoload the jQuery bindings // We know that all of the modules exist above this, so we're safe var select2 = S2.require('jquery.select2'); // Hold the AMD module references on the jQuery function that was just loaded // This allows Select2 to use the internal loader outside of this file, such // as in the language files. jQuery.fn.select2.amd = S2; // Return the Select2 instance for anyone who is importing it. return select2; })); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/suggest/bootstrap-suggest.js ================================================ /** * bootstrap-suggest-plugin - v0.1.29 * @description 这是一个基于 bootstrap 按钮式下拉菜单组件的搜索建议插件,必须使用于按钮式下拉菜单组件上。 * @author lzwme - https://lzw.me * @GitHub https://github.com/lzwme/bootstrap-suggest-plugin.git * @since 2019-11-18 09:30:06 */ (function(factory) { if (typeof define === "function" && define.amd) { define(["jquery"], factory); } else if (typeof exports === "object" && typeof module === "object") { factory(require("jquery")); } else if (window.jQuery) { factory(window.jQuery); } else { throw new Error("Not found jQuery."); } })(function($) { var VERSION = "VERSION_PLACEHOLDER"; var $window = $(window); var isIe = "ActiveXObject" in window; // 用于对 IE 的兼容判断 var inputLock; // 用于中文输入法输入时锁定搜索 // ie 下和 chrome 51 以上浏览器版本,出现滚动条时不计算 padding var chromeVer = navigator.userAgent.match(/Chrome\/(\d+)/); if (chromeVer) { chromeVer = +chromeVer[1]; } var notNeedCalcPadding = isIe || chromeVer > 51; // 一些常量 var BSSUGGEST = "bsSuggest"; var onDataRequestSuccess = "onDataRequestSuccess"; var DISABLED = "disabled"; var TRUE = true; var FALSE = false; function isUndefined(val) { return val === void 0; } /** * 错误处理 */ function handleError(e1, e2) { if (!window.console || !window.console.trace) { return; } console.trace(e1); if (e2) { console.trace(e2); } } /** * 获取当前 tr 列的关键字数据 */ function getPointKeyword($list) { return $list.data(); } /** * 设置或获取输入框的 alt 值 */ function setOrGetAlt($input, val) { return isUndefined(val) ? $input.attr("alt") : $input.attr("alt", val); } /** * 设置或获取输入框的 data-id 值 */ function setOrGetDataId($input, val) { return val !== void 0 ? $input.attr("data-id", val) : $input.attr("data-id"); } /** * 设置选中的值 */ function setValue($input, keywords, options) { if (!keywords || !keywords.key) { return; } var separator = options.separator || ",", inputValList, inputIdList, dataId = setOrGetDataId($input); if (options && options.multiWord) { inputValList = $input.val().split(separator); inputValList[inputValList.length - 1] = keywords.key; //多关键字检索支持设置id --- 存在 bug,不建议使用 if (!dataId) { inputIdList = [keywords.id]; } else { inputIdList = dataId.split(separator); inputIdList.push(keywords.id); } setOrGetDataId($input, inputIdList.join(separator)) .val(inputValList.join(separator)) .focus(); } else { setOrGetDataId($input, keywords.id || "") .val(keywords.key) .focus(); } $input .data("pre-val", $input.val()) .trigger("onSetSelectValue", [ keywords, (options.data.value || options._lastData.value)[keywords.index] ]); } /** * 调整选择菜单位置 * @param {Object} $input * @param {Object} $dropdownMenu * @param {Object} options */ function adjustDropMenuPos($input, $dropdownMenu, options) { if (!$dropdownMenu.is(":visible")) { return; } var $parent = $input.parent(); var parentHeight = $parent.height(); var parentWidth = $parent.width(); if (options.autoDropup) { setTimeout(function() { var offsetTop = $input.offset().top; var winScrollTop = $window.scrollTop(); var menuHeight = $dropdownMenu.height(); if ( // 自动判断菜单向上展开 $window.height() + winScrollTop - offsetTop < menuHeight && // 假如向下会撑长页面 offsetTop > menuHeight + winScrollTop // 而且向上不会撑到顶部 ) { $parent.addClass("dropup"); } else { $parent.removeClass("dropup"); } }, 10); } // 列表对齐方式 var dmcss = {}; if (options.listAlign === "left") { dmcss = { left: $input.siblings("div").width() - parentWidth, right: "auto" }; } else if (options.listAlign === "right") { dmcss = { left: "auto", right: 0 }; } // ie 下,不显示按钮时的 top/bottom if (isIe && !options.showBtn) { if (!$parent.hasClass("dropup")) { dmcss.top = parentHeight; dmcss.bottom = "auto"; } else { dmcss.top = "auto"; dmcss.bottom = parentHeight; } } // 是否自动最小宽度 if (!options.autoMinWidth) { dmcss.minWidth = parentWidth; } /* else { dmcss['width'] = 'auto'; }*/ $dropdownMenu.css(dmcss); return $input; } /** * 设置输入框背景色 * 当设置了 indexId,而输入框的 data-id 为空时,输入框加载警告色 */ function setBackground($input, options) { var inputbg, bg, warnbg; if ((options.indexId === -1 && !options.idField) || options.multiWord) { return $input; } bg = options.inputBgColor; warnbg = options.inputWarnColor; var curVal = $input.val(); var preVal = $input.data("pre-val"); if (setOrGetDataId($input) || !curVal) { $input.css("background", bg || ""); if (!curVal && preVal) { $input.trigger("onUnsetSelectValue").data("pre-val", ""); } return $input; } inputbg = $input .css("backgroundColor") .replace(/ /g, "") .split(",", 3) .join(","); // 自由输入的内容,设置背景色 if (!~warnbg.indexOf(inputbg)) { $input .trigger("onUnsetSelectValue") // 触发取消data-id事件 .data("pre-val", "") .css("background", warnbg); } return $input; } /** * 调整滑动条 */ function adjustScroll($input, $dropdownMenu, options) { // 控制滑动条 var $hover = $input.parent().find("tbody tr." + options.listHoverCSS), pos, maxHeight; if ($hover.length) { pos = ($hover.index() + 3) * $hover.height(); maxHeight = +$dropdownMenu.css("maxHeight").replace("px", ""); if (pos > maxHeight || $dropdownMenu.scrollTop() > maxHeight) { pos = pos - maxHeight; } else { pos = 0; } $dropdownMenu.scrollTop(pos); } } /** * 解除所有列表 hover 样式 */ function unHoverAll($dropdownMenu, options) { $dropdownMenu .find("tr." + options.listHoverCSS) .removeClass(options.listHoverCSS); } /** * 验证 $input 对象是否符合条件 * 1. 必须为 bootstrap 下拉式菜单 * 2. 必须未初始化过 */ function checkInput($input, $dropdownMenu, options) { if ( !$dropdownMenu.length || // 过滤非 bootstrap 下拉式菜单对象 $input.data(BSSUGGEST) // 是否已经初始化的检测 ) { return FALSE; } $input.data(BSSUGGEST, { options: options }); return TRUE; } /** * 数据格式检测 * 检测 ajax 返回成功数据或 data 参数数据是否有效 * data 格式:{"value": [{}, {}...]} */ function checkData(data) { var isEmpty = TRUE, o; for (o in data) { if (o === "value") { isEmpty = FALSE; break; } } if (isEmpty) { handleError("返回数据格式错误!"); return FALSE; } if (!data.value.length) { // handleError('返回数据为空!'); return FALSE; } return data; } /** * 判断字段名是否在 options.effectiveFields 配置项中 * @param {String} field 要判断的字段名 * @param {Object} options * @return {Boolean} effectiveFields 为空时始终返回 true */ function inEffectiveFields(field, options) { var effectiveFields = options.effectiveFields; return !( field === "__index" || (effectiveFields.length && !~$.inArray(field, effectiveFields)) ); } /** * 判断字段名是否在 options.searchFields 搜索字段配置中 */ function inSearchFields(field, options) { return ~$.inArray(field, options.searchFields); } /** * 通过下拉菜单显示提示文案 */ function showTip(tip, $input, $dropdownMenu, options) { $dropdownMenu .html('
                ' + tip + "
                ") .show(); adjustDropMenuPos($input, $dropdownMenu, options); } /** * 显示下拉列表 */ function showDropMenu($input, options) { var $dropdownMenu = $input.parent().find("ul:eq(0)"); if (!$dropdownMenu.is(":visible")) { // $dropdownMenu.css('display', 'block'); $dropdownMenu.show(); $input.trigger("onShowDropdown", [options ? options.data.value : []]); } } /** * 隐藏下拉列表 */ function hideDropMenu($input, options) { var $dropdownMenu = $input.parent().find("ul:eq(0)"); if ($dropdownMenu.is(":visible")) { // $dropdownMenu.css('display', ''); $dropdownMenu.hide(); $input.trigger("onHideDropdown", [options ? options.data.value : []]); } } /** * 下拉列表刷新 * 作为 fnGetData 的 callback 函数调用 */ function refreshDropMenu($input, data, options) { var $dropdownMenu = $input.parent().find("ul:eq(0)"), len, i, field, index = 0, tds, html = [ '' ], idValue, keyValue; // 作为输入框 data-id 和内容的字段值 var dataList = data.value; if (!data || !(len = dataList.length)) { if (options.emptyTip) { showTip(options.emptyTip, $input, $dropdownMenu, options); } else { $dropdownMenu.empty(); hideDropMenu($input, options); } return $input; } // 相同数据,不用继续渲染了 if ( options._lastData && JSON.stringify(options._lastData) === JSON.stringify(data) && $dropdownMenu.find("tr").length === len ) { showDropMenu($input, options); return adjustDropMenuPos($input, $dropdownMenu, options); } options._lastData = data; /** 显示于列表中的字段 */ var columns = options.effectiveFields.length ? options.effectiveFields : $.map(dataList[0], function(val, key) { return key; }); // 生成表头 if (options.showHeader) { html.push(""); $.each(columns, function(index, field) { if (!inEffectiveFields(field, options)) return; html.push( "" ); index++; }); html.push(""); } html.push(""); // console.log(data, len); // 按列加数据 var dataI; var maxOptionCount = Math.min(options.maxOptionCount, len); for (i = 0; i < maxOptionCount; i++) { index = 0; tds = []; dataI = dataList[i]; idValue = dataI[options.idField]; keyValue = dataI[options.keyField]; for (field in dataI) { // 标记作为 value 和 作为 id 的值 if (isUndefined(keyValue) && options.indexKey === index) { keyValue = dataI[field]; } if (isUndefined(idValue) && options.indexId === index) { idValue = dataI[field]; } index++; } $.each(columns, function(index, field) { // 列表中只显示有效的字段 if (inEffectiveFields(field, options)) { tds.push('"); } }); html.push( '', tds.join(""), "" ); } html.push("
                ", options.effectiveFieldsAlias[field] || field, index === 0 ? "(" + len + ")" : "", // 表头第一列记录总数 "
                ', dataI[field], "
                "); $dropdownMenu.html(html.join("")); showDropMenu($input, options); //.show(); // scrollbar 存在时,延时到动画结束时调整 padding setTimeout(function() { if (notNeedCalcPadding) { return; } var $table = $dropdownMenu.find("table:eq(0)"), pdr = 0, mgb = 0; if ( $dropdownMenu.height() < $table.height() && +$dropdownMenu.css("minWidth").replace("px", "") < $dropdownMenu.width() ) { pdr = 18; mgb = 20; } $dropdownMenu.css("paddingRight", pdr); $table.css("marginBottom", mgb); }, 301); adjustDropMenuPos($input, $dropdownMenu, options); return $input; } /** * ajax 获取数据 * @param {Object} options * @return {Object} $.Deferred */ function ajax(options, keyword) { keyword = keyword || ""; var preAjax = options._preAjax; if (preAjax && preAjax.abort && preAjax.readyState !== 4) { // console.log('abort pre ajax'); preAjax.abort(); } var ajaxParam = { type: "GET", dataType: options.jsonp ? "jsonp" : "json", timeout: 5000 }; // jsonp if (options.jsonp) { ajaxParam.jsonp = options.jsonp; } // 自定义 ajax 请求参数生成方法 var adjustAjaxParam, fnAdjustAjaxParam = options.fnAdjustAjaxParam; if ($.isFunction(fnAdjustAjaxParam)) { adjustAjaxParam = fnAdjustAjaxParam(keyword, options); // options.fnAdjustAjaxParam 返回false,则终止 ajax 请求 if (FALSE === adjustAjaxParam) { return; } $.extend(ajaxParam, adjustAjaxParam); } // url 调整 ajaxParam.url = (function() { if (!keyword || ajaxParam.data) { return ajaxParam.url || options.url; } var type = "?"; if (/=$/.test(options.url)) { type = ""; } else if (/\?/.test(options.url)) { type = "&"; } return options.url + type + encodeURIComponent(keyword); })(); return (options._preAjax = $.ajax(ajaxParam) .done(function(result) { options.data = options.fnProcessData(result); }) .fail(function(err) { if (options.fnAjaxFail) { options.fnAjaxFail(err, options); } })); } /** * 检测 keyword 与 value 是否存在互相包含 * @param {String} keyword 用户输入的关键字 * @param {String} key 匹配字段的 key * @param {String} value key 字段对应的值 * @param {Object} options * @return {Boolean} 包含/不包含 */ function isInWord(keyword, key, value, options) { value = $.trim(value); if (options.ignorecase) { keyword = keyword.toLocaleLowerCase(); value = value.toLocaleLowerCase(); } return ( value && (inEffectiveFields(key, options) || inSearchFields(key, options)) && // 必须在有效的搜索字段中 (~value.indexOf(keyword) || // 匹配值包含关键字 (options.twoWayMatch && ~keyword.indexOf(value))) // 关键字包含匹配值 ); } /** * 通过 ajax 或 json 参数获取数据 */ function getData(keyword, $input, callback, options) { var data, validData, filterData = { value: [] }, i, key, len, fnPreprocessKeyword = options.fnPreprocessKeyword; keyword = keyword || ""; // 获取数据前对关键字预处理方法 if ($.isFunction(fnPreprocessKeyword)) { keyword = fnPreprocessKeyword(keyword, options); } // 给了url参数,则从服务器 ajax 请求 // console.log(options.url + keyword); if (options.url) { var timer; if (options.searchingTip) { timer = setTimeout(function() { showTip( options.searchingTip, $input, $input.parent().find("ul"), options ); }, 600); } ajax(options, keyword) .done(function(result) { callback($input, options.data, options); // 为 refreshDropMenu $input.trigger(onDataRequestSuccess, result); if (options.getDataMethod === "firstByUrl") { options.url = null; } }) .always(function() { timer && clearTimeout(timer); }); } else { // 没有给出 url 参数,则从 data 参数获取 data = options.data; validData = checkData(data); // 本地的 data 数据,则在本地过滤 if (validData) { if (keyword) { // 输入不为空时则进行匹配 len = data.value.length; for (i = 0; i < len; i++) { for (key in data.value[i]) { if ( data.value[i][key] && isInWord(keyword, key, data.value[i][key] + "", options) ) { filterData.value.push(data.value[i]); filterData.value[filterData.value.length - 1].__index = i; break; } } } } else { filterData = data; } } callback($input, filterData, options); } // else } /** * 数据处理 * url 获取数据时,对数据的处理,作为 fnGetData 之后的回调处理 */ function processData(data) { return checkData(data); } /** * 取得 clearable 清除按钮 */ function getIClear($input, options) { var $iClear = $input.prev("i.clearable"); // 是否可清除已输入的内容(添加清除按钮) if (options.clearable && !$iClear.length) { $iClear = $( '' ).prependTo($input.parent()); } return $iClear .css({ position: "absolute", top: "calc(50% - 6px)", transform: "rotate(45deg)", // right: options.showBtn ? Math.max($input.next('.input-group-btn').width(), 33) + 2 : 12, zIndex: 4, cursor: "pointer", width: "14px", lineHeight: "14px", textAlign: "center", fontSize: 12 }) .hide(); } /** * 默认的配置选项 * @type {Object} */ var defaultOptions = { url: null, // 请求数据的 URL 地址 jsonp: null, // 设置此参数名,将开启jsonp功能,否则使用json数据结构 data: { value: [] }, // 提示所用的数据,注意格式 indexId: 0, // 每组数据的第几个数据,作为input输入框的 data-id,设为 -1 且 idField 为空则不设置此值 indexKey: 0, // 每组数据的第几个数据,作为input输入框的内容 idField: "", // 每组数据的哪个字段作为 data-id,优先级高于 indexId 设置(推荐) keyField: "", // 每组数据的哪个字段作为输入框内容,优先级高于 indexKey 设置(推荐) /* 搜索相关 */ autoSelect: TRUE, // 键盘向上/下方向键时,是否自动选择值 allowNoKeyword: TRUE, // 是否允许无关键字时请求数据 getDataMethod: "firstByUrl", // 获取数据的方式,url:一直从url请求;data:从 options.data 获取;firstByUrl:第一次从Url获取全部数据,之后从options.data获取 delayUntilKeyup: FALSE, // 获取数据的方式 为 firstByUrl 时,是否延迟到有输入时才请求数据 ignorecase: FALSE, // 前端搜索匹配时,是否忽略大小写 effectiveFields: [], // 有效显示于列表中的字段,非有效字段都会过滤,默认全部有效。 effectiveFieldsAlias: {}, // 有效字段的别名对象,用于 header 的显示 searchFields: [], // 有效搜索字段,从前端搜索过滤数据时使用,但不一定显示在列表中。effectiveFields 配置字段也会用于搜索过滤 twoWayMatch: TRUE, // 是否双向匹配搜索。为 true 即输入关键字包含或包含于匹配字段均认为匹配成功,为 false 则输入关键字包含于匹配字段认为匹配成功 multiWord: FALSE, // 以分隔符号分割的多关键字支持 separator: ",", // 多关键字支持时的分隔符,默认为半角逗号 delay: 300, // 搜索触发的延时时间间隔,单位毫秒 emptyTip: "", // 查询为空时显示的内容,可为 html searchingTip: "搜索中...", // ajax 搜索时显示的提示内容,当搜索时间较长时给出正在搜索的提示 hideOnSelect: FALSE, // 鼠标从列表单击选择了值时,是否隐藏选择列表 maxOptionCount: 200, // 选择列表最多显示的可选项数量,默认为 200 /* UI */ autoDropup: FALSE, // 选择菜单是否自动判断向上展开。设为 true,则当下拉菜单高度超过窗体,且向上方向不会被窗体覆盖,则选择菜单向上弹出 autoMinWidth: FALSE, // 是否自动最小宽度,设为 false 则最小宽度不小于输入框宽度 showHeader: FALSE, // 是否显示选择列表的 header。为 true 时,有效字段大于一列则显示表头 showBtn: TRUE, // 是否显示下拉按钮 inputBgColor: "", // 输入框背景色,当与容器背景色不同时,可能需要该项的配置 inputWarnColor: "rgba(255,0,0,.1)", // 输入框内容不是下拉列表选择时的警告色 listStyle: { "padding-top": 0, "max-height": "375px", "max-width": "800px", overflow: "auto", width: "auto", transition: "0.3s", "-webkit-transition": "0.3s", "-moz-transition": "0.3s", "-o-transition": "0.3s", "word-break": "keep-all", "white-space": "nowrap" }, // 列表的样式控制 listAlign: "left", // 提示列表对齐位置,left/right/auto listHoverStyle: "background: #07d; color:#fff", // 提示框列表鼠标悬浮的样式 listHoverCSS: "jhover", // 提示框列表鼠标悬浮的样式名称 clearable: FALSE, // 是否可清除已输入的内容 /* key */ keyLeft: 37, // 向左方向键,不同的操作系统可能会有差别,则自行定义 keyUp: 38, // 向上方向键 keyRight: 39, // 向右方向键 keyDown: 40, // 向下方向键 keyEnter: 13, // 回车键 /* methods */ fnProcessData: processData, // 格式化数据的方法,返回数据格式参考 data 参数 fnGetData: getData, // 获取数据的方法,无特殊需求一般不作设置 fnAdjustAjaxParam: null, // 调整 ajax 请求参数方法,用于更多的请求配置需求。如对请求关键字作进一步处理、修改超时时间等 fnPreprocessKeyword: null, // 搜索过滤数据前,对输入关键字作进一步处理方法。注意,应返回字符串 fnAjaxFail: null // ajax 失败时回调方法 }; var methods = { init: function(options) { // 参数设置 var self = this; options = options || {}; // 默认配置有效显示字段多于一个,则显示列表表头,否则不显示 if ( isUndefined(options.showHeader) && options.effectiveFields && options.effectiveFields.length > 1 ) { options.showHeader = TRUE; } options = $.extend(TRUE, {}, defaultOptions, options); // 旧的方法兼容 if (options.processData) { options.fnProcessData = options.processData; } if (options.getData) { options.fnGetData = options.getData; } if ( options.getDataMethod === "firstByUrl" && options.url && !options.delayUntilKeyup ) { ajax(options).done(function(result) { options.url = null; self.trigger(onDataRequestSuccess, result); }); } // 鼠标滑动到条目样式 if (!$("#" + BSSUGGEST).length) { $("head:eq(0)").append( '" ); } return self.each(function() { var $input = $(this), $parent = $input.parent(), $iClear = getIClear($input, options), isMouseenterMenu, keyupTimer, // keyup 与 input 事件延时定时器 $dropdownMenu = $parent.find("ul:eq(0)"); // 兼容 bs4 $dropdownMenu.parent().css("position", "relative"); // 验证输入框对象是否符合条件 if (!checkInput($input, $dropdownMenu, options)) { console.warn( "不是一个标准的 bootstrap 下拉式菜单或已初始化:", $input ); return; } // 是否显示 button 按钮 if (!options.showBtn) { $input.css("borderRadius", 4); $parent .css("width", "100%") .find(".btn:eq(0)") .hide(); } // 移除 disabled 类,并禁用自动完成 $input .removeClass(DISABLED) .prop(DISABLED, FALSE) .attr("autocomplete", "off"); // dropdown-menu 增加修饰 $dropdownMenu.css(options.listStyle); // 默认背景色 if (!options.inputBgColor) { options.inputBgColor = $input.css("backgroundColor"); } // 开始事件处理 $input .on("keydown.bs", function(event) { var currentList, tipsKeyword; // 提示列表上被选中的关键字 // 当提示层显示时才对键盘事件处理 if (!$dropdownMenu.is(":visible")) { setOrGetDataId($input, ""); return; } currentList = $dropdownMenu.find("." + options.listHoverCSS); tipsKeyword = ""; // 提示列表上被选中的关键字 unHoverAll($dropdownMenu, options); if (event.keyCode === options.keyDown) { // 如果按的是向下方向键 if (!currentList.length) { // 如果提示列表没有一个被选中,则将列表第一个选中 tipsKeyword = getPointKeyword( $dropdownMenu.find("tbody tr:first").mouseover() ); } else if (!currentList.next().length) { // 如果是最后一个被选中,则取消选中,即可认为是输入框被选中,并恢复输入的值 if (options.autoSelect) { setOrGetDataId($input, "").val(setOrGetAlt($input)); } } else { // 选中下一行 tipsKeyword = getPointKeyword(currentList.next().mouseover()); } // 控制滑动条 adjustScroll($input, $dropdownMenu, options); if (!options.autoSelect) { return; } } else if (event.keyCode === options.keyUp) { // 如果按的是向上方向键 if (!currentList.length) { tipsKeyword = getPointKeyword( $dropdownMenu.find("tbody tr:last").mouseover() ); } else if (!currentList.prev().length) { if (options.autoSelect) { setOrGetDataId($input, "").val(setOrGetAlt($input)); } } else { // 选中前一行 tipsKeyword = getPointKeyword(currentList.prev().mouseover()); } // 控制滑动条 adjustScroll($input, $dropdownMenu, options); if (!options.autoSelect) { return; } } else if (event.keyCode === options.keyEnter) { tipsKeyword = getPointKeyword(currentList); hideDropMenu($input, options); } else { setOrGetDataId($input, ""); } // 设置值 tipsKeyword // console.log(tipsKeyword); setValue($input, tipsKeyword, options); }) .on("compositionstart.bs", function(event) { // 中文输入开始,锁定 // console.log('compositionstart'); inputLock = TRUE; }) .on("compositionend.bs", function(event) { // 中文输入结束,解除锁定 // console.log('compositionend'); inputLock = FALSE; }) .on("keyup.bs input.bs paste.bs", function(event) { var word; if (event.keyCode) { setBackground($input, options); } // 如果弹起的键是回车、向上或向下方向键则返回 if ( ~$.inArray(event.keyCode, [ options.keyDown, options.keyUp, options.keyEnter ]) ) { $input.val($input.val()); // 让鼠标输入跳到最后 return; } clearTimeout(keyupTimer); keyupTimer = setTimeout(function() { // console.log('input keyup', event); // 锁定状态,返回 if (inputLock) { return; } word = $input.val(); // 若输入框值没有改变则返回 if ($.trim(word) && word === setOrGetAlt($input)) { return; } // 当按下键之前记录输入框值,以方便查看键弹起时值有没有变 setOrGetAlt($input, word); if (options.multiWord) { word = word.split(options.separator).reverse()[0]; } // 是否允许空数据查询 if (!word.length && !options.allowNoKeyword) { return; } options.fnGetData($.trim(word), $input, refreshDropMenu, options); }, options.delay || 300); }) .on("focus.bs", function() { // console.log('input focus'); adjustDropMenuPos($input, $dropdownMenu, options); }) .on("blur.bs", function() { if (!isMouseenterMenu) { // 不是进入下拉列表状态,则隐藏列表 hideDropMenu($input, options); inputLock = true; setTimeout(function() { inputLock = FALSE; }); } }) .on("click.bs", function() { // console.log('input click'); var word = $input.val(); if ( $.trim(word) && word === setOrGetAlt($input) && $dropdownMenu.find("table tr").length ) { return showDropMenu($input, options); } if ($dropdownMenu.is(":visible")) { return; } if (options.multiWord) { word = word.split(options.separator).reverse()[0]; } // 是否允许空数据查询 if (!word.length && !options.allowNoKeyword) { return; } // console.log('word', word); options.fnGetData($.trim(word), $input, refreshDropMenu, options); }); // 下拉按钮点击时 $parent .find(".btn:eq(0)") .attr("data-toggle", "") .click(function() { if (!$dropdownMenu.is(":visible")) { if (options.url) { $input.click().focus(); if (!$dropdownMenu.find("tr").length) { return FALSE; } } else { // 不以 keyword 作为过滤,展示所有的数据 refreshDropMenu($input, options.data, options); } showDropMenu($input, options); } else { hideDropMenu($input, options); } return FALSE; }); // 列表中滑动时,输入框失去焦点 $dropdownMenu .mouseenter(function() { // console.log('mouseenter') isMouseenterMenu = 1; $input.blur(); }) .mouseleave(function() { // console.log('mouseleave') isMouseenterMenu = 0; $input.focus(); }) .on("mouseenter", "tbody tr", function() { // 行上的移动事件 unHoverAll($dropdownMenu, options); $(this).addClass(options.listHoverCSS); return FALSE; // 阻止冒泡 }) .on("mousedown", "tbody tr", function() { var keywords = getPointKeyword($(this)); setValue($input, keywords, options); setOrGetAlt($input, keywords.key); setBackground($input, options); if (options.hideOnSelect) { hideDropMenu($input, options); } }); // 存在清空按钮 if ($iClear.length) { $iClear.click(function() { setOrGetDataId($input, "").val(""); setBackground($input, options); }); $parent .mouseenter(function() { if (!$input.prop(DISABLED)) { $iClear .css( "right", options.showBtn ? Math.max($input.next().width(), 33) + 2 : 12 ) .show(); } }) .mouseleave(function() { $iClear.hide(); }); } }); }, show: function() { return this.each(function() { $(this).click(); }); }, hide: function() { return this.each(function() { hideDropMenu($(this)); }); }, disable: function() { return this.each(function() { $(this) .attr(DISABLED, TRUE) .parent() .find(".btn:eq(0)") .prop(DISABLED, TRUE); }); }, enable: function() { return this.each(function() { $(this) .attr(DISABLED, FALSE) .parent() .find(".btn:eq(0)") .prop(DISABLED, FALSE); }); }, destroy: function() { return this.each(function() { var evNameList = "click.bs keydown.bs compositionstart.bs compositionend.bs keyup.bs input.bs paste.bs focus.bs click.bs"; $(this) .off(evNameList) .removeData(BSSUGGEST) .removeAttr("style") .parent() .find(".btn:eq(0)") .off() .show() .attr("data-toggle", "dropdown") .prop(DISABLED, FALSE) // .addClass(DISABLED); .next() .css("display", "") .off(); }); }, version: function() { return VERSION; } }; $.fn[BSSUGGEST] = function(options) { // 方法判断 if (typeof options === "string" && methods[options]) { var inited = TRUE; this.each(function() { if (!$(this).data(BSSUGGEST)) { return (inited = FALSE); } }); // 只要有一个未初始化,则全部都不执行方法,除非是 init 或 version if (!inited && "init" !== options && "version" !== options) { return this; } // 如果是方法,则参数第一个为函数名,从第二个开始为函数参数 return methods[options].apply(this, [].slice.call(arguments, 1)); } else { // 调用初始化方法 return methods.init.apply(this, arguments); } }; }); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote-zh-CN.js ================================================ (function ($) { $.extend($.summernote.lang, { 'zh-CN': { font: { bold: '粗体', italic: '斜体', underline: '下划线', clear: '清除格式', height: '行高', name: '字体', strikethrough: '删除线', subscript: '下标', superscript: '上标', size: '字号' }, image: { image: '图片', insert: '插入图片', resizeFull: '缩放至 100%', resizeHalf: '缩放至 50%', resizeQuarter: '缩放至 25%', floatLeft: '靠左浮动', floatRight: '靠右浮动', floatNone: '取消浮动', shapeRounded: '形状: 圆角', shapeCircle: '形状: 圆', shapeThumbnail: '形状: 缩略图', shapeNone: '形状: 无', dragImageHere: '将图片拖拽至此处', dropImage: '拖拽图片或文本', selectFromFiles: '从本地上传', maximumFileSize: '文件大小最大值', maximumFileSizeError: '文件大小超出最大值。', url: '图片地址', remove: '移除图片', original: '原始图片' }, video: { video: '视频', videoLink: '视频链接', insert: '插入视频', url: '视频地址', providers: '(优酷, 腾讯, Instagram, DailyMotion, Youtube等)' }, link: { link: '链接', insert: '插入链接', unlink: '去除链接', edit: '编辑链接', textToDisplay: '显示文本', url: '链接地址', openInNewWindow: '在新窗口打开' }, table: { table: '表格', addRowAbove: '在上方插入行', addRowBelow: '在下方插入行', addColLeft: '在左侧插入列', addColRight: '在右侧插入列', delRow: '删除行', delCol: '删除列', delTable: '删除表格' }, hr: { insert: '水平线' }, style: { style: '样式', p: '普通', blockquote: '引用', pre: '代码', h1: '标题 1', h2: '标题 2', h3: '标题 3', h4: '标题 4', h5: '标题 5', h6: '标题 6' }, lists: { unordered: '无序列表', ordered: '有序列表' }, options: { help: '帮助', fullscreen: '全屏', codeview: '源代码' }, paragraph: { paragraph: '段落', outdent: '减少缩进', indent: '增加缩进', left: '左对齐', center: '居中对齐', right: '右对齐', justify: '两端对齐' }, color: { recent: '最近使用', more: '更多', background: '背景', foreground: '前景', transparent: '透明', setTransparent: '透明', reset: '重置', resetToDefault: '默认' }, shortcut: { shortcuts: '快捷键', close: '关闭', textFormatting: '文本格式', action: '动作', paragraphFormatting: '段落格式', documentStyle: '文档样式', extraKeys: '额外按键' }, help: { insertParagraph: '插入段落', undo: '撤销', redo: '重做', tab: '增加缩进', untab: '减少缩进', bold: '粗体', italic: '斜体', underline: '下划线', strikethrough: '删除线', removeFormat: '清除格式', justifyLeft: '左对齐', justifyCenter: '居中对齐', justifyRight: '右对齐', justifyFull: '两端对齐', insertUnorderedList: '无序列表', insertOrderedList: '有序列表', outdent: '减少缩进', indent: '增加缩进', formatPara: '设置选中内容样式为 普通', formatH1: '设置选中内容样式为 标题1', formatH2: '设置选中内容样式为 标题2', formatH3: '设置选中内容样式为 标题3', formatH4: '设置选中内容样式为 标题4', formatH5: '设置选中内容样式为 标题5', formatH6: '设置选中内容样式为 标题6', insertHorizontalRule: '插入水平线', 'linkDialog.show': '显示链接对话框' }, history: { undo: '撤销', redo: '重做' }, specialChar: { specialChar: '特殊字符', select: '选取特殊字符' } } }); })(jQuery); ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote.css ================================================ /*! * * Super simple wysiwyg editor v0.8.18 * https://summernote.org * * * Copyright 2013- Alan Hong. and other contributors * summernote may be freely distributed under the MIT license. * * Date: 2020-05-20T18:09Z * */ @font-face{font-family:"summernote";font-style:normal;font-weight:400;font-display:auto;src:url(font/summernote.eot);src:url(font/summernote.eot?#iefix) format("embedded-opentype"),url(font/summernote.woff2) format("woff2"),url(font/summernote.woff) format("woff"),url(font/summernote.ttf) format("truetype")}[class^=note-icon]:before,[class*=" note-icon"]:before{display:inline-block;font-family:summernote;font-style:normal;font-size:inherit;text-decoration:inherit;text-rendering:auto;text-transform:none;vertical-align:middle;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;speak:none}.note-icon-fw{text-align:center;width:1.25em}.note-icon-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.note-icon-pull-left{float:left}.note-icon-pull-right{float:right}.note-icon.note-icon-pull-left{margin-right:.3em}.note-icon.note-icon-pull-right{margin-left:.3em}.note-icon-align::before{content:""}.note-icon-align-center::before{content:""}.note-icon-align-indent::before{content:""}.note-icon-align-justify::before{content:""}.note-icon-align-left::before{content:""}.note-icon-align-outdent::before{content:""}.note-icon-align-right::before{content:""}.note-icon-arrow-circle-down::before{content:""}.note-icon-arrow-circle-left::before{content:""}.note-icon-arrow-circle-right::before{content:""}.note-icon-arrow-circle-up::before{content:""}.note-icon-arrows-alt::before{content:""}.note-icon-arrows-h::before{content:""}.note-icon-arrows-v::before{content:""}.note-icon-bold::before{content:""}.note-icon-caret::before{content:""}.note-icon-chain-broken::before{content:""}.note-icon-circle::before{content:""}.note-icon-close::before{content:""}.note-icon-code::before{content:""}.note-icon-col-after::before{content:""}.note-icon-col-before::before{content:""}.note-icon-col-remove::before{content:""}.note-icon-eraser::before{content:""}.note-icon-float-left::before{content:""}.note-icon-float-none::before{content:""}.note-icon-float-right::before{content:""}.note-icon-font::before{content:""}.note-icon-frame::before{content:""}.note-icon-italic::before{content:""}.note-icon-link::before{content:""}.note-icon-magic::before{content:""}.note-icon-menu-check::before{content:""}.note-icon-minus::before{content:""}.note-icon-orderedlist::before{content:""}.note-icon-pencil::before{content:""}.note-icon-picture::before{content:""}.note-icon-question::before{content:""}.note-icon-redo::before{content:""}.note-icon-rollback::before{content:""}.note-icon-row-above::before{content:""}.note-icon-row-below::before{content:""}.note-icon-row-remove::before{content:""}.note-icon-special-character::before{content:""}.note-icon-square::before{content:""}.note-icon-strikethrough::before{content:""}.note-icon-subscript::before{content:""}.note-icon-summernote::before{content:""}.note-icon-superscript::before{content:""}.note-icon-table::before{content:""}.note-icon-text-height::before{content:""}.note-icon-trash::before{content:""}.note-icon-underline::before{content:""}.note-icon-undo::before{content:""}.note-icon-unorderedlist::before{content:""}.note-icon-video::before{content:""}.note-editor{position:relative}.note-editor .note-dropzone{position:absolute;display:none;z-index:100;color:#87cefa;background-color:#fff;opacity:.95}.note-editor .note-dropzone .note-dropzone-message{display:table-cell;vertical-align:middle;text-align:center;font-size:28px;font-weight:700}.note-editor .note-dropzone.hover{color:#098ddf}.note-editor.dragover .note-dropzone{display:table}.note-editor .note-editing-area{position:relative}.note-editor .note-editing-area .note-editable{outline:none}.note-editor .note-editing-area .note-editable sup{vertical-align:super}.note-editor .note-editing-area .note-editable sub{vertical-align:sub}.note-editor .note-editing-area .note-editable img.note-float-left{margin-right:10px}.note-editor .note-editing-area .note-editable img.note-float-right{margin-left:10px}.note-editor.note-frame,.note-editor.note-airframe{border:1px solid #00000032}.note-editor.note-frame.codeview .note-editing-area .note-editable,.note-editor.note-airframe.codeview .note-editing-area .note-editable{display:none}.note-editor.note-frame.codeview .note-editing-area .note-codable,.note-editor.note-airframe.codeview .note-editing-area .note-codable{display:block}.note-editor.note-frame .note-editing-area,.note-editor.note-airframe .note-editing-area{overflow:hidden}.note-editor.note-frame .note-editing-area .note-editable,.note-editor.note-airframe .note-editing-area .note-editable{padding:10px;overflow:auto;word-wrap:break-word}.note-editor.note-frame .note-editing-area .note-editable[contenteditable=false],.note-editor.note-airframe .note-editing-area .note-editable[contenteditable=false]{background-color:#8080801d}.note-editor.note-frame .note-editing-area .note-codable,.note-editor.note-airframe .note-editing-area .note-codable{display:none;width:100%;padding:10px;border:none;box-shadow:none;font-family:Menlo,Monaco,monospace,sans-serif;font-size:14px;color:#ccc;background-color:#222;resize:none;outline:none;-ms-box-sizing:border-box;box-sizing:border-box;border-radius:0;margin-bottom:0}.note-editor.note-frame.fullscreen,.note-editor.note-airframe.fullscreen{position:fixed;top:0;left:0;width:100% !important;z-index:1050}.note-editor.note-frame.fullscreen .note-resizebar,.note-editor.note-airframe.fullscreen .note-resizebar{display:none}.note-editor.note-frame .note-status-output,.note-editor.note-airframe .note-status-output{display:block;width:100%;font-size:14px;line-height:1.42857143;height:20px;margin-bottom:0;color:#000;border:0;border-top:1px solid #e2e2e2}.note-editor.note-frame .note-status-output:empty,.note-editor.note-airframe .note-status-output:empty{height:0;border-top:0 solid transparent}.note-editor.note-frame .note-status-output .pull-right,.note-editor.note-airframe .note-status-output .pull-right{float:right !important}.note-editor.note-frame .note-status-output .text-muted,.note-editor.note-airframe .note-status-output .text-muted{color:#777}.note-editor.note-frame .note-status-output .text-primary,.note-editor.note-airframe .note-status-output .text-primary{color:#286090}.note-editor.note-frame .note-status-output .text-success,.note-editor.note-airframe .note-status-output .text-success{color:#3c763d}.note-editor.note-frame .note-status-output .text-info,.note-editor.note-airframe .note-status-output .text-info{color:#31708f}.note-editor.note-frame .note-status-output .text-warning,.note-editor.note-airframe .note-status-output .text-warning{color:#8a6d3b}.note-editor.note-frame .note-status-output .text-danger,.note-editor.note-airframe .note-status-output .text-danger{color:#a94442}.note-editor.note-frame .note-status-output .alert,.note-editor.note-airframe .note-status-output .alert{margin:-7px 0 0 0;padding:7px 10px 2px 10px;border-radius:0;color:#000;background-color:#f5f5f5}.note-editor.note-frame .note-status-output .alert .note-icon,.note-editor.note-airframe .note-status-output .alert .note-icon{margin-right:5px}.note-editor.note-frame .note-status-output .alert-success,.note-editor.note-airframe .note-status-output .alert-success{color:#3c763d !important;background-color:#dff0d8 !important}.note-editor.note-frame .note-status-output .alert-info,.note-editor.note-airframe .note-status-output .alert-info{color:#31708f !important;background-color:#d9edf7 !important}.note-editor.note-frame .note-status-output .alert-warning,.note-editor.note-airframe .note-status-output .alert-warning{color:#8a6d3b !important;background-color:#fcf8e3 !important}.note-editor.note-frame .note-status-output .alert-danger,.note-editor.note-airframe .note-status-output .alert-danger{color:#a94442 !important;background-color:#f2dede !important}.note-editor.note-frame .note-statusbar,.note-editor.note-airframe .note-statusbar{background-color:#8080801d;border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-top:1px solid #00000032}.note-editor.note-frame .note-statusbar .note-resizebar,.note-editor.note-airframe .note-statusbar .note-resizebar{padding-top:1px;height:9px;width:100%;cursor:ns-resize}.note-editor.note-frame .note-statusbar .note-resizebar .note-icon-bar,.note-editor.note-airframe .note-statusbar .note-resizebar .note-icon-bar{width:20px;margin:1px auto;border-top:1px solid #00000032}.note-editor.note-frame .note-statusbar.locked .note-resizebar,.note-editor.note-airframe .note-statusbar.locked .note-resizebar{cursor:default}.note-editor.note-frame .note-statusbar.locked .note-resizebar .note-icon-bar,.note-editor.note-airframe .note-statusbar.locked .note-resizebar .note-icon-bar{display:none}.note-editor.note-frame .note-placeholder,.note-editor.note-airframe .note-placeholder{padding:10px}.note-editor.note-airframe{border:0}.note-editor.note-airframe .note-editing-area .note-editable{padding:0}.note-popover.popover{display:none;max-width:none}.note-popover.popover .popover-content a{display:inline-block;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}.note-popover.popover .arrow{left:20px !important}.note-toolbar{position:relative}.note-popover .popover-content,.note-editor .note-toolbar{margin:0;padding:0 0 5px 5px}.note-popover .popover-content>.note-btn-group,.note-editor .note-toolbar>.note-btn-group{margin-top:5px;margin-left:0;margin-right:5px}.note-popover .popover-content .note-btn-group .note-table,.note-editor .note-toolbar .note-btn-group .note-table{min-width:0;padding:5px}.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker,.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker{font-size:18px}.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-mousecatcher,.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-mousecatcher{position:absolute !important;z-index:3;width:10em;height:10em;cursor:pointer}.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-unhighlighted,.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-unhighlighted{position:relative !important;z-index:1;width:5em;height:5em;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIj4+Pjp6ekKlAqjAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKhmnaJzPAAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC") repeat}.note-popover .popover-content .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-highlighted,.note-editor .note-toolbar .note-btn-group .note-table .note-dimension-picker .note-dimension-picker-highlighted{position:absolute !important;z-index:2;width:1em;height:1em;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIjd6vvD2f9LKLW+AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKwNDEVT0AAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC") repeat}.note-popover .popover-content .note-style .dropdown-style blockquote,.note-popover .popover-content .note-style .dropdown-style pre,.note-editor .note-toolbar .note-style .dropdown-style blockquote,.note-editor .note-toolbar .note-style .dropdown-style pre{margin:0;padding:5px 10px}.note-popover .popover-content .note-style .dropdown-style h1,.note-popover .popover-content .note-style .dropdown-style h2,.note-popover .popover-content .note-style .dropdown-style h3,.note-popover .popover-content .note-style .dropdown-style h4,.note-popover .popover-content .note-style .dropdown-style h5,.note-popover .popover-content .note-style .dropdown-style h6,.note-popover .popover-content .note-style .dropdown-style p,.note-editor .note-toolbar .note-style .dropdown-style h1,.note-editor .note-toolbar .note-style .dropdown-style h2,.note-editor .note-toolbar .note-style .dropdown-style h3,.note-editor .note-toolbar .note-style .dropdown-style h4,.note-editor .note-toolbar .note-style .dropdown-style h5,.note-editor .note-toolbar .note-style .dropdown-style h6,.note-editor .note-toolbar .note-style .dropdown-style p{margin:0;padding:0}.note-popover .popover-content .note-color-all .note-dropdown-menu,.note-editor .note-toolbar .note-color-all .note-dropdown-menu{min-width:337px}.note-popover .popover-content .note-color .dropdown-toggle,.note-editor .note-toolbar .note-color .dropdown-toggle{width:20px;padding-left:5px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette{display:inline-block;margin:0;width:160px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette:first-child,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette:first-child{margin:0 5px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-palette-title,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-palette-title{font-size:12px;margin:2px 7px;text-align:center;border-bottom:1px solid #eee}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-reset,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-reset,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select{font-size:11px;margin:3px;padding:0 3px;cursor:pointer;width:100%;border-radius:5px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-reset:hover,.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select:hover,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-reset:hover,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select:hover{background:#eee}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-row,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-row{height:20px}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-color-select-btn,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-color-select-btn{display:none}.note-popover .popover-content .note-color .note-dropdown-menu .note-palette .note-holder-custom .note-color-btn,.note-editor .note-toolbar .note-color .note-dropdown-menu .note-palette .note-holder-custom .note-color-btn{border:1px solid #eee}.note-popover .popover-content .note-para .note-dropdown-menu,.note-editor .note-toolbar .note-para .note-dropdown-menu{min-width:228px;padding:5px}.note-popover .popover-content .note-para .note-dropdown-menu>div+div,.note-editor .note-toolbar .note-para .note-dropdown-menu>div+div{margin-left:5px}.note-popover .popover-content .note-dropdown-menu,.note-editor .note-toolbar .note-dropdown-menu{min-width:160px}.note-popover .popover-content .note-dropdown-menu.right,.note-editor .note-toolbar .note-dropdown-menu.right{right:0;left:auto}.note-popover .popover-content .note-dropdown-menu.right::before,.note-editor .note-toolbar .note-dropdown-menu.right::before{right:9px;left:auto !important}.note-popover .popover-content .note-dropdown-menu.right::after,.note-editor .note-toolbar .note-dropdown-menu.right::after{right:10px;left:auto !important}.note-popover .popover-content .note-dropdown-menu.note-check a i,.note-editor .note-toolbar .note-dropdown-menu.note-check a i{color:#00bfff;visibility:hidden}.note-popover .popover-content .note-dropdown-menu.note-check a.checked i,.note-editor .note-toolbar .note-dropdown-menu.note-check a.checked i{visibility:visible}.note-popover .popover-content .note-fontsize-10,.note-editor .note-toolbar .note-fontsize-10{font-size:10px}.note-popover .popover-content .note-color-palette,.note-editor .note-toolbar .note-color-palette{line-height:1}.note-popover .popover-content .note-color-palette div .note-color-btn,.note-editor .note-toolbar .note-color-palette div .note-color-btn{width:20px;height:20px;padding:0;margin:0;border:0;border-radius:0}.note-popover .popover-content .note-color-palette div .note-color-btn:hover,.note-editor .note-toolbar .note-color-palette div .note-color-btn:hover{transform:scale(1.2);transition:all .2s}.note-modal .modal-dialog{outline:0;border-radius:5px;}.note-modal .form-group{margin-left:0;margin-right:0}.note-modal .note-modal-form{margin:0}.note-modal .note-image-dialog .note-dropzone{min-height:100px;font-size:30px;line-height:4;color:#d3d3d3;text-align:center;border:4px dashed #d3d3d3;margin-bottom:10px}@-moz-document url-prefix(){.note-modal .note-image-input{height:auto}}.note-placeholder{position:absolute;display:none;color:gray}.note-handle .note-control-selection{position:absolute;display:none;border:1px solid #000}.note-handle .note-control-selection>div{position:absolute}.note-handle .note-control-selection .note-control-selection-bg{width:100%;height:100%;background-color:#000;-webkit-opacity:.3;-khtml-opacity:.3;-moz-opacity:.3;opacity:.3;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(opacity=30);filter:alpha(opacity=30)}.note-handle .note-control-selection .note-control-handle,.note-handle .note-control-selection .note-control-sizing,.note-handle .note-control-selection .note-control-holder{width:7px;height:7px;border:1px solid #000}.note-handle .note-control-selection .note-control-sizing{background-color:#000}.note-handle .note-control-selection .note-control-nw{top:-5px;left:-5px;border-right:none;border-bottom:none}.note-handle .note-control-selection .note-control-ne{top:-5px;right:-5px;border-bottom:none;border-left:none}.note-handle .note-control-selection .note-control-sw{bottom:-5px;left:-5px;border-top:none;border-right:none}.note-handle .note-control-selection .note-control-se{right:-5px;bottom:-5px;cursor:se-resize}.note-handle .note-control-selection .note-control-se.note-control-holder{cursor:default;border-top:none;border-left:none}.note-handle .note-control-selection .note-control-selection-info{right:0;bottom:0;padding:5px;margin:5px;color:#fff;background-color:#000;font-size:12px;border-radius:5px;-webkit-opacity:.7;-khtml-opacity:.7;-moz-opacity:.7;opacity:.7;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(opacity=70);filter:alpha(opacity=70)}.note-hint-popover{min-width:100px;padding:2px}.note-hint-popover .popover-content{padding:3px;max-height:150px;overflow:auto}.note-hint-popover .popover-content .note-hint-group .note-hint-item{display:block !important;padding:3px}.note-hint-popover .popover-content .note-hint-group .note-hint-item.active,.note-hint-popover .popover-content .note-hint-group .note-hint-item:hover{display:block;clear:both;font-weight:400;line-height:1.4;color:#fff;white-space:nowrap;text-decoration:none;background-color:#428bca;outline:0;cursor:pointer} ================================================ FILE: ruoyi-admin/src/main/resources/static/ajax/libs/summernote/summernote.js ================================================ /*! * * Super simple wysiwyg editor v0.8.18 * https://summernote.org * * * Copyright 2013- Alan Hong. and other contributors * summernote may be freely distributed under the MIT license. * * Date: 2020-05-20T18:09Z * */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(require("jquery")); else if(typeof define === 'function' && define.amd) define(["jquery"], factory); else { var a = typeof exports === 'object' ? factory(require("jquery")) : factory(root["jQuery"]); for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; } })(window, function(__WEBPACK_EXTERNAL_MODULE__0__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 52); /******/ }) /************************************************************************/ /******/ ({ /***/ 0: /***/ (function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE__0__; /***/ }), /***/ 1: /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_0__); function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var Renderer = /*#__PURE__*/function () { function Renderer(markup, children, options, callback) { _classCallCheck(this, Renderer); this.markup = markup; this.children = children; this.options = options; this.callback = callback; } _createClass(Renderer, [{ key: "render", value: function render($parent) { var $node = jquery__WEBPACK_IMPORTED_MODULE_0___default()(this.markup); if (this.options && this.options.contents) { $node.html(this.options.contents); } if (this.options && this.options.className) { $node.addClass(this.options.className); } if (this.options && this.options.data) { jquery__WEBPACK_IMPORTED_MODULE_0___default.a.each(this.options.data, function (k, v) { $node.attr('data-' + k, v); }); } if (this.options && this.options.click) { $node.on('click', this.options.click); } if (this.children) { var $container = $node.find('.note-children-container'); this.children.forEach(function (child) { child.render($container.length ? $container : $node); }); } if (this.callback) { this.callback($node, this.options); } if (this.options && this.options.callback) { this.options.callback($node); } if ($parent) { $parent.append($node); } return $node; } }]); return Renderer; }(); /* harmony default export */ __webpack_exports__["a"] = ({ create: function create(markup, callback) { return function () { var options = _typeof(arguments[1]) === 'object' ? arguments[1] : arguments[0]; var children = Array.isArray(arguments[0]) ? arguments[0] : []; if (options && options.children) { children = options.children; } return new Renderer(markup, children, options, callback); }; } }); /***/ }), /***/ 2: /***/ (function(module, exports) { /* WEBPACK VAR INJECTION */(function(__webpack_amd_options__) {/* globals __webpack_amd_options__ */ module.exports = __webpack_amd_options__; /* WEBPACK VAR INJECTION */}.call(this, {})) /***/ }), /***/ 3: /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; // EXTERNAL MODULE: external {"root":"jQuery","commonjs2":"jquery","commonjs":"jquery","amd":"jquery"} var external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_ = __webpack_require__(0); var external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default = /*#__PURE__*/__webpack_require__.n(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_); // CONCATENATED MODULE: ./src/js/base/summernote-en-US.js external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote || { lang: {} }; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.lang, { 'en-US': { font: { bold: 'Bold', italic: 'Italic', underline: 'Underline', clear: 'Remove Font Style', height: 'Line Height', name: 'Font Family', strikethrough: 'Strikethrough', subscript: 'Subscript', superscript: 'Superscript', size: 'Font Size', sizeunit: 'Font Size Unit' }, image: { image: 'Picture', insert: 'Insert Image', resizeFull: 'Resize full', resizeHalf: 'Resize half', resizeQuarter: 'Resize quarter', resizeNone: 'Original size', floatLeft: 'Float Left', floatRight: 'Float Right', floatNone: 'Remove float', shapeRounded: 'Shape: Rounded', shapeCircle: 'Shape: Circle', shapeThumbnail: 'Shape: Thumbnail', shapeNone: 'Shape: None', dragImageHere: 'Drag image or text here', dropImage: 'Drop image or Text', selectFromFiles: 'Select from files', maximumFileSize: 'Maximum file size', maximumFileSizeError: 'Maximum file size exceeded.', url: 'Image URL', remove: 'Remove Image', original: 'Original' }, video: { video: 'Video', videoLink: 'Video Link', insert: 'Insert Video', url: 'Video URL', providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion or Youku)' }, link: { link: 'Link', insert: 'Insert Link', unlink: 'Unlink', edit: 'Edit', textToDisplay: 'Text to display', url: 'To what URL should this link go?', openInNewWindow: 'Open in new window', useProtocol: 'Use default protocol' }, table: { table: 'Table', addRowAbove: 'Add row above', addRowBelow: 'Add row below', addColLeft: 'Add column left', addColRight: 'Add column right', delRow: 'Delete row', delCol: 'Delete column', delTable: 'Delete table' }, hr: { insert: 'Insert Horizontal Rule' }, style: { style: 'Style', p: 'Normal', blockquote: 'Quote', pre: 'Code', h1: 'Header 1', h2: 'Header 2', h3: 'Header 3', h4: 'Header 4', h5: 'Header 5', h6: 'Header 6' }, lists: { unordered: 'Unordered list', ordered: 'Ordered list' }, options: { help: 'Help', fullscreen: 'Full Screen', codeview: 'Code View' }, paragraph: { paragraph: 'Paragraph', outdent: 'Outdent', indent: 'Indent', left: 'Align left', center: 'Align center', right: 'Align right', justify: 'Justify full' }, color: { recent: 'Recent Color', more: 'More Color', background: 'Background Color', foreground: 'Text Color', transparent: 'Transparent', setTransparent: 'Set transparent', reset: 'Reset', resetToDefault: 'Reset to default', cpSelect: 'Select' }, shortcut: { shortcuts: 'Keyboard shortcuts', close: 'Close', textFormatting: 'Text formatting', action: 'Action', paragraphFormatting: 'Paragraph formatting', documentStyle: 'Document Style', extraKeys: 'Extra keys' }, help: { 'escape': 'Escape', 'insertParagraph': 'Insert Paragraph', 'undo': 'Undo the last command', 'redo': 'Redo the last command', 'tab': 'Tab', 'untab': 'Untab', 'bold': 'Set a bold style', 'italic': 'Set a italic style', 'underline': 'Set a underline style', 'strikethrough': 'Set a strikethrough style', 'removeFormat': 'Clean a style', 'justifyLeft': 'Set left align', 'justifyCenter': 'Set center align', 'justifyRight': 'Set right align', 'justifyFull': 'Set full align', 'insertUnorderedList': 'Toggle unordered list', 'insertOrderedList': 'Toggle ordered list', 'outdent': 'Outdent on current paragraph', 'indent': 'Indent on current paragraph', 'formatPara': 'Change current block\'s format as a paragraph(P tag)', 'formatH1': 'Change current block\'s format as H1', 'formatH2': 'Change current block\'s format as H2', 'formatH3': 'Change current block\'s format as H3', 'formatH4': 'Change current block\'s format as H4', 'formatH5': 'Change current block\'s format as H5', 'formatH6': 'Change current block\'s format as H6', 'insertHorizontalRule': 'Insert horizontal rule', 'linkDialog.show': 'Show Link Dialog' }, history: { undo: 'Undo', redo: 'Redo' }, specialChar: { specialChar: 'SPECIAL CHARACTERS', select: 'Select Special characters' }, output: { noSelection: 'No Selection Made!' } } }); // CONCATENATED MODULE: ./src/js/base/core/env.js var isSupportAmd = typeof define === 'function' && __webpack_require__(2); // eslint-disable-line /** * returns whether font is installed or not. * * @param {String} fontName * @return {Boolean} */ var genericFontFamilies = ['sans-serif', 'serif', 'monospace', 'cursive', 'fantasy']; function validFontName(fontName) { return external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.inArray(fontName.toLowerCase(), genericFontFamilies) === -1 ? "'".concat(fontName, "'") : fontName; } function env_isFontInstalled(fontName) { var testFontName = fontName === 'Comic Sans MS' ? 'Courier New' : 'Comic Sans MS'; var testText = 'mmmmmmmmmmwwwww'; var testSize = '200px'; var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); context.font = testSize + " '" + testFontName + "'"; var originalWidth = context.measureText(testText).width; context.font = testSize + ' ' + validFontName(fontName) + ', "' + testFontName + '"'; var width = context.measureText(testText).width; return originalWidth !== width; } var userAgent = navigator.userAgent; var isMSIE = /MSIE|Trident/i.test(userAgent); var browserVersion; if (isMSIE) { var matches = /MSIE (\d+[.]\d+)/.exec(userAgent); if (matches) { browserVersion = parseFloat(matches[1]); } matches = /Trident\/.*rv:([0-9]{1,}[.0-9]{0,})/.exec(userAgent); if (matches) { browserVersion = parseFloat(matches[1]); } } var isEdge = /Edge\/\d+/.test(userAgent); var isSupportTouch = 'ontouchstart' in window || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; // [workaround] IE doesn't have input events for contentEditable // - see: https://goo.gl/4bfIvA var inputEventName = isMSIE ? 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted' : 'input'; /** * @class core.env * * Object which check platform and agent * * @singleton * @alternateClassName env */ /* harmony default export */ var env = ({ isMac: navigator.appVersion.indexOf('Mac') > -1, isMSIE: isMSIE, isEdge: isEdge, isFF: !isEdge && /firefox/i.test(userAgent), isPhantom: /PhantomJS/i.test(userAgent), isWebkit: !isEdge && /webkit/i.test(userAgent), isChrome: !isEdge && /chrome/i.test(userAgent), isSafari: !isEdge && /safari/i.test(userAgent) && !/chrome/i.test(userAgent), browserVersion: browserVersion, jqueryVersion: parseFloat(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.fn.jquery), isSupportAmd: isSupportAmd, isSupportTouch: isSupportTouch, isFontInstalled: env_isFontInstalled, isW3CRangeSupport: !!document.createRange, inputEventName: inputEventName, genericFontFamilies: genericFontFamilies, validFontName: validFontName }); // CONCATENATED MODULE: ./src/js/base/core/func.js /** * @class core.func * * func utils (for high-order func's arg) * * @singleton * @alternateClassName func */ function eq(itemA) { return function (itemB) { return itemA === itemB; }; } function eq2(itemA, itemB) { return itemA === itemB; } function peq2(propName) { return function (itemA, itemB) { return itemA[propName] === itemB[propName]; }; } function ok() { return true; } function fail() { return false; } function not(f) { return function () { return !f.apply(f, arguments); }; } function and(fA, fB) { return function (item) { return fA(item) && fB(item); }; } function func_self(a) { return a; } function func_invoke(obj, method) { return function () { return obj[method].apply(obj, arguments); }; } var idCounter = 0; /** * reset globally-unique id * */ function resetUniqueId() { idCounter = 0; } /** * generate a globally-unique id * * @param {String} [prefix] */ function uniqueId(prefix) { var id = ++idCounter + ''; return prefix ? prefix + id : id; } /** * returns bnd (bounds) from rect * * - IE Compatibility Issue: http://goo.gl/sRLOAo * - Scroll Issue: http://goo.gl/sNjUc * * @param {Rect} rect * @return {Object} bounds * @return {Number} bounds.top * @return {Number} bounds.left * @return {Number} bounds.width * @return {Number} bounds.height */ function rect2bnd(rect) { var $document = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); return { top: rect.top + $document.scrollTop(), left: rect.left + $document.scrollLeft(), width: rect.right - rect.left, height: rect.bottom - rect.top }; } /** * returns a copy of the object where the keys have become the values and the values the keys. * @param {Object} obj * @return {Object} */ function invertObject(obj) { var inverted = {}; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { inverted[obj[key]] = key; } } return inverted; } /** * @param {String} namespace * @param {String} [prefix] * @return {String} */ function namespaceToCamel(namespace, prefix) { prefix = prefix || ''; return prefix + namespace.split('.').map(function (name) { return name.substring(0, 1).toUpperCase() + name.substring(1); }).join(''); } /** * Returns a function, that, as long as it continues to be invoked, will not * be triggered. The function will be called after it stops being called for * N milliseconds. If `immediate` is passed, trigger the function on the * leading edge, instead of the trailing. * @param {Function} func * @param {Number} wait * @param {Boolean} immediate * @return {Function} */ function debounce(func, wait, immediate) { var timeout; return function () { var context = this; var args = arguments; var later = function later() { timeout = null; if (!immediate) { func.apply(context, args); } }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { func.apply(context, args); } }; } /** * * @param {String} url * @return {Boolean} */ function isValidUrl(url) { var expression = /[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/gi; return expression.test(url); } /* harmony default export */ var func = ({ eq: eq, eq2: eq2, peq2: peq2, ok: ok, fail: fail, self: func_self, not: not, and: and, invoke: func_invoke, resetUniqueId: resetUniqueId, uniqueId: uniqueId, rect2bnd: rect2bnd, invertObject: invertObject, namespaceToCamel: namespaceToCamel, debounce: debounce, isValidUrl: isValidUrl }); // CONCATENATED MODULE: ./src/js/base/core/lists.js /** * returns the first item of an array. * * @param {Array} array */ function lists_head(array) { return array[0]; } /** * returns the last item of an array. * * @param {Array} array */ function lists_last(array) { return array[array.length - 1]; } /** * returns everything but the last entry of the array. * * @param {Array} array */ function initial(array) { return array.slice(0, array.length - 1); } /** * returns the rest of the items in an array. * * @param {Array} array */ function tail(array) { return array.slice(1); } /** * returns item of array */ function find(array, pred) { for (var idx = 0, len = array.length; idx < len; idx++) { var item = array[idx]; if (pred(item)) { return item; } } } /** * returns true if all of the values in the array pass the predicate truth test. */ function lists_all(array, pred) { for (var idx = 0, len = array.length; idx < len; idx++) { if (!pred(array[idx])) { return false; } } return true; } /** * returns true if the value is present in the list. */ function contains(array, item) { if (array && array.length && item) { if (array.indexOf) { return array.indexOf(item) !== -1; } else if (array.contains) { // `DOMTokenList` doesn't implement `.indexOf`, but it implements `.contains` return array.contains(item); } } return false; } /** * get sum from a list * * @param {Array} array - array * @param {Function} fn - iterator */ function sum(array, fn) { fn = fn || func.self; return array.reduce(function (memo, v) { return memo + fn(v); }, 0); } /** * returns a copy of the collection with array type. * @param {Collection} collection - collection eg) node.childNodes, ... */ function from(collection) { var result = []; var length = collection.length; var idx = -1; while (++idx < length) { result[idx] = collection[idx]; } return result; } /** * returns whether list is empty or not */ function lists_isEmpty(array) { return !array || !array.length; } /** * cluster elements by predicate function. * * @param {Array} array - array * @param {Function} fn - predicate function for cluster rule * @param {Array[]} */ function clusterBy(array, fn) { if (!array.length) { return []; } var aTail = tail(array); return aTail.reduce(function (memo, v) { var aLast = lists_last(memo); if (fn(lists_last(aLast), v)) { aLast[aLast.length] = v; } else { memo[memo.length] = [v]; } return memo; }, [[lists_head(array)]]); } /** * returns a copy of the array with all false values removed * * @param {Array} array - array * @param {Function} fn - predicate function for cluster rule */ function compact(array) { var aResult = []; for (var idx = 0, len = array.length; idx < len; idx++) { if (array[idx]) { aResult.push(array[idx]); } } return aResult; } /** * produces a duplicate-free version of the array * * @param {Array} array */ function unique(array) { var results = []; for (var idx = 0, len = array.length; idx < len; idx++) { if (!contains(results, array[idx])) { results.push(array[idx]); } } return results; } /** * returns next item. * @param {Array} array */ function lists_next(array, item) { if (array && array.length && item) { var idx = array.indexOf(item); return idx === -1 ? null : array[idx + 1]; } return null; } /** * returns prev item. * @param {Array} array */ function prev(array, item) { if (array && array.length && item) { var idx = array.indexOf(item); return idx === -1 ? null : array[idx - 1]; } return null; } /** * @class core.list * * list utils * * @singleton * @alternateClassName list */ /* harmony default export */ var lists = ({ head: lists_head, last: lists_last, initial: initial, tail: tail, prev: prev, next: lists_next, find: find, contains: contains, all: lists_all, sum: sum, from: from, isEmpty: lists_isEmpty, clusterBy: clusterBy, compact: compact, unique: unique }); // CONCATENATED MODULE: ./src/js/base/core/dom.js var NBSP_CHAR = String.fromCharCode(160); var ZERO_WIDTH_NBSP_CHAR = "\uFEFF"; /** * @method isEditable * * returns whether node is `note-editable` or not. * * @param {Node} node * @return {Boolean} */ function isEditable(node) { return node && external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(node).hasClass('note-editable'); } /** * @method isControlSizing * * returns whether node is `note-control-sizing` or not. * * @param {Node} node * @return {Boolean} */ function isControlSizing(node) { return node && external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(node).hasClass('note-control-sizing'); } /** * @method makePredByNodeName * * returns predicate which judge whether nodeName is same * * @param {String} nodeName * @return {Function} */ function makePredByNodeName(nodeName) { nodeName = nodeName.toUpperCase(); return function (node) { return node && node.nodeName.toUpperCase() === nodeName; }; } /** * @method isText * * * * @param {Node} node * @return {Boolean} true if node's type is text(3) */ function isText(node) { return node && node.nodeType === 3; } /** * @method isElement * * * * @param {Node} node * @return {Boolean} true if node's type is element(1) */ function isElement(node) { return node && node.nodeType === 1; } /** * ex) br, col, embed, hr, img, input, ... * @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements */ function isVoid(node) { return node && /^BR|^IMG|^HR|^IFRAME|^BUTTON|^INPUT|^AUDIO|^VIDEO|^EMBED/.test(node.nodeName.toUpperCase()); } function isPara(node) { if (isEditable(node)) { return false; } // Chrome(v31.0), FF(v25.0.1) use DIV for paragraph return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase()); } function isHeading(node) { return node && /^H[1-7]/.test(node.nodeName.toUpperCase()); } var isPre = makePredByNodeName('PRE'); var isLi = makePredByNodeName('LI'); function isPurePara(node) { return isPara(node) && !isLi(node); } var isTable = makePredByNodeName('TABLE'); var isData = makePredByNodeName('DATA'); function dom_isInline(node) { return !isBodyContainer(node) && !isList(node) && !isHr(node) && !isPara(node) && !isTable(node) && !isBlockquote(node) && !isData(node); } function isList(node) { return node && /^UL|^OL/.test(node.nodeName.toUpperCase()); } var isHr = makePredByNodeName('HR'); function dom_isCell(node) { return node && /^TD|^TH/.test(node.nodeName.toUpperCase()); } var isBlockquote = makePredByNodeName('BLOCKQUOTE'); function isBodyContainer(node) { return dom_isCell(node) || isBlockquote(node) || isEditable(node); } var isAnchor = makePredByNodeName('A'); function isParaInline(node) { return dom_isInline(node) && !!dom_ancestor(node, isPara); } function isBodyInline(node) { return dom_isInline(node) && !dom_ancestor(node, isPara); } var isBody = makePredByNodeName('BODY'); /** * returns whether nodeB is closest sibling of nodeA * * @param {Node} nodeA * @param {Node} nodeB * @return {Boolean} */ function isClosestSibling(nodeA, nodeB) { return nodeA.nextSibling === nodeB || nodeA.previousSibling === nodeB; } /** * returns array of closest siblings with node * * @param {Node} node * @param {function} [pred] - predicate function * @return {Node[]} */ function withClosestSiblings(node, pred) { pred = pred || func.ok; var siblings = []; if (node.previousSibling && pred(node.previousSibling)) { siblings.push(node.previousSibling); } siblings.push(node); if (node.nextSibling && pred(node.nextSibling)) { siblings.push(node.nextSibling); } return siblings; } /** * blank HTML for cursor position * - [workaround] old IE only works with   * - [workaround] IE11 and other browser works with bogus br */ var blankHTML = env.isMSIE && env.browserVersion < 11 ? ' ' : '
                '; /** * @method nodeLength * * returns #text's text size or element's childNodes size * * @param {Node} node */ function nodeLength(node) { if (isText(node)) { return node.nodeValue.length; } if (node) { return node.childNodes.length; } return 0; } /** * returns whether deepest child node is empty or not. * * @param {Node} node * @return {Boolean} */ function deepestChildIsEmpty(node) { do { if (node.firstElementChild === null || node.firstElementChild.innerHTML === '') break; } while (node = node.firstElementChild); return dom_isEmpty(node); } /** * returns whether node is empty or not. * * @param {Node} node * @return {Boolean} */ function dom_isEmpty(node) { var len = nodeLength(node); if (len === 0) { return true; } else if (!isText(node) && len === 1 && node.innerHTML === blankHTML) { // ex)


                ,
                return true; } else if (lists.all(node.childNodes, isText) && node.innerHTML === '') { // ex)

                , return true; } return false; } /** * padding blankHTML if node is empty (for cursor position) */ function paddingBlankHTML(node) { if (!isVoid(node) && !nodeLength(node)) { node.innerHTML = blankHTML; } } /** * find nearest ancestor predicate hit * * @param {Node} node * @param {Function} pred - predicate function */ function dom_ancestor(node, pred) { while (node) { if (pred(node)) { return node; } if (isEditable(node)) { break; } node = node.parentNode; } return null; } /** * find nearest ancestor only single child blood line and predicate hit * * @param {Node} node * @param {Function} pred - predicate function */ function singleChildAncestor(node, pred) { node = node.parentNode; while (node) { if (nodeLength(node) !== 1) { break; } if (pred(node)) { return node; } if (isEditable(node)) { break; } node = node.parentNode; } return null; } /** * returns new array of ancestor nodes (until predicate hit). * * @param {Node} node * @param {Function} [optional] pred - predicate function */ function listAncestor(node, pred) { pred = pred || func.fail; var ancestors = []; dom_ancestor(node, function (el) { if (!isEditable(el)) { ancestors.push(el); } return pred(el); }); return ancestors; } /** * find farthest ancestor predicate hit */ function lastAncestor(node, pred) { var ancestors = listAncestor(node); return lists.last(ancestors.filter(pred)); } /** * returns common ancestor node between two nodes. * * @param {Node} nodeA * @param {Node} nodeB */ function dom_commonAncestor(nodeA, nodeB) { var ancestors = listAncestor(nodeA); for (var n = nodeB; n; n = n.parentNode) { if (ancestors.indexOf(n) > -1) return n; } return null; // difference document area } /** * listing all previous siblings (until predicate hit). * * @param {Node} node * @param {Function} [optional] pred - predicate function */ function listPrev(node, pred) { pred = pred || func.fail; var nodes = []; while (node) { if (pred(node)) { break; } nodes.push(node); node = node.previousSibling; } return nodes; } /** * listing next siblings (until predicate hit). * * @param {Node} node * @param {Function} [pred] - predicate function */ function listNext(node, pred) { pred = pred || func.fail; var nodes = []; while (node) { if (pred(node)) { break; } nodes.push(node); node = node.nextSibling; } return nodes; } /** * listing descendant nodes * * @param {Node} node * @param {Function} [pred] - predicate function */ function listDescendant(node, pred) { var descendants = []; pred = pred || func.ok; // start DFS(depth first search) with node (function fnWalk(current) { if (node !== current && pred(current)) { descendants.push(current); } for (var idx = 0, len = current.childNodes.length; idx < len; idx++) { fnWalk(current.childNodes[idx]); } })(node); return descendants; } /** * wrap node with new tag. * * @param {Node} node * @param {Node} tagName of wrapper * @return {Node} - wrapper */ function wrap(node, wrapperName) { var parent = node.parentNode; var wrapper = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('<' + wrapperName + '>')[0]; parent.insertBefore(wrapper, node); wrapper.appendChild(node); return wrapper; } /** * insert node after preceding * * @param {Node} node * @param {Node} preceding - predicate function */ function insertAfter(node, preceding) { var next = preceding.nextSibling; var parent = preceding.parentNode; if (next) { parent.insertBefore(node, next); } else { parent.appendChild(node); } return node; } /** * append elements. * * @param {Node} node * @param {Collection} aChild */ function appendChildNodes(node, aChild) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(aChild, function (idx, child) { node.appendChild(child); }); return node; } /** * returns whether boundaryPoint is left edge or not. * * @param {BoundaryPoint} point * @return {Boolean} */ function isLeftEdgePoint(point) { return point.offset === 0; } /** * returns whether boundaryPoint is right edge or not. * * @param {BoundaryPoint} point * @return {Boolean} */ function isRightEdgePoint(point) { return point.offset === nodeLength(point.node); } /** * returns whether boundaryPoint is edge or not. * * @param {BoundaryPoint} point * @return {Boolean} */ function isEdgePoint(point) { return isLeftEdgePoint(point) || isRightEdgePoint(point); } /** * returns whether node is left edge of ancestor or not. * * @param {Node} node * @param {Node} ancestor * @return {Boolean} */ function dom_isLeftEdgeOf(node, ancestor) { while (node && node !== ancestor) { if (dom_position(node) !== 0) { return false; } node = node.parentNode; } return true; } /** * returns whether node is right edge of ancestor or not. * * @param {Node} node * @param {Node} ancestor * @return {Boolean} */ function isRightEdgeOf(node, ancestor) { if (!ancestor) { return false; } while (node && node !== ancestor) { if (dom_position(node) !== nodeLength(node.parentNode) - 1) { return false; } node = node.parentNode; } return true; } /** * returns whether point is left edge of ancestor or not. * @param {BoundaryPoint} point * @param {Node} ancestor * @return {Boolean} */ function isLeftEdgePointOf(point, ancestor) { return isLeftEdgePoint(point) && dom_isLeftEdgeOf(point.node, ancestor); } /** * returns whether point is right edge of ancestor or not. * @param {BoundaryPoint} point * @param {Node} ancestor * @return {Boolean} */ function isRightEdgePointOf(point, ancestor) { return isRightEdgePoint(point) && isRightEdgeOf(point.node, ancestor); } /** * returns offset from parent. * * @param {Node} node */ function dom_position(node) { var offset = 0; while (node = node.previousSibling) { offset += 1; } return offset; } function hasChildren(node) { return !!(node && node.childNodes && node.childNodes.length); } /** * returns previous boundaryPoint * * @param {BoundaryPoint} point * @param {Boolean} isSkipInnerOffset * @return {BoundaryPoint} */ function dom_prevPoint(point, isSkipInnerOffset) { var node; var offset; if (point.offset === 0) { if (isEditable(point.node)) { return null; } node = point.node.parentNode; offset = dom_position(point.node); } else if (hasChildren(point.node)) { node = point.node.childNodes[point.offset - 1]; offset = nodeLength(node); } else { node = point.node; offset = isSkipInnerOffset ? 0 : point.offset - 1; } return { node: node, offset: offset }; } /** * returns next boundaryPoint * * @param {BoundaryPoint} point * @param {Boolean} isSkipInnerOffset * @return {BoundaryPoint} */ function dom_nextPoint(point, isSkipInnerOffset) { var node, offset; if (nodeLength(point.node) === point.offset) { if (isEditable(point.node)) { return null; } var nextTextNode = getNextTextNode(point.node); if (nextTextNode) { node = nextTextNode; offset = 0; } else { node = point.node.parentNode; offset = dom_position(point.node) + 1; } } else if (hasChildren(point.node)) { node = point.node.childNodes[point.offset]; offset = 0; } else { node = point.node; offset = isSkipInnerOffset ? nodeLength(point.node) : point.offset + 1; } return { node: node, offset: offset }; } /** * returns next boundaryPoint with empty node * * @param {BoundaryPoint} point * @param {Boolean} isSkipInnerOffset * @return {BoundaryPoint} */ function nextPointWithEmptyNode(point, isSkipInnerOffset) { var node, offset; // if node is empty string node, return current node's sibling. if (dom_isEmpty(point.node)) { node = point.node.nextSibling; offset = 0; return { node: node, offset: offset }; } if (nodeLength(point.node) === point.offset) { if (isEditable(point.node)) { return null; } var nextTextNode = getNextTextNode(point.node); if (nextTextNode) { node = nextTextNode; offset = 0; } else { node = point.node.parentNode; offset = dom_position(point.node) + 1; } // if next node is editable, return current node's sibling node. if (isEditable(node)) { node = point.node.nextSibling; offset = 0; } } else if (hasChildren(point.node)) { node = point.node.childNodes[point.offset]; offset = 0; if (dom_isEmpty(node)) { return null; } } else { node = point.node; offset = isSkipInnerOffset ? nodeLength(point.node) : point.offset + 1; if (dom_isEmpty(node)) { return null; } } return { node: node, offset: offset }; } /* * returns the next Text node index or 0 if not found. */ function getNextTextNode(actual) { if (!actual.nextSibling) return undefined; if (actual.parent !== actual.nextSibling.parent) return undefined; if (isText(actual.nextSibling)) return actual.nextSibling; return getNextTextNode(actual.nextSibling); } /** * returns whether pointA and pointB is same or not. * * @param {BoundaryPoint} pointA * @param {BoundaryPoint} pointB * @return {Boolean} */ function isSamePoint(pointA, pointB) { return pointA.node === pointB.node && pointA.offset === pointB.offset; } /** * returns whether point is visible (can set cursor) or not. * * @param {BoundaryPoint} point * @return {Boolean} */ function isVisiblePoint(point) { if (isText(point.node) || !hasChildren(point.node) || dom_isEmpty(point.node)) { return true; } var leftNode = point.node.childNodes[point.offset - 1]; var rightNode = point.node.childNodes[point.offset]; if ((!leftNode || isVoid(leftNode)) && (!rightNode || isVoid(rightNode))) { return true; } return false; } /** * @method prevPointUtil * * @param {BoundaryPoint} point * @param {Function} pred * @return {BoundaryPoint} */ function prevPointUntil(point, pred) { while (point) { if (pred(point)) { return point; } point = dom_prevPoint(point); } return null; } /** * @method nextPointUntil * * @param {BoundaryPoint} point * @param {Function} pred * @return {BoundaryPoint} */ function nextPointUntil(point, pred) { while (point) { if (pred(point)) { return point; } point = dom_nextPoint(point); } return null; } /** * returns whether point has character or not. * * @param {Point} point * @return {Boolean} */ function isCharPoint(point) { if (!isText(point.node)) { return false; } var ch = point.node.nodeValue.charAt(point.offset - 1); return ch && ch !== ' ' && ch !== NBSP_CHAR; } /** * returns whether point has space or not. * * @param {Point} point * @return {Boolean} */ function isSpacePoint(point) { if (!isText(point.node)) { return false; } var ch = point.node.nodeValue.charAt(point.offset - 1); return ch === ' ' || ch === NBSP_CHAR; } /** * @method walkPoint * * @param {BoundaryPoint} startPoint * @param {BoundaryPoint} endPoint * @param {Function} handler * @param {Boolean} isSkipInnerOffset */ function walkPoint(startPoint, endPoint, handler, isSkipInnerOffset) { var point = startPoint; while (point) { handler(point); if (isSamePoint(point, endPoint)) { break; } var isSkipOffset = isSkipInnerOffset && startPoint.node !== point.node && endPoint.node !== point.node; point = nextPointWithEmptyNode(point, isSkipOffset); } } /** * @method makeOffsetPath * * return offsetPath(array of offset) from ancestor * * @param {Node} ancestor - ancestor node * @param {Node} node */ function makeOffsetPath(ancestor, node) { var ancestors = listAncestor(node, func.eq(ancestor)); return ancestors.map(dom_position).reverse(); } /** * @method fromOffsetPath * * return element from offsetPath(array of offset) * * @param {Node} ancestor - ancestor node * @param {array} offsets - offsetPath */ function fromOffsetPath(ancestor, offsets) { var current = ancestor; for (var i = 0, len = offsets.length; i < len; i++) { if (current.childNodes.length <= offsets[i]) { current = current.childNodes[current.childNodes.length - 1]; } else { current = current.childNodes[offsets[i]]; } } return current; } /** * @method splitNode * * split element or #text * * @param {BoundaryPoint} point * @param {Object} [options] * @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false * @param {Boolean} [options.isNotSplitEdgePoint] - default: false * @param {Boolean} [options.isDiscardEmptySplits] - default: false * @return {Node} right node of boundaryPoint */ function splitNode(point, options) { var isSkipPaddingBlankHTML = options && options.isSkipPaddingBlankHTML; var isNotSplitEdgePoint = options && options.isNotSplitEdgePoint; var isDiscardEmptySplits = options && options.isDiscardEmptySplits; if (isDiscardEmptySplits) { isSkipPaddingBlankHTML = true; } // edge case if (isEdgePoint(point) && (isText(point.node) || isNotSplitEdgePoint)) { if (isLeftEdgePoint(point)) { return point.node; } else if (isRightEdgePoint(point)) { return point.node.nextSibling; } } // split #text if (isText(point.node)) { return point.node.splitText(point.offset); } else { var childNode = point.node.childNodes[point.offset]; var clone = insertAfter(point.node.cloneNode(false), point.node); appendChildNodes(clone, listNext(childNode)); if (!isSkipPaddingBlankHTML) { paddingBlankHTML(point.node); paddingBlankHTML(clone); } if (isDiscardEmptySplits) { if (dom_isEmpty(point.node)) { remove(point.node); } if (dom_isEmpty(clone)) { remove(clone); return point.node.nextSibling; } } return clone; } } /** * @method splitTree * * split tree by point * * @param {Node} root - split root * @param {BoundaryPoint} point * @param {Object} [options] * @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false * @param {Boolean} [options.isNotSplitEdgePoint] - default: false * @return {Node} right node of boundaryPoint */ function splitTree(root, point, options) { // ex) [#text, ,

                ] var ancestors = listAncestor(point.node, func.eq(root)); if (!ancestors.length) { return null; } else if (ancestors.length === 1) { return splitNode(point, options); } return ancestors.reduce(function (node, parent) { if (node === point.node) { node = splitNode(point, options); } return splitNode({ node: parent, offset: node ? dom_position(node) : nodeLength(parent) }, options); }); } /** * split point * * @param {Point} point * @param {Boolean} isInline * @return {Object} */ function splitPoint(point, isInline) { // find splitRoot, container // - inline: splitRoot is a child of paragraph // - block: splitRoot is a child of bodyContainer var pred = isInline ? isPara : isBodyContainer; var ancestors = listAncestor(point.node, pred); var topAncestor = lists.last(ancestors) || point.node; var splitRoot, container; if (pred(topAncestor)) { splitRoot = ancestors[ancestors.length - 2]; container = topAncestor; } else { splitRoot = topAncestor; container = splitRoot.parentNode; } // if splitRoot is exists, split with splitTree var pivot = splitRoot && splitTree(splitRoot, point, { isSkipPaddingBlankHTML: isInline, isNotSplitEdgePoint: isInline }); // if container is point.node, find pivot with point.offset if (!pivot && container === point.node) { pivot = point.node.childNodes[point.offset]; } return { rightNode: pivot, container: container }; } function dom_create(nodeName) { return document.createElement(nodeName); } function createText(text) { return document.createTextNode(text); } /** * @method remove * * remove node, (isRemoveChild: remove child or not) * * @param {Node} node * @param {Boolean} isRemoveChild */ function remove(node, isRemoveChild) { if (!node || !node.parentNode) { return; } if (node.removeNode) { return node.removeNode(isRemoveChild); } var parent = node.parentNode; if (!isRemoveChild) { var nodes = []; for (var i = 0, len = node.childNodes.length; i < len; i++) { nodes.push(node.childNodes[i]); } for (var _i = 0, _len = nodes.length; _i < _len; _i++) { parent.insertBefore(nodes[_i], node); } } parent.removeChild(node); } /** * @method removeWhile * * @param {Node} node * @param {Function} pred */ function removeWhile(node, pred) { while (node) { if (isEditable(node) || !pred(node)) { break; } var parent = node.parentNode; remove(node); node = parent; } } /** * @method replace * * replace node with provided nodeName * * @param {Node} node * @param {String} nodeName * @return {Node} - new node */ function dom_replace(node, nodeName) { if (node.nodeName.toUpperCase() === nodeName.toUpperCase()) { return node; } var newNode = dom_create(nodeName); if (node.style.cssText) { newNode.style.cssText = node.style.cssText; } appendChildNodes(newNode, lists.from(node.childNodes)); insertAfter(newNode, node); remove(node); return newNode; } var isTextarea = makePredByNodeName('TEXTAREA'); /** * @param {jQuery} $node * @param {Boolean} [stripLinebreaks] - default: false */ function dom_value($node, stripLinebreaks) { var val = isTextarea($node[0]) ? $node.val() : $node.html(); if (stripLinebreaks) { return val.replace(/[\n\r]/g, ''); } return val; } /** * @method html * * get the HTML contents of node * * @param {jQuery} $node * @param {Boolean} [isNewlineOnBlock] */ function dom_html($node, isNewlineOnBlock) { var markup = dom_value($node); if (isNewlineOnBlock) { var regexTag = /<(\/?)(\b(?!!)[^>\s]*)(.*?)(\s*\/?>)/g; markup = markup.replace(regexTag, function (match, endSlash, name) { name = name.toUpperCase(); var isEndOfInlineContainer = /^DIV|^TD|^TH|^P|^LI|^H[1-7]/.test(name) && !!endSlash; var isBlockNode = /^BLOCKQUOTE|^TABLE|^TBODY|^TR|^HR|^UL|^OL/.test(name); return match + (isEndOfInlineContainer || isBlockNode ? '\n' : ''); }); markup = markup.trim(); } return markup; } function posFromPlaceholder(placeholder) { var $placeholder = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(placeholder); var pos = $placeholder.offset(); var height = $placeholder.outerHeight(true); // include margin return { left: pos.left, top: pos.top + height }; } function attachEvents($node, events) { Object.keys(events).forEach(function (key) { $node.on(key, events[key]); }); } function detachEvents($node, events) { Object.keys(events).forEach(function (key) { $node.off(key, events[key]); }); } /** * @method isCustomStyleTag * * assert if a node contains a "note-styletag" class, * which implies that's a custom-made style tag node * * @param {Node} an HTML DOM node */ function isCustomStyleTag(node) { return node && !isText(node) && lists.contains(node.classList, 'note-styletag'); } /* harmony default export */ var dom = ({ /** @property {String} NBSP_CHAR */ NBSP_CHAR: NBSP_CHAR, /** @property {String} ZERO_WIDTH_NBSP_CHAR */ ZERO_WIDTH_NBSP_CHAR: ZERO_WIDTH_NBSP_CHAR, /** @property {String} blank */ blank: blankHTML, /** @property {String} emptyPara */ emptyPara: "

                ".concat(blankHTML, "

                "), makePredByNodeName: makePredByNodeName, isEditable: isEditable, isControlSizing: isControlSizing, isText: isText, isElement: isElement, isVoid: isVoid, isPara: isPara, isPurePara: isPurePara, isHeading: isHeading, isInline: dom_isInline, isBlock: func.not(dom_isInline), isBodyInline: isBodyInline, isBody: isBody, isParaInline: isParaInline, isPre: isPre, isList: isList, isTable: isTable, isData: isData, isCell: dom_isCell, isBlockquote: isBlockquote, isBodyContainer: isBodyContainer, isAnchor: isAnchor, isDiv: makePredByNodeName('DIV'), isLi: isLi, isBR: makePredByNodeName('BR'), isSpan: makePredByNodeName('SPAN'), isB: makePredByNodeName('B'), isU: makePredByNodeName('U'), isS: makePredByNodeName('S'), isI: makePredByNodeName('I'), isImg: makePredByNodeName('IMG'), isTextarea: isTextarea, deepestChildIsEmpty: deepestChildIsEmpty, isEmpty: dom_isEmpty, isEmptyAnchor: func.and(isAnchor, dom_isEmpty), isClosestSibling: isClosestSibling, withClosestSiblings: withClosestSiblings, nodeLength: nodeLength, isLeftEdgePoint: isLeftEdgePoint, isRightEdgePoint: isRightEdgePoint, isEdgePoint: isEdgePoint, isLeftEdgeOf: dom_isLeftEdgeOf, isRightEdgeOf: isRightEdgeOf, isLeftEdgePointOf: isLeftEdgePointOf, isRightEdgePointOf: isRightEdgePointOf, prevPoint: dom_prevPoint, nextPoint: dom_nextPoint, nextPointWithEmptyNode: nextPointWithEmptyNode, isSamePoint: isSamePoint, isVisiblePoint: isVisiblePoint, prevPointUntil: prevPointUntil, nextPointUntil: nextPointUntil, isCharPoint: isCharPoint, isSpacePoint: isSpacePoint, walkPoint: walkPoint, ancestor: dom_ancestor, singleChildAncestor: singleChildAncestor, listAncestor: listAncestor, lastAncestor: lastAncestor, listNext: listNext, listPrev: listPrev, listDescendant: listDescendant, commonAncestor: dom_commonAncestor, wrap: wrap, insertAfter: insertAfter, appendChildNodes: appendChildNodes, position: dom_position, hasChildren: hasChildren, makeOffsetPath: makeOffsetPath, fromOffsetPath: fromOffsetPath, splitTree: splitTree, splitPoint: splitPoint, create: dom_create, createText: createText, remove: remove, removeWhile: removeWhile, replace: dom_replace, html: dom_html, value: dom_value, posFromPlaceholder: posFromPlaceholder, attachEvents: attachEvents, detachEvents: detachEvents, isCustomStyleTag: isCustomStyleTag }); // CONCATENATED MODULE: ./src/js/base/Context.js function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var Context_Context = /*#__PURE__*/function () { /** * @param {jQuery} $note * @param {Object} options */ function Context($note, options) { _classCallCheck(this, Context); this.$note = $note; this.memos = {}; this.modules = {}; this.layoutInfo = {}; this.options = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend(true, {}, options); // init ui with options external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui_template(this.options); this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.initialize(); } /** * create layout and initialize modules and other resources */ _createClass(Context, [{ key: "initialize", value: function initialize() { this.layoutInfo = this.ui.createLayout(this.$note); this._initialize(); this.$note.hide(); return this; } /** * destroy modules and other resources and remove layout */ }, { key: "destroy", value: function destroy() { this._destroy(); this.$note.removeData('summernote'); this.ui.removeLayout(this.$note, this.layoutInfo); } /** * destory modules and other resources and initialize it again */ }, { key: "reset", value: function reset() { var disabled = this.isDisabled(); this.code(dom.emptyPara); this._destroy(); this._initialize(); if (disabled) { this.disable(); } } }, { key: "_initialize", value: function _initialize() { var _this = this; // set own id this.options.id = func.uniqueId(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.now()); // set default container for tooltips, popovers, and dialogs this.options.container = this.options.container || this.layoutInfo.editor; // add optional buttons var buttons = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend({}, this.options.buttons); Object.keys(buttons).forEach(function (key) { _this.memo('button.' + key, buttons[key]); }); var modules = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend({}, this.options.modules, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.plugins || {}); // add and initialize modules Object.keys(modules).forEach(function (key) { _this.module(key, modules[key], true); }); Object.keys(this.modules).forEach(function (key) { _this.initializeModule(key); }); } }, { key: "_destroy", value: function _destroy() { var _this2 = this; // destroy modules with reversed order Object.keys(this.modules).reverse().forEach(function (key) { _this2.removeModule(key); }); Object.keys(this.memos).forEach(function (key) { _this2.removeMemo(key); }); // trigger custom onDestroy callback this.triggerEvent('destroy', this); } }, { key: "code", value: function code(html) { var isActivated = this.invoke('codeview.isActivated'); if (html === undefined) { this.invoke('codeview.sync'); return isActivated ? this.layoutInfo.codable.val() : this.layoutInfo.editable.html(); } else { if (isActivated) { this.invoke('codeview.sync', html); } else { this.layoutInfo.editable.html(html); } this.$note.val(html); this.triggerEvent('change', html, this.layoutInfo.editable); } } }, { key: "isDisabled", value: function isDisabled() { return this.layoutInfo.editable.attr('contenteditable') === 'false'; } }, { key: "enable", value: function enable() { this.layoutInfo.editable.attr('contenteditable', true); this.invoke('toolbar.activate', true); this.triggerEvent('disable', false); this.options.editing = true; } }, { key: "disable", value: function disable() { // close codeview if codeview is opend if (this.invoke('codeview.isActivated')) { this.invoke('codeview.deactivate'); } this.layoutInfo.editable.attr('contenteditable', false); this.options.editing = false; this.invoke('toolbar.deactivate', true); this.triggerEvent('disable', true); } }, { key: "triggerEvent", value: function triggerEvent() { var namespace = lists.head(arguments); var args = lists.tail(lists.from(arguments)); var callback = this.options.callbacks[func.namespaceToCamel(namespace, 'on')]; if (callback) { callback.apply(this.$note[0], args); } this.$note.trigger('summernote.' + namespace, args); } }, { key: "initializeModule", value: function initializeModule(key) { var module = this.modules[key]; module.shouldInitialize = module.shouldInitialize || func.ok; if (!module.shouldInitialize()) { return; } // initialize module if (module.initialize) { module.initialize(); } // attach events if (module.events) { dom.attachEvents(this.$note, module.events); } } }, { key: "module", value: function module(key, ModuleClass, withoutIntialize) { if (arguments.length === 1) { return this.modules[key]; } this.modules[key] = new ModuleClass(this); if (!withoutIntialize) { this.initializeModule(key); } } }, { key: "removeModule", value: function removeModule(key) { var module = this.modules[key]; if (module.shouldInitialize()) { if (module.events) { dom.detachEvents(this.$note, module.events); } if (module.destroy) { module.destroy(); } } delete this.modules[key]; } }, { key: "memo", value: function memo(key, obj) { if (arguments.length === 1) { return this.memos[key]; } this.memos[key] = obj; } }, { key: "removeMemo", value: function removeMemo(key) { if (this.memos[key] && this.memos[key].destroy) { this.memos[key].destroy(); } delete this.memos[key]; } /** * Some buttons need to change their visual style immediately once they get pressed */ }, { key: "createInvokeHandlerAndUpdateState", value: function createInvokeHandlerAndUpdateState(namespace, value) { var _this3 = this; return function (event) { _this3.createInvokeHandler(namespace, value)(event); _this3.invoke('buttons.updateCurrentStyle'); }; } }, { key: "createInvokeHandler", value: function createInvokeHandler(namespace, value) { var _this4 = this; return function (event) { event.preventDefault(); var $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(event.target); _this4.invoke(namespace, value || $target.closest('[data-value]').data('value'), $target); }; } }, { key: "invoke", value: function invoke() { var namespace = lists.head(arguments); var args = lists.tail(lists.from(arguments)); var splits = namespace.split('.'); var hasSeparator = splits.length > 1; var moduleName = hasSeparator && lists.head(splits); var methodName = hasSeparator ? lists.last(splits) : lists.head(splits); var module = this.modules[moduleName || 'editor']; if (!moduleName && this[methodName]) { return this[methodName].apply(this, args); } else if (module && module[methodName] && module.shouldInitialize()) { return module[methodName].apply(module, args); } } }]); return Context; }(); // CONCATENATED MODULE: ./src/js/summernote.js external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.fn.extend({ /** * Summernote API * * @param {Object|String} * @return {this} */ summernote: function summernote() { var type = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.type(lists.head(arguments)); var isExternalAPICalled = type === 'string'; var hasInitOptions = type === 'object'; var options = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend({}, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.options, hasInitOptions ? lists.head(arguments) : {}); // Update options options.langInfo = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend(true, {}, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.lang['en-US'], external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.lang[options.lang]); options.icons = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend(true, {}, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.options.icons, options.icons); options.tooltip = options.tooltip === 'auto' ? !env.isSupportTouch : options.tooltip; this.each(function (idx, note) { var $note = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(note); if (!$note.data('summernote')) { var context = new Context_Context($note, options); $note.data('summernote', context); $note.data('summernote').triggerEvent('init', context.layoutInfo); } }); var $note = this.first(); if ($note.length) { var context = $note.data('summernote'); if (isExternalAPICalled) { return context.invoke.apply(context, lists.from(arguments)); } else if (options.focus) { context.invoke('editor.focus'); } } return this; } }); // CONCATENATED MODULE: ./src/js/base/core/range.js function range_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function range_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function range_createClass(Constructor, protoProps, staticProps) { if (protoProps) range_defineProperties(Constructor.prototype, protoProps); if (staticProps) range_defineProperties(Constructor, staticProps); return Constructor; } /** * return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js * * @param {TextRange} textRange * @param {Boolean} isStart * @return {BoundaryPoint} * * @see http://msdn.microsoft.com/en-us/library/ie/ms535872(v=vs.85).aspx */ function textRangeToPoint(textRange, isStart) { var container = textRange.parentElement(); var offset; var tester = document.body.createTextRange(); var prevContainer; var childNodes = lists.from(container.childNodes); for (offset = 0; offset < childNodes.length; offset++) { if (dom.isText(childNodes[offset])) { continue; } tester.moveToElementText(childNodes[offset]); if (tester.compareEndPoints('StartToStart', textRange) >= 0) { break; } prevContainer = childNodes[offset]; } if (offset !== 0 && dom.isText(childNodes[offset - 1])) { var textRangeStart = document.body.createTextRange(); var curTextNode = null; textRangeStart.moveToElementText(prevContainer || container); textRangeStart.collapse(!prevContainer); curTextNode = prevContainer ? prevContainer.nextSibling : container.firstChild; var pointTester = textRange.duplicate(); pointTester.setEndPoint('StartToStart', textRangeStart); var textCount = pointTester.text.replace(/[\r\n]/g, '').length; while (textCount > curTextNode.nodeValue.length && curTextNode.nextSibling) { textCount -= curTextNode.nodeValue.length; curTextNode = curTextNode.nextSibling; } // [workaround] enforce IE to re-reference curTextNode, hack var dummy = curTextNode.nodeValue; // eslint-disable-line if (isStart && curTextNode.nextSibling && dom.isText(curTextNode.nextSibling) && textCount === curTextNode.nodeValue.length) { textCount -= curTextNode.nodeValue.length; curTextNode = curTextNode.nextSibling; } container = curTextNode; offset = textCount; } return { cont: container, offset: offset }; } /** * return TextRange from boundary point (inspired by google closure-library) * @param {BoundaryPoint} point * @return {TextRange} */ function pointToTextRange(point) { var textRangeInfo = function textRangeInfo(container, offset) { var node, isCollapseToStart; if (dom.isText(container)) { var prevTextNodes = dom.listPrev(container, func.not(dom.isText)); var prevContainer = lists.last(prevTextNodes).previousSibling; node = prevContainer || container.parentNode; offset += lists.sum(lists.tail(prevTextNodes), dom.nodeLength); isCollapseToStart = !prevContainer; } else { node = container.childNodes[offset] || container; if (dom.isText(node)) { return textRangeInfo(node, 0); } offset = 0; isCollapseToStart = false; } return { node: node, collapseToStart: isCollapseToStart, offset: offset }; }; var textRange = document.body.createTextRange(); var info = textRangeInfo(point.node, point.offset); textRange.moveToElementText(info.node); textRange.collapse(info.collapseToStart); textRange.moveStart('character', info.offset); return textRange; } /** * Wrapped Range * * @constructor * @param {Node} sc - start container * @param {Number} so - start offset * @param {Node} ec - end container * @param {Number} eo - end offset */ var range_WrappedRange = /*#__PURE__*/function () { function WrappedRange(sc, so, ec, eo) { range_classCallCheck(this, WrappedRange); this.sc = sc; this.so = so; this.ec = ec; this.eo = eo; // isOnEditable: judge whether range is on editable or not this.isOnEditable = this.makeIsOn(dom.isEditable); // isOnList: judge whether range is on list node or not this.isOnList = this.makeIsOn(dom.isList); // isOnAnchor: judge whether range is on anchor node or not this.isOnAnchor = this.makeIsOn(dom.isAnchor); // isOnCell: judge whether range is on cell node or not this.isOnCell = this.makeIsOn(dom.isCell); // isOnData: judge whether range is on data node or not this.isOnData = this.makeIsOn(dom.isData); } // nativeRange: get nativeRange from sc, so, ec, eo range_createClass(WrappedRange, [{ key: "nativeRange", value: function nativeRange() { if (env.isW3CRangeSupport) { var w3cRange = document.createRange(); w3cRange.setStart(this.sc, this.so); w3cRange.setEnd(this.ec, this.eo); return w3cRange; } else { var textRange = pointToTextRange({ node: this.sc, offset: this.so }); textRange.setEndPoint('EndToEnd', pointToTextRange({ node: this.ec, offset: this.eo })); return textRange; } } }, { key: "getPoints", value: function getPoints() { return { sc: this.sc, so: this.so, ec: this.ec, eo: this.eo }; } }, { key: "getStartPoint", value: function getStartPoint() { return { node: this.sc, offset: this.so }; } }, { key: "getEndPoint", value: function getEndPoint() { return { node: this.ec, offset: this.eo }; } /** * select update visible range */ }, { key: "select", value: function select() { var nativeRng = this.nativeRange(); if (env.isW3CRangeSupport) { var selection = document.getSelection(); if (selection.rangeCount > 0) { selection.removeAllRanges(); } selection.addRange(nativeRng); } else { nativeRng.select(); } return this; } /** * Moves the scrollbar to start container(sc) of current range * * @return {WrappedRange} */ }, { key: "scrollIntoView", value: function scrollIntoView(container) { var height = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(container).height(); if (container.scrollTop + height < this.sc.offsetTop) { container.scrollTop += Math.abs(container.scrollTop + height - this.sc.offsetTop); } return this; } /** * @return {WrappedRange} */ }, { key: "normalize", value: function normalize() { /** * @param {BoundaryPoint} point * @param {Boolean} isLeftToRight - true: prefer to choose right node * - false: prefer to choose left node * @return {BoundaryPoint} */ var getVisiblePoint = function getVisiblePoint(point, isLeftToRight) { if (!point) { return point; } // Just use the given point [XXX:Adhoc] // - case 01. if the point is on the middle of the node // - case 02. if the point is on the right edge and prefer to choose left node // - case 03. if the point is on the left edge and prefer to choose right node // - case 04. if the point is on the right edge and prefer to choose right node but the node is void // - case 05. if the point is on the left edge and prefer to choose left node but the node is void // - case 06. if the point is on the block node and there is no children if (dom.isVisiblePoint(point)) { if (!dom.isEdgePoint(point) || dom.isRightEdgePoint(point) && !isLeftToRight || dom.isLeftEdgePoint(point) && isLeftToRight || dom.isRightEdgePoint(point) && isLeftToRight && dom.isVoid(point.node.nextSibling) || dom.isLeftEdgePoint(point) && !isLeftToRight && dom.isVoid(point.node.previousSibling) || dom.isBlock(point.node) && dom.isEmpty(point.node)) { return point; } } // point on block's edge var block = dom.ancestor(point.node, dom.isBlock); var hasRightNode = false; if (!hasRightNode) { var prevPoint = dom.prevPoint(point) || { node: null }; hasRightNode = (dom.isLeftEdgePointOf(point, block) || dom.isVoid(prevPoint.node)) && !isLeftToRight; } var hasLeftNode = false; if (!hasLeftNode) { var _nextPoint = dom.nextPoint(point) || { node: null }; hasLeftNode = (dom.isRightEdgePointOf(point, block) || dom.isVoid(_nextPoint.node)) && isLeftToRight; } if (hasRightNode || hasLeftNode) { // returns point already on visible point if (dom.isVisiblePoint(point)) { return point; } // reverse direction isLeftToRight = !isLeftToRight; } var nextPoint = isLeftToRight ? dom.nextPointUntil(dom.nextPoint(point), dom.isVisiblePoint) : dom.prevPointUntil(dom.prevPoint(point), dom.isVisiblePoint); return nextPoint || point; }; var endPoint = getVisiblePoint(this.getEndPoint(), false); var startPoint = this.isCollapsed() ? endPoint : getVisiblePoint(this.getStartPoint(), true); return new WrappedRange(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset); } /** * returns matched nodes on range * * @param {Function} [pred] - predicate function * @param {Object} [options] * @param {Boolean} [options.includeAncestor] * @param {Boolean} [options.fullyContains] * @return {Node[]} */ }, { key: "nodes", value: function nodes(pred, options) { pred = pred || func.ok; var includeAncestor = options && options.includeAncestor; var fullyContains = options && options.fullyContains; // TODO compare points and sort var startPoint = this.getStartPoint(); var endPoint = this.getEndPoint(); var nodes = []; var leftEdgeNodes = []; dom.walkPoint(startPoint, endPoint, function (point) { if (dom.isEditable(point.node)) { return; } var node; if (fullyContains) { if (dom.isLeftEdgePoint(point)) { leftEdgeNodes.push(point.node); } if (dom.isRightEdgePoint(point) && lists.contains(leftEdgeNodes, point.node)) { node = point.node; } } else if (includeAncestor) { node = dom.ancestor(point.node, pred); } else { node = point.node; } if (node && pred(node)) { nodes.push(node); } }, true); return lists.unique(nodes); } /** * returns commonAncestor of range * @return {Element} - commonAncestor */ }, { key: "commonAncestor", value: function commonAncestor() { return dom.commonAncestor(this.sc, this.ec); } /** * returns expanded range by pred * * @param {Function} pred - predicate function * @return {WrappedRange} */ }, { key: "expand", value: function expand(pred) { var startAncestor = dom.ancestor(this.sc, pred); var endAncestor = dom.ancestor(this.ec, pred); if (!startAncestor && !endAncestor) { return new WrappedRange(this.sc, this.so, this.ec, this.eo); } var boundaryPoints = this.getPoints(); if (startAncestor) { boundaryPoints.sc = startAncestor; boundaryPoints.so = 0; } if (endAncestor) { boundaryPoints.ec = endAncestor; boundaryPoints.eo = dom.nodeLength(endAncestor); } return new WrappedRange(boundaryPoints.sc, boundaryPoints.so, boundaryPoints.ec, boundaryPoints.eo); } /** * @param {Boolean} isCollapseToStart * @return {WrappedRange} */ }, { key: "collapse", value: function collapse(isCollapseToStart) { if (isCollapseToStart) { return new WrappedRange(this.sc, this.so, this.sc, this.so); } else { return new WrappedRange(this.ec, this.eo, this.ec, this.eo); } } /** * splitText on range */ }, { key: "splitText", value: function splitText() { var isSameContainer = this.sc === this.ec; var boundaryPoints = this.getPoints(); if (dom.isText(this.ec) && !dom.isEdgePoint(this.getEndPoint())) { this.ec.splitText(this.eo); } if (dom.isText(this.sc) && !dom.isEdgePoint(this.getStartPoint())) { boundaryPoints.sc = this.sc.splitText(this.so); boundaryPoints.so = 0; if (isSameContainer) { boundaryPoints.ec = boundaryPoints.sc; boundaryPoints.eo = this.eo - this.so; } } return new WrappedRange(boundaryPoints.sc, boundaryPoints.so, boundaryPoints.ec, boundaryPoints.eo); } /** * delete contents on range * @return {WrappedRange} */ }, { key: "deleteContents", value: function deleteContents() { if (this.isCollapsed()) { return this; } var rng = this.splitText(); var nodes = rng.nodes(null, { fullyContains: true }); // find new cursor point var point = dom.prevPointUntil(rng.getStartPoint(), function (point) { return !lists.contains(nodes, point.node); }); var emptyParents = []; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(nodes, function (idx, node) { // find empty parents var parent = node.parentNode; if (point.node !== parent && dom.nodeLength(parent) === 1) { emptyParents.push(parent); } dom.remove(node, false); }); // remove empty parents external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(emptyParents, function (idx, node) { dom.remove(node, false); }); return new WrappedRange(point.node, point.offset, point.node, point.offset).normalize(); } /** * makeIsOn: return isOn(pred) function */ }, { key: "makeIsOn", value: function makeIsOn(pred) { return function () { var ancestor = dom.ancestor(this.sc, pred); return !!ancestor && ancestor === dom.ancestor(this.ec, pred); }; } /** * @param {Function} pred * @return {Boolean} */ }, { key: "isLeftEdgeOf", value: function isLeftEdgeOf(pred) { if (!dom.isLeftEdgePoint(this.getStartPoint())) { return false; } var node = dom.ancestor(this.sc, pred); return node && dom.isLeftEdgeOf(this.sc, node); } /** * returns whether range was collapsed or not */ }, { key: "isCollapsed", value: function isCollapsed() { return this.sc === this.ec && this.so === this.eo; } /** * wrap inline nodes which children of body with paragraph * * @return {WrappedRange} */ }, { key: "wrapBodyInlineWithPara", value: function wrapBodyInlineWithPara() { if (dom.isBodyContainer(this.sc) && dom.isEmpty(this.sc)) { this.sc.innerHTML = dom.emptyPara; return new WrappedRange(this.sc.firstChild, 0, this.sc.firstChild, 0); } /** * [workaround] firefox often create range on not visible point. so normalize here. * - firefox: |

                text

                | * - chrome:

                |text|

                */ var rng = this.normalize(); if (dom.isParaInline(this.sc) || dom.isPara(this.sc)) { return rng; } // find inline top ancestor var topAncestor; if (dom.isInline(rng.sc)) { var ancestors = dom.listAncestor(rng.sc, func.not(dom.isInline)); topAncestor = lists.last(ancestors); if (!dom.isInline(topAncestor)) { topAncestor = ancestors[ancestors.length - 2] || rng.sc.childNodes[rng.so]; } } else { topAncestor = rng.sc.childNodes[rng.so > 0 ? rng.so - 1 : 0]; } if (topAncestor) { // siblings not in paragraph var inlineSiblings = dom.listPrev(topAncestor, dom.isParaInline).reverse(); inlineSiblings = inlineSiblings.concat(dom.listNext(topAncestor.nextSibling, dom.isParaInline)); // wrap with paragraph if (inlineSiblings.length) { var para = dom.wrap(lists.head(inlineSiblings), 'p'); dom.appendChildNodes(para, lists.tail(inlineSiblings)); } } return this.normalize(); } /** * insert node at current cursor * * @param {Node} node * @return {Node} */ }, { key: "insertNode", value: function insertNode(node) { var rng = this; if (dom.isText(node) || dom.isInline(node)) { rng = this.wrapBodyInlineWithPara().deleteContents(); } var info = dom.splitPoint(rng.getStartPoint(), dom.isInline(node)); if (info.rightNode) { info.rightNode.parentNode.insertBefore(node, info.rightNode); if (dom.isEmpty(info.rightNode) && dom.isPara(node)) { info.rightNode.parentNode.removeChild(info.rightNode); } } else { info.container.appendChild(node); } return node; } /** * insert html at current cursor */ }, { key: "pasteHTML", value: function pasteHTML(markup) { markup = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.trim(markup); var contentsContainer = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
                ').html(markup)[0]; var childNodes = lists.from(contentsContainer.childNodes); // const rng = this.wrapBodyInlineWithPara().deleteContents(); var rng = this; var reversed = false; if (rng.so >= 0) { childNodes = childNodes.reverse(); reversed = true; } childNodes = childNodes.map(function (childNode) { return rng.insertNode(childNode); }); if (reversed) { childNodes = childNodes.reverse(); } return childNodes; } /** * returns text in range * * @return {String} */ }, { key: "toString", value: function toString() { var nativeRng = this.nativeRange(); return env.isW3CRangeSupport ? nativeRng.toString() : nativeRng.text; } /** * returns range for word before cursor * * @param {Boolean} [findAfter] - find after cursor, default: false * @return {WrappedRange} */ }, { key: "getWordRange", value: function getWordRange(findAfter) { var endPoint = this.getEndPoint(); if (!dom.isCharPoint(endPoint)) { return this; } var startPoint = dom.prevPointUntil(endPoint, function (point) { return !dom.isCharPoint(point); }); if (findAfter) { endPoint = dom.nextPointUntil(endPoint, function (point) { return !dom.isCharPoint(point); }); } return new WrappedRange(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset); } /** * returns range for words before cursor * * @param {Boolean} [findAfter] - find after cursor, default: false * @return {WrappedRange} */ }, { key: "getWordsRange", value: function getWordsRange(findAfter) { var endPoint = this.getEndPoint(); var isNotTextPoint = function isNotTextPoint(point) { return !dom.isCharPoint(point) && !dom.isSpacePoint(point); }; if (isNotTextPoint(endPoint)) { return this; } var startPoint = dom.prevPointUntil(endPoint, isNotTextPoint); if (findAfter) { endPoint = dom.nextPointUntil(endPoint, isNotTextPoint); } return new WrappedRange(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset); } /** * returns range for words before cursor that match with a Regex * * example: * range: 'hi @Peter Pan' * regex: '/@[a-z ]+/i' * return range: '@Peter Pan' * * @param {RegExp} [regex] * @return {WrappedRange|null} */ }, { key: "getWordsMatchRange", value: function getWordsMatchRange(regex) { var endPoint = this.getEndPoint(); var startPoint = dom.prevPointUntil(endPoint, function (point) { if (!dom.isCharPoint(point) && !dom.isSpacePoint(point)) { return true; } var rng = new WrappedRange(point.node, point.offset, endPoint.node, endPoint.offset); var result = regex.exec(rng.toString()); return result && result.index === 0; }); var rng = new WrappedRange(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset); var text = rng.toString(); var result = regex.exec(text); if (result && result[0].length === text.length) { return rng; } else { return null; } } /** * create offsetPath bookmark * * @param {Node} editable */ }, { key: "bookmark", value: function bookmark(editable) { return { s: { path: dom.makeOffsetPath(editable, this.sc), offset: this.so }, e: { path: dom.makeOffsetPath(editable, this.ec), offset: this.eo } }; } /** * create offsetPath bookmark base on paragraph * * @param {Node[]} paras */ }, { key: "paraBookmark", value: function paraBookmark(paras) { return { s: { path: lists.tail(dom.makeOffsetPath(lists.head(paras), this.sc)), offset: this.so }, e: { path: lists.tail(dom.makeOffsetPath(lists.last(paras), this.ec)), offset: this.eo } }; } /** * getClientRects * @return {Rect[]} */ }, { key: "getClientRects", value: function getClientRects() { var nativeRng = this.nativeRange(); return nativeRng.getClientRects(); } }]); return WrappedRange; }(); /** * Data structure * * BoundaryPoint: a point of dom tree * * BoundaryPoints: two boundaryPoints corresponding to the start and the end of the Range * * See to http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Position */ /* harmony default export */ var range = ({ /** * create Range Object From arguments or Browser Selection * * @param {Node} sc - start container * @param {Number} so - start offset * @param {Node} ec - end container * @param {Number} eo - end offset * @return {WrappedRange} */ create: function create(sc, so, ec, eo) { if (arguments.length === 4) { return new range_WrappedRange(sc, so, ec, eo); } else if (arguments.length === 2) { // collapsed ec = sc; eo = so; return new range_WrappedRange(sc, so, ec, eo); } else { var wrappedRange = this.createFromSelection(); if (!wrappedRange && arguments.length === 1) { var bodyElement = arguments[0]; if (dom.isEditable(bodyElement)) { bodyElement = bodyElement.lastChild; } return this.createFromBodyElement(bodyElement, dom.emptyPara === arguments[0].innerHTML); } return wrappedRange; } }, createFromBodyElement: function createFromBodyElement(bodyElement) { var isCollapseToStart = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var wrappedRange = this.createFromNode(bodyElement); return wrappedRange.collapse(isCollapseToStart); }, createFromSelection: function createFromSelection() { var sc, so, ec, eo; if (env.isW3CRangeSupport) { var selection = document.getSelection(); if (!selection || selection.rangeCount === 0) { return null; } else if (dom.isBody(selection.anchorNode)) { // Firefox: returns entire body as range on initialization. // We won't never need it. return null; } var nativeRng = selection.getRangeAt(0); sc = nativeRng.startContainer; so = nativeRng.startOffset; ec = nativeRng.endContainer; eo = nativeRng.endOffset; } else { // IE8: TextRange var textRange = document.selection.createRange(); var textRangeEnd = textRange.duplicate(); textRangeEnd.collapse(false); var textRangeStart = textRange; textRangeStart.collapse(true); var startPoint = textRangeToPoint(textRangeStart, true); var endPoint = textRangeToPoint(textRangeEnd, false); // same visible point case: range was collapsed. if (dom.isText(startPoint.node) && dom.isLeftEdgePoint(startPoint) && dom.isTextNode(endPoint.node) && dom.isRightEdgePoint(endPoint) && endPoint.node.nextSibling === startPoint.node) { startPoint = endPoint; } sc = startPoint.cont; so = startPoint.offset; ec = endPoint.cont; eo = endPoint.offset; } return new range_WrappedRange(sc, so, ec, eo); }, /** * @method * * create WrappedRange from node * * @param {Node} node * @return {WrappedRange} */ createFromNode: function createFromNode(node) { var sc = node; var so = 0; var ec = node; var eo = dom.nodeLength(ec); // browsers can't target a picture or void node if (dom.isVoid(sc)) { so = dom.listPrev(sc).length - 1; sc = sc.parentNode; } if (dom.isBR(ec)) { eo = dom.listPrev(ec).length - 1; ec = ec.parentNode; } else if (dom.isVoid(ec)) { eo = dom.listPrev(ec).length; ec = ec.parentNode; } return this.create(sc, so, ec, eo); }, /** * create WrappedRange from node after position * * @param {Node} node * @return {WrappedRange} */ createFromNodeBefore: function createFromNodeBefore(node) { return this.createFromNode(node).collapse(true); }, /** * create WrappedRange from node after position * * @param {Node} node * @return {WrappedRange} */ createFromNodeAfter: function createFromNodeAfter(node) { return this.createFromNode(node).collapse(); }, /** * @method * * create WrappedRange from bookmark * * @param {Node} editable * @param {Object} bookmark * @return {WrappedRange} */ createFromBookmark: function createFromBookmark(editable, bookmark) { var sc = dom.fromOffsetPath(editable, bookmark.s.path); var so = bookmark.s.offset; var ec = dom.fromOffsetPath(editable, bookmark.e.path); var eo = bookmark.e.offset; return new range_WrappedRange(sc, so, ec, eo); }, /** * @method * * create WrappedRange from paraBookmark * * @param {Object} bookmark * @param {Node[]} paras * @return {WrappedRange} */ createFromParaBookmark: function createFromParaBookmark(bookmark, paras) { var so = bookmark.s.offset; var eo = bookmark.e.offset; var sc = dom.fromOffsetPath(lists.head(paras), bookmark.s.path); var ec = dom.fromOffsetPath(lists.last(paras), bookmark.e.path); return new range_WrappedRange(sc, so, ec, eo); } }); // CONCATENATED MODULE: ./src/js/base/core/key.js var KEY_MAP = { 'BACKSPACE': 8, 'TAB': 9, 'ENTER': 13, 'ESCAPE': 27, 'SPACE': 32, 'DELETE': 46, // Arrow 'LEFT': 37, 'UP': 38, 'RIGHT': 39, 'DOWN': 40, // Number: 0-9 'NUM0': 48, 'NUM1': 49, 'NUM2': 50, 'NUM3': 51, 'NUM4': 52, 'NUM5': 53, 'NUM6': 54, 'NUM7': 55, 'NUM8': 56, // Alphabet: a-z 'B': 66, 'E': 69, 'I': 73, 'J': 74, 'K': 75, 'L': 76, 'R': 82, 'S': 83, 'U': 85, 'V': 86, 'Y': 89, 'Z': 90, 'SLASH': 191, 'LEFTBRACKET': 219, 'BACKSLASH': 220, 'RIGHTBRACKET': 221, // Navigation 'HOME': 36, 'END': 35, 'PAGEUP': 33, 'PAGEDOWN': 34 }; /** * @class core.key * * Object for keycodes. * * @singleton * @alternateClassName key */ /* harmony default export */ var core_key = ({ /** * @method isEdit * * @param {Number} keyCode * @return {Boolean} */ isEdit: function isEdit(keyCode) { return lists.contains([KEY_MAP.BACKSPACE, KEY_MAP.TAB, KEY_MAP.ENTER, KEY_MAP.SPACE, KEY_MAP.DELETE], keyCode); }, /** * @method isMove * * @param {Number} keyCode * @return {Boolean} */ isMove: function isMove(keyCode) { return lists.contains([KEY_MAP.LEFT, KEY_MAP.UP, KEY_MAP.RIGHT, KEY_MAP.DOWN], keyCode); }, /** * @method isNavigation * * @param {Number} keyCode * @return {Boolean} */ isNavigation: function isNavigation(keyCode) { return lists.contains([KEY_MAP.HOME, KEY_MAP.END, KEY_MAP.PAGEUP, KEY_MAP.PAGEDOWN], keyCode); }, /** * @property {Object} nameFromCode * @property {String} nameFromCode.8 "BACKSPACE" */ nameFromCode: func.invertObject(KEY_MAP), code: KEY_MAP }); // CONCATENATED MODULE: ./src/js/base/core/async.js /** * @method readFileAsDataURL * * read contents of file as representing URL * * @param {File} file * @return {Promise} - then: dataUrl */ function readFileAsDataURL(file) { return external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.Deferred(function (deferred) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend(new FileReader(), { onload: function onload(e) { var dataURL = e.target.result; deferred.resolve(dataURL); }, onerror: function onerror(err) { deferred.reject(err); } }).readAsDataURL(file); }).promise(); } /** * @method createImage * * create `` from url string * * @param {String} url * @return {Promise} - then: $image */ function createImage(url) { return external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.Deferred(function (deferred) { var $img = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(''); $img.one('load', function () { $img.off('error abort'); deferred.resolve($img); }).one('error abort', function () { $img.off('load').detach(); deferred.reject($img); }).css({ display: 'none' }).appendTo(document.body).attr('src', url); }).promise(); } // CONCATENATED MODULE: ./src/js/base/editing/History.js function History_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function History_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function History_createClass(Constructor, protoProps, staticProps) { if (protoProps) History_defineProperties(Constructor.prototype, protoProps); if (staticProps) History_defineProperties(Constructor, staticProps); return Constructor; } var History_History = /*#__PURE__*/function () { function History(context) { History_classCallCheck(this, History); this.stack = []; this.stackOffset = -1; this.context = context; this.$editable = context.layoutInfo.editable; this.editable = this.$editable[0]; } History_createClass(History, [{ key: "makeSnapshot", value: function makeSnapshot() { var rng = range.create(this.editable); var emptyBookmark = { s: { path: [], offset: 0 }, e: { path: [], offset: 0 } }; return { contents: this.$editable.html(), bookmark: rng && rng.isOnEditable() ? rng.bookmark(this.editable) : emptyBookmark }; } }, { key: "applySnapshot", value: function applySnapshot(snapshot) { if (snapshot.contents !== null) { this.$editable.html(snapshot.contents); } if (snapshot.bookmark !== null) { range.createFromBookmark(this.editable, snapshot.bookmark).select(); } } /** * @method rewind * Rewinds the history stack back to the first snapshot taken. * Leaves the stack intact, so that "Redo" can still be used. */ }, { key: "rewind", value: function rewind() { // Create snap shot if not yet recorded if (this.$editable.html() !== this.stack[this.stackOffset].contents) { this.recordUndo(); } // Return to the first available snapshot. this.stackOffset = 0; // Apply that snapshot. this.applySnapshot(this.stack[this.stackOffset]); } /** * @method commit * Resets history stack, but keeps current editor's content. */ }, { key: "commit", value: function commit() { // Clear the stack. this.stack = []; // Restore stackOffset to its original value. this.stackOffset = -1; // Record our first snapshot (of nothing). this.recordUndo(); } /** * @method reset * Resets the history stack completely; reverting to an empty editor. */ }, { key: "reset", value: function reset() { // Clear the stack. this.stack = []; // Restore stackOffset to its original value. this.stackOffset = -1; // Clear the editable area. this.$editable.html(''); // Record our first snapshot (of nothing). this.recordUndo(); } /** * undo */ }, { key: "undo", value: function undo() { // Create snap shot if not yet recorded if (this.$editable.html() !== this.stack[this.stackOffset].contents) { this.recordUndo(); } if (this.stackOffset > 0) { this.stackOffset--; this.applySnapshot(this.stack[this.stackOffset]); } } /** * redo */ }, { key: "redo", value: function redo() { if (this.stack.length - 1 > this.stackOffset) { this.stackOffset++; this.applySnapshot(this.stack[this.stackOffset]); } } /** * recorded undo */ }, { key: "recordUndo", value: function recordUndo() { this.stackOffset++; // Wash out stack after stackOffset if (this.stack.length > this.stackOffset) { this.stack = this.stack.slice(0, this.stackOffset); } // Create new snapshot and push it to the end this.stack.push(this.makeSnapshot()); // If the stack size reachs to the limit, then slice it if (this.stack.length > this.context.options.historyLimit) { this.stack.shift(); this.stackOffset -= 1; } } }]); return History; }(); // CONCATENATED MODULE: ./src/js/base/editing/Style.js function Style_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Style_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Style_createClass(Constructor, protoProps, staticProps) { if (protoProps) Style_defineProperties(Constructor.prototype, protoProps); if (staticProps) Style_defineProperties(Constructor, staticProps); return Constructor; } var Style_Style = /*#__PURE__*/function () { function Style() { Style_classCallCheck(this, Style); } Style_createClass(Style, [{ key: "jQueryCSS", /** * @method jQueryCSS * * [workaround] for old jQuery * passing an array of style properties to .css() * will result in an object of property-value pairs. * (compability with version < 1.9) * * @private * @param {jQuery} $obj * @param {Array} propertyNames - An array of one or more CSS properties. * @return {Object} */ value: function jQueryCSS($obj, propertyNames) { if (env.jqueryVersion < 1.9) { var result = {}; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(propertyNames, function (idx, propertyName) { result[propertyName] = $obj.css(propertyName); }); return result; } return $obj.css(propertyNames); } /** * returns style object from node * * @param {jQuery} $node * @return {Object} */ }, { key: "fromNode", value: function fromNode($node) { var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height']; var styleInfo = this.jQueryCSS($node, properties) || {}; var fontSize = $node[0].style.fontSize || styleInfo['font-size']; styleInfo['font-size'] = parseInt(fontSize, 10); styleInfo['font-size-unit'] = fontSize.match(/[a-z%]+$/); return styleInfo; } /** * paragraph level style * * @param {WrappedRange} rng * @param {Object} styleInfo */ }, { key: "stylePara", value: function stylePara(rng, styleInfo) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(rng.nodes(dom.isPara, { includeAncestor: true }), function (idx, para) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(para).css(styleInfo); }); } /** * insert and returns styleNodes on range. * * @param {WrappedRange} rng * @param {Object} [options] - options for styleNodes * @param {String} [options.nodeName] - default: `SPAN` * @param {Boolean} [options.expandClosestSibling] - default: `false` * @param {Boolean} [options.onlyPartialContains] - default: `false` * @return {Node[]} */ }, { key: "styleNodes", value: function styleNodes(rng, options) { rng = rng.splitText(); var nodeName = options && options.nodeName || 'SPAN'; var expandClosestSibling = !!(options && options.expandClosestSibling); var onlyPartialContains = !!(options && options.onlyPartialContains); if (rng.isCollapsed()) { return [rng.insertNode(dom.create(nodeName))]; } var pred = dom.makePredByNodeName(nodeName); var nodes = rng.nodes(dom.isText, { fullyContains: true }).map(function (text) { return dom.singleChildAncestor(text, pred) || dom.wrap(text, nodeName); }); if (expandClosestSibling) { if (onlyPartialContains) { var nodesInRange = rng.nodes(); // compose with partial contains predication pred = func.and(pred, function (node) { return lists.contains(nodesInRange, node); }); } return nodes.map(function (node) { var siblings = dom.withClosestSiblings(node, pred); var head = lists.head(siblings); var tails = lists.tail(siblings); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(tails, function (idx, elem) { dom.appendChildNodes(head, elem.childNodes); dom.remove(elem); }); return lists.head(siblings); }); } else { return nodes; } } /** * get current style on cursor * * @param {WrappedRange} rng * @return {Object} - object contains style properties. */ }, { key: "current", value: function current(rng) { var $cont = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(!dom.isElement(rng.sc) ? rng.sc.parentNode : rng.sc); var styleInfo = this.fromNode($cont); // document.queryCommandState for toggle state // [workaround] prevent Firefox nsresult: "0x80004005 (NS_ERROR_FAILURE)" try { styleInfo = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.extend(styleInfo, { 'font-bold': document.queryCommandState('bold') ? 'bold' : 'normal', 'font-italic': document.queryCommandState('italic') ? 'italic' : 'normal', 'font-underline': document.queryCommandState('underline') ? 'underline' : 'normal', 'font-subscript': document.queryCommandState('subscript') ? 'subscript' : 'normal', 'font-superscript': document.queryCommandState('superscript') ? 'superscript' : 'normal', 'font-strikethrough': document.queryCommandState('strikethrough') ? 'strikethrough' : 'normal', 'font-family': document.queryCommandValue('fontname') || styleInfo['font-family'] }); } catch (e) {} // eslint-disable-next-line // list-style-type to list-style(unordered, ordered) if (!rng.isOnList()) { styleInfo['list-style'] = 'none'; } else { var orderedTypes = ['circle', 'disc', 'disc-leading-zero', 'square']; var isUnordered = orderedTypes.indexOf(styleInfo['list-style-type']) > -1; styleInfo['list-style'] = isUnordered ? 'unordered' : 'ordered'; } var para = dom.ancestor(rng.sc, dom.isPara); if (para && para.style['line-height']) { styleInfo['line-height'] = para.style.lineHeight; } else { var lineHeight = parseInt(styleInfo['line-height'], 10) / parseInt(styleInfo['font-size'], 10); styleInfo['line-height'] = lineHeight.toFixed(1); } styleInfo.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor); styleInfo.ancestors = dom.listAncestor(rng.sc, dom.isEditable); styleInfo.range = rng; return styleInfo; } }]); return Style; }(); // CONCATENATED MODULE: ./src/js/base/editing/Bullet.js function Bullet_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Bullet_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Bullet_createClass(Constructor, protoProps, staticProps) { if (protoProps) Bullet_defineProperties(Constructor.prototype, protoProps); if (staticProps) Bullet_defineProperties(Constructor, staticProps); return Constructor; } var Bullet_Bullet = /*#__PURE__*/function () { function Bullet() { Bullet_classCallCheck(this, Bullet); } Bullet_createClass(Bullet, [{ key: "insertOrderedList", /** * toggle ordered list */ value: function insertOrderedList(editable) { this.toggleList('OL', editable); } /** * toggle unordered list */ }, { key: "insertUnorderedList", value: function insertUnorderedList(editable) { this.toggleList('UL', editable); } /** * indent */ }, { key: "indent", value: function indent(editable) { var _this = this; var rng = range.create(editable).wrapBodyInlineWithPara(); var paras = rng.nodes(dom.isPara, { includeAncestor: true }); var clustereds = lists.clusterBy(paras, func.peq2('parentNode')); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(clustereds, function (idx, paras) { var head = lists.head(paras); if (dom.isLi(head)) { var previousList = _this.findList(head.previousSibling); if (previousList) { paras.map(function (para) { return previousList.appendChild(para); }); } else { _this.wrapList(paras, head.parentNode.nodeName); paras.map(function (para) { return para.parentNode; }).map(function (para) { return _this.appendToPrevious(para); }); } } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(paras, function (idx, para) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(para).css('marginLeft', function (idx, val) { return (parseInt(val, 10) || 0) + 25; }); }); } }); rng.select(); } /** * outdent */ }, { key: "outdent", value: function outdent(editable) { var _this2 = this; var rng = range.create(editable).wrapBodyInlineWithPara(); var paras = rng.nodes(dom.isPara, { includeAncestor: true }); var clustereds = lists.clusterBy(paras, func.peq2('parentNode')); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(clustereds, function (idx, paras) { var head = lists.head(paras); if (dom.isLi(head)) { _this2.releaseList([paras]); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(paras, function (idx, para) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(para).css('marginLeft', function (idx, val) { val = parseInt(val, 10) || 0; return val > 25 ? val - 25 : ''; }); }); } }); rng.select(); } /** * toggle list * * @param {String} listName - OL or UL */ }, { key: "toggleList", value: function toggleList(listName, editable) { var _this3 = this; var rng = range.create(editable).wrapBodyInlineWithPara(); var paras = rng.nodes(dom.isPara, { includeAncestor: true }); var bookmark = rng.paraBookmark(paras); var clustereds = lists.clusterBy(paras, func.peq2('parentNode')); // paragraph to list if (lists.find(paras, dom.isPurePara)) { var wrappedParas = []; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(clustereds, function (idx, paras) { wrappedParas = wrappedParas.concat(_this3.wrapList(paras, listName)); }); paras = wrappedParas; // list to paragraph or change list style } else { var diffLists = rng.nodes(dom.isList, { includeAncestor: true }).filter(function (listNode) { return !external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.nodeName(listNode, listName); }); if (diffLists.length) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(diffLists, function (idx, listNode) { dom.replace(listNode, listName); }); } else { paras = this.releaseList(clustereds, true); } } range.createFromParaBookmark(bookmark, paras).select(); } /** * @param {Node[]} paras * @param {String} listName * @return {Node[]} */ }, { key: "wrapList", value: function wrapList(paras, listName) { var head = lists.head(paras); var last = lists.last(paras); var prevList = dom.isList(head.previousSibling) && head.previousSibling; var nextList = dom.isList(last.nextSibling) && last.nextSibling; var listNode = prevList || dom.insertAfter(dom.create(listName || 'UL'), last); // P to LI paras = paras.map(function (para) { return dom.isPurePara(para) ? dom.replace(para, 'LI') : para; }); // append to list(
                  ,
                    ) dom.appendChildNodes(listNode, paras); if (nextList) { dom.appendChildNodes(listNode, lists.from(nextList.childNodes)); dom.remove(nextList); } return paras; } /** * @method releaseList * * @param {Array[]} clustereds * @param {Boolean} isEscapseToBody * @return {Node[]} */ }, { key: "releaseList", value: function releaseList(clustereds, isEscapseToBody) { var _this4 = this; var releasedParas = []; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(clustereds, function (idx, paras) { var head = lists.head(paras); var last = lists.last(paras); var headList = isEscapseToBody ? dom.lastAncestor(head, dom.isList) : head.parentNode; var parentItem = headList.parentNode; if (headList.parentNode.nodeName === 'LI') { paras.map(function (para) { var newList = _this4.findNextSiblings(para); if (parentItem.nextSibling) { parentItem.parentNode.insertBefore(para, parentItem.nextSibling); } else { parentItem.parentNode.appendChild(para); } if (newList.length) { _this4.wrapList(newList, headList.nodeName); para.appendChild(newList[0].parentNode); } }); if (headList.children.length === 0) { parentItem.removeChild(headList); } if (parentItem.childNodes.length === 0) { parentItem.parentNode.removeChild(parentItem); } } else { var lastList = headList.childNodes.length > 1 ? dom.splitTree(headList, { node: last.parentNode, offset: dom.position(last) + 1 }, { isSkipPaddingBlankHTML: true }) : null; var middleList = dom.splitTree(headList, { node: head.parentNode, offset: dom.position(head) }, { isSkipPaddingBlankHTML: true }); paras = isEscapseToBody ? dom.listDescendant(middleList, dom.isLi) : lists.from(middleList.childNodes).filter(dom.isLi); // LI to P if (isEscapseToBody || !dom.isList(headList.parentNode)) { paras = paras.map(function (para) { return dom.replace(para, 'P'); }); } external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(lists.from(paras).reverse(), function (idx, para) { dom.insertAfter(para, headList); }); // remove empty lists var rootLists = lists.compact([headList, middleList, lastList]); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(rootLists, function (idx, rootList) { var listNodes = [rootList].concat(dom.listDescendant(rootList, dom.isList)); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(listNodes.reverse(), function (idx, listNode) { if (!dom.nodeLength(listNode)) { dom.remove(listNode, true); } }); }); } releasedParas = releasedParas.concat(paras); }); return releasedParas; } /** * @method appendToPrevious * * Appends list to previous list item, if * none exist it wraps the list in a new list item. * * @param {HTMLNode} ListItem * @return {HTMLNode} */ }, { key: "appendToPrevious", value: function appendToPrevious(node) { return node.previousSibling ? dom.appendChildNodes(node.previousSibling, [node]) : this.wrapList([node], 'LI'); } /** * @method findList * * Finds an existing list in list item * * @param {HTMLNode} ListItem * @return {Array[]} */ }, { key: "findList", value: function findList(node) { return node ? lists.find(node.children, function (child) { return ['OL', 'UL'].indexOf(child.nodeName) > -1; }) : null; } /** * @method findNextSiblings * * Finds all list item siblings that follow it * * @param {HTMLNode} ListItem * @return {HTMLNode} */ }, { key: "findNextSiblings", value: function findNextSiblings(node) { var siblings = []; while (node.nextSibling) { siblings.push(node.nextSibling); node = node.nextSibling; } return siblings; } }]); return Bullet; }(); // CONCATENATED MODULE: ./src/js/base/editing/Typing.js function Typing_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Typing_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Typing_createClass(Constructor, protoProps, staticProps) { if (protoProps) Typing_defineProperties(Constructor.prototype, protoProps); if (staticProps) Typing_defineProperties(Constructor, staticProps); return Constructor; } /** * @class editing.Typing * * Typing * */ var Typing_Typing = /*#__PURE__*/function () { function Typing(context) { Typing_classCallCheck(this, Typing); // a Bullet instance to toggle lists off this.bullet = new Bullet_Bullet(); this.options = context.options; } /** * insert tab * * @param {WrappedRange} rng * @param {Number} tabsize */ Typing_createClass(Typing, [{ key: "insertTab", value: function insertTab(rng, tabsize) { var tab = dom.createText(new Array(tabsize + 1).join(dom.NBSP_CHAR)); rng = rng.deleteContents(); rng.insertNode(tab, true); rng = range.create(tab, tabsize); rng.select(); } /** * insert paragraph * * @param {jQuery} $editable * @param {WrappedRange} rng Can be used in unit tests to "mock" the range * * blockquoteBreakingLevel * 0 - No break, the new paragraph remains inside the quote * 1 - Break the first blockquote in the ancestors list * 2 - Break all blockquotes, so that the new paragraph is not quoted (this is the default) */ }, { key: "insertParagraph", value: function insertParagraph(editable, rng) { rng = rng || range.create(editable); // deleteContents on range. rng = rng.deleteContents(); // Wrap range if it needs to be wrapped by paragraph rng = rng.wrapBodyInlineWithPara(); // finding paragraph var splitRoot = dom.ancestor(rng.sc, dom.isPara); var nextPara; // on paragraph: split paragraph if (splitRoot) { // if it is an empty line with li if (dom.isLi(splitRoot) && (dom.isEmpty(splitRoot) || dom.deepestChildIsEmpty(splitRoot))) { // toggle UL/OL and escape this.bullet.toggleList(splitRoot.parentNode.nodeName); return; } else { var blockquote = null; if (this.options.blockquoteBreakingLevel === 1) { blockquote = dom.ancestor(splitRoot, dom.isBlockquote); } else if (this.options.blockquoteBreakingLevel === 2) { blockquote = dom.lastAncestor(splitRoot, dom.isBlockquote); } if (blockquote) { // We're inside a blockquote and options ask us to break it nextPara = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(dom.emptyPara)[0]; // If the split is right before a
                    , remove it so that there's no "empty line" // after the split in the new blockquote created if (dom.isRightEdgePoint(rng.getStartPoint()) && dom.isBR(rng.sc.nextSibling)) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(rng.sc.nextSibling).remove(); } var split = dom.splitTree(blockquote, rng.getStartPoint(), { isDiscardEmptySplits: true }); if (split) { split.parentNode.insertBefore(nextPara, split); } else { dom.insertAfter(nextPara, blockquote); // There's no split if we were at the end of the blockquote } } else { nextPara = dom.splitTree(splitRoot, rng.getStartPoint()); // not a blockquote, just insert the paragraph var emptyAnchors = dom.listDescendant(splitRoot, dom.isEmptyAnchor); emptyAnchors = emptyAnchors.concat(dom.listDescendant(nextPara, dom.isEmptyAnchor)); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(emptyAnchors, function (idx, anchor) { dom.remove(anchor); }); // replace empty heading, pre or custom-made styleTag with P tag if ((dom.isHeading(nextPara) || dom.isPre(nextPara) || dom.isCustomStyleTag(nextPara)) && dom.isEmpty(nextPara)) { nextPara = dom.replace(nextPara, 'p'); } } } // no paragraph: insert empty paragraph } else { var next = rng.sc.childNodes[rng.so]; nextPara = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(dom.emptyPara)[0]; if (next) { rng.sc.insertBefore(nextPara, next); } else { rng.sc.appendChild(nextPara); } } range.create(nextPara, 0).normalize().select().scrollIntoView(editable); } }]); return Typing; }(); // CONCATENATED MODULE: ./src/js/base/editing/Table.js function Table_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Table_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Table_createClass(Constructor, protoProps, staticProps) { if (protoProps) Table_defineProperties(Constructor.prototype, protoProps); if (staticProps) Table_defineProperties(Constructor, staticProps); return Constructor; } /** * @class Create a virtual table to create what actions to do in change. * @param {object} startPoint Cell selected to apply change. * @param {enum} where Where change will be applied Row or Col. Use enum: TableResultAction.where * @param {enum} action Action to be applied. Use enum: TableResultAction.requestAction * @param {object} domTable Dom element of table to make changes. */ var TableResultAction = function TableResultAction(startPoint, where, action, domTable) { var _startPoint = { 'colPos': 0, 'rowPos': 0 }; var _virtualTable = []; var _actionCellList = []; /// /////////////////////////////////////////// // Private functions /// /////////////////////////////////////////// /** * Set the startPoint of action. */ function setStartPoint() { if (!startPoint || !startPoint.tagName || startPoint.tagName.toLowerCase() !== 'td' && startPoint.tagName.toLowerCase() !== 'th') { // Impossible to identify start Cell point return; } _startPoint.colPos = startPoint.cellIndex; if (!startPoint.parentElement || !startPoint.parentElement.tagName || startPoint.parentElement.tagName.toLowerCase() !== 'tr') { // Impossible to identify start Row point return; } _startPoint.rowPos = startPoint.parentElement.rowIndex; } /** * Define virtual table position info object. * * @param {int} rowIndex Index position in line of virtual table. * @param {int} cellIndex Index position in column of virtual table. * @param {object} baseRow Row affected by this position. * @param {object} baseCell Cell affected by this position. * @param {bool} isSpan Inform if it is an span cell/row. */ function setVirtualTablePosition(rowIndex, cellIndex, baseRow, baseCell, isRowSpan, isColSpan, isVirtualCell) { var objPosition = { 'baseRow': baseRow, 'baseCell': baseCell, 'isRowSpan': isRowSpan, 'isColSpan': isColSpan, 'isVirtual': isVirtualCell }; if (!_virtualTable[rowIndex]) { _virtualTable[rowIndex] = []; } _virtualTable[rowIndex][cellIndex] = objPosition; } /** * Create action cell object. * * @param {object} virtualTableCellObj Object of specific position on virtual table. * @param {enum} resultAction Action to be applied in that item. */ function getActionCell(virtualTableCellObj, resultAction, virtualRowPosition, virtualColPosition) { return { 'baseCell': virtualTableCellObj.baseCell, 'action': resultAction, 'virtualTable': { 'rowIndex': virtualRowPosition, 'cellIndex': virtualColPosition } }; } /** * Recover free index of row to append Cell. * * @param {int} rowIndex Index of row to find free space. * @param {int} cellIndex Index of cell to find free space in table. */ function recoverCellIndex(rowIndex, cellIndex) { if (!_virtualTable[rowIndex]) { return cellIndex; } if (!_virtualTable[rowIndex][cellIndex]) { return cellIndex; } var newCellIndex = cellIndex; while (_virtualTable[rowIndex][newCellIndex]) { newCellIndex++; if (!_virtualTable[rowIndex][newCellIndex]) { return newCellIndex; } } } /** * Recover info about row and cell and add information to virtual table. * * @param {object} row Row to recover information. * @param {object} cell Cell to recover information. */ function addCellInfoToVirtual(row, cell) { var cellIndex = recoverCellIndex(row.rowIndex, cell.cellIndex); var cellHasColspan = cell.colSpan > 1; var cellHasRowspan = cell.rowSpan > 1; var isThisSelectedCell = row.rowIndex === _startPoint.rowPos && cell.cellIndex === _startPoint.colPos; setVirtualTablePosition(row.rowIndex, cellIndex, row, cell, cellHasRowspan, cellHasColspan, false); // Add span rows to virtual Table. var rowspanNumber = cell.attributes.rowSpan ? parseInt(cell.attributes.rowSpan.value, 10) : 0; if (rowspanNumber > 1) { for (var rp = 1; rp < rowspanNumber; rp++) { var rowspanIndex = row.rowIndex + rp; adjustStartPoint(rowspanIndex, cellIndex, cell, isThisSelectedCell); setVirtualTablePosition(rowspanIndex, cellIndex, row, cell, true, cellHasColspan, true); } } // Add span cols to virtual table. var colspanNumber = cell.attributes.colSpan ? parseInt(cell.attributes.colSpan.value, 10) : 0; if (colspanNumber > 1) { for (var cp = 1; cp < colspanNumber; cp++) { var cellspanIndex = recoverCellIndex(row.rowIndex, cellIndex + cp); adjustStartPoint(row.rowIndex, cellspanIndex, cell, isThisSelectedCell); setVirtualTablePosition(row.rowIndex, cellspanIndex, row, cell, cellHasRowspan, true, true); } } } /** * Process validation and adjust of start point if needed * * @param {int} rowIndex * @param {int} cellIndex * @param {object} cell * @param {bool} isSelectedCell */ function adjustStartPoint(rowIndex, cellIndex, cell, isSelectedCell) { if (rowIndex === _startPoint.rowPos && _startPoint.colPos >= cell.cellIndex && cell.cellIndex <= cellIndex && !isSelectedCell) { _startPoint.colPos++; } } /** * Create virtual table of cells with all cells, including span cells. */ function createVirtualTable() { var rows = domTable.rows; for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) { var cells = rows[rowIndex].cells; for (var cellIndex = 0; cellIndex < cells.length; cellIndex++) { addCellInfoToVirtual(rows[rowIndex], cells[cellIndex]); } } } /** * Get action to be applied on the cell. * * @param {object} cell virtual table cell to apply action */ function getDeleteResultActionToCell(cell) { switch (where) { case TableResultAction.where.Column: if (cell.isColSpan) { return TableResultAction.resultAction.SubtractSpanCount; } break; case TableResultAction.where.Row: if (!cell.isVirtual && cell.isRowSpan) { return TableResultAction.resultAction.AddCell; } else if (cell.isRowSpan) { return TableResultAction.resultAction.SubtractSpanCount; } break; } return TableResultAction.resultAction.RemoveCell; } /** * Get action to be applied on the cell. * * @param {object} cell virtual table cell to apply action */ function getAddResultActionToCell(cell) { switch (where) { case TableResultAction.where.Column: if (cell.isColSpan) { return TableResultAction.resultAction.SumSpanCount; } else if (cell.isRowSpan && cell.isVirtual) { return TableResultAction.resultAction.Ignore; } break; case TableResultAction.where.Row: if (cell.isRowSpan) { return TableResultAction.resultAction.SumSpanCount; } else if (cell.isColSpan && cell.isVirtual) { return TableResultAction.resultAction.Ignore; } break; } return TableResultAction.resultAction.AddCell; } function init() { setStartPoint(); createVirtualTable(); } /// /////////////////////////////////////////// // Public functions /// /////////////////////////////////////////// /** * Recover array os what to do in table. */ this.getActionList = function () { var fixedRow = where === TableResultAction.where.Row ? _startPoint.rowPos : -1; var fixedCol = where === TableResultAction.where.Column ? _startPoint.colPos : -1; var actualPosition = 0; var canContinue = true; while (canContinue) { var rowPosition = fixedRow >= 0 ? fixedRow : actualPosition; var colPosition = fixedCol >= 0 ? fixedCol : actualPosition; var row = _virtualTable[rowPosition]; if (!row) { canContinue = false; return _actionCellList; } var cell = row[colPosition]; if (!cell) { canContinue = false; return _actionCellList; } // Define action to be applied in this cell var resultAction = TableResultAction.resultAction.Ignore; switch (action) { case TableResultAction.requestAction.Add: resultAction = getAddResultActionToCell(cell); break; case TableResultAction.requestAction.Delete: resultAction = getDeleteResultActionToCell(cell); break; } _actionCellList.push(getActionCell(cell, resultAction, rowPosition, colPosition)); actualPosition++; } return _actionCellList; }; init(); }; /** * * Where action occours enum. */ TableResultAction.where = { 'Row': 0, 'Column': 1 }; /** * * Requested action to apply enum. */ TableResultAction.requestAction = { 'Add': 0, 'Delete': 1 }; /** * * Result action to be executed enum. */ TableResultAction.resultAction = { 'Ignore': 0, 'SubtractSpanCount': 1, 'RemoveCell': 2, 'AddCell': 3, 'SumSpanCount': 4 }; /** * * @class editing.Table * * Table * */ var Table_Table = /*#__PURE__*/function () { function Table() { Table_classCallCheck(this, Table); } Table_createClass(Table, [{ key: "tab", /** * handle tab key * * @param {WrappedRange} rng * @param {Boolean} isShift */ value: function tab(rng, isShift) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); var table = dom.ancestor(cell, dom.isTable); var cells = dom.listDescendant(table, dom.isCell); var nextCell = lists[isShift ? 'prev' : 'next'](cells, cell); if (nextCell) { range.create(nextCell, 0).select(); } } /** * Add a new row * * @param {WrappedRange} rng * @param {String} position (top/bottom) * @return {Node} */ }, { key: "addRow", value: function addRow(rng, position) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); var currentTr = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('tr'); var trAttributes = this.recoverAttributes(currentTr); var html = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(''); var vTable = new TableResultAction(cell, TableResultAction.where.Row, TableResultAction.requestAction.Add, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentTr).closest('table')[0]); var actions = vTable.getActionList(); for (var idCell = 0; idCell < actions.length; idCell++) { var currentCell = actions[idCell]; var tdAttributes = this.recoverAttributes(currentCell.baseCell); switch (currentCell.action) { case TableResultAction.resultAction.AddCell: html.append('' + dom.blank + ''); break; case TableResultAction.resultAction.SumSpanCount: { if (position === 'top') { var baseCellTr = currentCell.baseCell.parent; var isTopFromRowSpan = (!baseCellTr ? 0 : currentCell.baseCell.closest('tr').rowIndex) <= currentTr[0].rowIndex; if (isTopFromRowSpan) { var newTd = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
                    ').append(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('' + dom.blank + '').removeAttr('rowspan')).html(); html.append(newTd); break; } } var rowspanNumber = parseInt(currentCell.baseCell.rowSpan, 10); rowspanNumber++; currentCell.baseCell.setAttribute('rowSpan', rowspanNumber); } break; } } if (position === 'top') { currentTr.before(html); } else { var cellHasRowspan = cell.rowSpan > 1; if (cellHasRowspan) { var lastTrIndex = currentTr[0].rowIndex + (cell.rowSpan - 2); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentTr).parent().find('tr')[lastTrIndex]).after(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(html)); return; } currentTr.after(html); } } /** * Add a new col * * @param {WrappedRange} rng * @param {String} position (left/right) * @return {Node} */ }, { key: "addCol", value: function addCol(rng, position) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); var row = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('tr'); var rowsGroup = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(row).siblings(); rowsGroup.push(row); var vTable = new TableResultAction(cell, TableResultAction.where.Column, TableResultAction.requestAction.Add, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(row).closest('table')[0]); var actions = vTable.getActionList(); for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) { var currentCell = actions[actionIndex]; var tdAttributes = this.recoverAttributes(currentCell.baseCell); switch (currentCell.action) { case TableResultAction.resultAction.AddCell: if (position === 'right') { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentCell.baseCell).after('' + dom.blank + ''); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentCell.baseCell).before('' + dom.blank + ''); } break; case TableResultAction.resultAction.SumSpanCount: if (position === 'right') { var colspanNumber = parseInt(currentCell.baseCell.colSpan, 10); colspanNumber++; currentCell.baseCell.setAttribute('colSpan', colspanNumber); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentCell.baseCell).before('' + dom.blank + ''); } break; } } } /* * Copy attributes from element. * * @param {object} Element to recover attributes. * @return {string} Copied string elements. */ }, { key: "recoverAttributes", value: function recoverAttributes(el) { var resultStr = ''; if (!el) { return resultStr; } var attrList = el.attributes || []; for (var i = 0; i < attrList.length; i++) { if (attrList[i].name.toLowerCase() === 'id') { continue; } if (attrList[i].specified) { resultStr += ' ' + attrList[i].name + '=\'' + attrList[i].value + '\''; } } return resultStr; } /** * Delete current row * * @param {WrappedRange} rng * @return {Node} */ }, { key: "deleteRow", value: function deleteRow(rng) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); var row = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('tr'); var cellPos = row.children('td, th').index(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell)); var rowPos = row[0].rowIndex; var vTable = new TableResultAction(cell, TableResultAction.where.Row, TableResultAction.requestAction.Delete, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(row).closest('table')[0]); var actions = vTable.getActionList(); for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) { if (!actions[actionIndex]) { continue; } var baseCell = actions[actionIndex].baseCell; var virtualPosition = actions[actionIndex].virtualTable; var hasRowspan = baseCell.rowSpan && baseCell.rowSpan > 1; var rowspanNumber = hasRowspan ? parseInt(baseCell.rowSpan, 10) : 0; switch (actions[actionIndex].action) { case TableResultAction.resultAction.Ignore: continue; case TableResultAction.resultAction.AddCell: { var nextRow = row.next('tr')[0]; if (!nextRow) { continue; } var cloneRow = row[0].cells[cellPos]; if (hasRowspan) { if (rowspanNumber > 2) { rowspanNumber--; nextRow.insertBefore(cloneRow, nextRow.cells[cellPos]); nextRow.cells[cellPos].setAttribute('rowSpan', rowspanNumber); nextRow.cells[cellPos].innerHTML = ''; } else if (rowspanNumber === 2) { nextRow.insertBefore(cloneRow, nextRow.cells[cellPos]); nextRow.cells[cellPos].removeAttribute('rowSpan'); nextRow.cells[cellPos].innerHTML = ''; } } } continue; case TableResultAction.resultAction.SubtractSpanCount: if (hasRowspan) { if (rowspanNumber > 2) { rowspanNumber--; baseCell.setAttribute('rowSpan', rowspanNumber); if (virtualPosition.rowIndex !== rowPos && baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; } } else if (rowspanNumber === 2) { baseCell.removeAttribute('rowSpan'); if (virtualPosition.rowIndex !== rowPos && baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; } } } continue; case TableResultAction.resultAction.RemoveCell: // Do not need remove cell because row will be deleted. continue; } } row.remove(); } /** * Delete current col * * @param {WrappedRange} rng * @return {Node} */ }, { key: "deleteCol", value: function deleteCol(rng) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); var row = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('tr'); var cellPos = row.children('td, th').index(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell)); var vTable = new TableResultAction(cell, TableResultAction.where.Column, TableResultAction.requestAction.Delete, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(row).closest('table')[0]); var actions = vTable.getActionList(); for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) { if (!actions[actionIndex]) { continue; } switch (actions[actionIndex].action) { case TableResultAction.resultAction.Ignore: continue; case TableResultAction.resultAction.SubtractSpanCount: { var baseCell = actions[actionIndex].baseCell; var hasColspan = baseCell.colSpan && baseCell.colSpan > 1; if (hasColspan) { var colspanNumber = baseCell.colSpan ? parseInt(baseCell.colSpan, 10) : 0; if (colspanNumber > 2) { colspanNumber--; baseCell.setAttribute('colSpan', colspanNumber); if (baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; } } else if (colspanNumber === 2) { baseCell.removeAttribute('colSpan'); if (baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; } } } } continue; case TableResultAction.resultAction.RemoveCell: dom.remove(actions[actionIndex].baseCell, true); continue; } } } /** * create empty table element * * @param {Number} rowCount * @param {Number} colCount * @return {Node} */ }, { key: "createTable", value: function createTable(colCount, rowCount, options) { var tds = []; var tdHTML; for (var idxCol = 0; idxCol < colCount; idxCol++) { tds.push('' + dom.blank + ''); } tdHTML = tds.join(''); var trs = []; var trHTML; for (var idxRow = 0; idxRow < rowCount; idxRow++) { trs.push('' + tdHTML + ''); } trHTML = trs.join(''); var $table = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('' + trHTML + '
                    '); if (options && options.tableClassName) { $table.addClass(options.tableClassName); } return $table[0]; } /** * Delete current table * * @param {WrappedRange} rng * @return {Node} */ }, { key: "deleteTable", value: function deleteTable(rng) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('table').remove(); } }]); return Table; }(); // CONCATENATED MODULE: ./src/js/base/module/Editor.js function Editor_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Editor_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Editor_createClass(Constructor, protoProps, staticProps) { if (protoProps) Editor_defineProperties(Constructor.prototype, protoProps); if (staticProps) Editor_defineProperties(Constructor, staticProps); return Constructor; } var KEY_BOGUS = 'bogus'; /** * @class Editor */ var Editor_Editor = /*#__PURE__*/function () { function Editor(context) { var _this = this; Editor_classCallCheck(this, Editor); this.context = context; this.$note = context.layoutInfo.note; this.$editor = context.layoutInfo.editor; this.$editable = context.layoutInfo.editable; this.options = context.options; this.lang = this.options.langInfo; this.editable = this.$editable[0]; this.lastRange = null; this.snapshot = null; this.style = new Style_Style(); this.table = new Table_Table(); this.typing = new Typing_Typing(context); this.bullet = new Bullet_Bullet(); this.history = new History_History(context); this.context.memo('help.escape', this.lang.help.escape); this.context.memo('help.undo', this.lang.help.undo); this.context.memo('help.redo', this.lang.help.redo); this.context.memo('help.tab', this.lang.help.tab); this.context.memo('help.untab', this.lang.help.untab); this.context.memo('help.insertParagraph', this.lang.help.insertParagraph); this.context.memo('help.insertOrderedList', this.lang.help.insertOrderedList); this.context.memo('help.insertUnorderedList', this.lang.help.insertUnorderedList); this.context.memo('help.indent', this.lang.help.indent); this.context.memo('help.outdent', this.lang.help.outdent); this.context.memo('help.formatPara', this.lang.help.formatPara); this.context.memo('help.insertHorizontalRule', this.lang.help.insertHorizontalRule); this.context.memo('help.fontName', this.lang.help.fontName); // native commands(with execCommand), generate function for execCommand var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', 'formatBlock', 'removeFormat', 'backColor']; for (var idx = 0, len = commands.length; idx < len; idx++) { this[commands[idx]] = function (sCmd) { return function (value) { _this.beforeCommand(); document.execCommand(sCmd, false, value); _this.afterCommand(true); }; }(commands[idx]); this.context.memo('help.' + commands[idx], this.lang.help[commands[idx]]); } this.fontName = this.wrapCommand(function (value) { return _this.fontStyling('font-family', env.validFontName(value)); }); this.fontSize = this.wrapCommand(function (value) { var unit = _this.currentStyle()['font-size-unit']; return _this.fontStyling('font-size', value + unit); }); this.fontSizeUnit = this.wrapCommand(function (value) { var size = _this.currentStyle()['font-size']; return _this.fontStyling('font-size', size + value); }); for (var _idx = 1; _idx <= 6; _idx++) { this['formatH' + _idx] = function (idx) { return function () { _this.formatBlock('H' + idx); }; }(_idx); this.context.memo('help.formatH' + _idx, this.lang.help['formatH' + _idx]); } this.insertParagraph = this.wrapCommand(function () { _this.typing.insertParagraph(_this.editable); }); this.insertOrderedList = this.wrapCommand(function () { _this.bullet.insertOrderedList(_this.editable); }); this.insertUnorderedList = this.wrapCommand(function () { _this.bullet.insertUnorderedList(_this.editable); }); this.indent = this.wrapCommand(function () { _this.bullet.indent(_this.editable); }); this.outdent = this.wrapCommand(function () { _this.bullet.outdent(_this.editable); }); /** * insertNode * insert node * @param {Node} node */ this.insertNode = this.wrapCommand(function (node) { if (_this.isLimited(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(node).text().length)) { return; } var rng = _this.getLastRange(); rng.insertNode(node); _this.setLastRange(range.createFromNodeAfter(node).select()); }); /** * insert text * @param {String} text */ this.insertText = this.wrapCommand(function (text) { if (_this.isLimited(text.length)) { return; } var rng = _this.getLastRange(); var textNode = rng.insertNode(dom.createText(text)); _this.setLastRange(range.create(textNode, dom.nodeLength(textNode)).select()); }); /** * paste HTML * @param {String} markup */ this.pasteHTML = this.wrapCommand(function (markup) { if (_this.isLimited(markup.length)) { return; } markup = _this.context.invoke('codeview.purify', markup); var contents = _this.getLastRange().pasteHTML(markup); _this.setLastRange(range.createFromNodeAfter(lists.last(contents)).select()); }); /** * formatBlock * * @param {String} tagName */ this.formatBlock = this.wrapCommand(function (tagName, $target) { var onApplyCustomStyle = _this.options.callbacks.onApplyCustomStyle; if (onApplyCustomStyle) { onApplyCustomStyle.call(_this, $target, _this.context, _this.onFormatBlock); } else { _this.onFormatBlock(tagName, $target); } }); /** * insert horizontal rule */ this.insertHorizontalRule = this.wrapCommand(function () { var hrNode = _this.getLastRange().insertNode(dom.create('HR')); if (hrNode.nextSibling) { _this.setLastRange(range.create(hrNode.nextSibling, 0).normalize().select()); } }); /** * lineHeight * @param {String} value */ this.lineHeight = this.wrapCommand(function (value) { _this.style.stylePara(_this.getLastRange(), { lineHeight: value }); }); /** * create link (command) * * @param {Object} linkInfo */ this.createLink = this.wrapCommand(function (linkInfo) { var linkUrl = linkInfo.url; var linkText = linkInfo.text; var isNewWindow = linkInfo.isNewWindow; var checkProtocol = linkInfo.checkProtocol; var rng = linkInfo.range || _this.getLastRange(); var additionalTextLength = linkText.length - rng.toString().length; if (additionalTextLength > 0 && _this.isLimited(additionalTextLength)) { return; } var isTextChanged = rng.toString() !== linkText; // handle spaced urls from input if (typeof linkUrl === 'string') { linkUrl = linkUrl.trim(); } if (_this.options.onCreateLink) { linkUrl = _this.options.onCreateLink(linkUrl); } else if (checkProtocol) { // if url doesn't have any protocol and not even a relative or a label, use http:// as default linkUrl = /^([A-Za-z][A-Za-z0-9+-.]*\:|#|\/)/.test(linkUrl) ? linkUrl : _this.options.defaultProtocol + linkUrl; } var anchors = []; if (isTextChanged) { rng = rng.deleteContents(); var anchor = rng.insertNode(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('' + linkText + '')[0]); anchors.push(anchor); } else { anchors = _this.style.styleNodes(rng, { nodeName: 'A', expandClosestSibling: true, onlyPartialContains: true }); } external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(anchors, function (idx, anchor) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(anchor).attr('href', linkUrl); if (isNewWindow) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(anchor).attr('target', '_blank'); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(anchor).removeAttr('target'); } }); _this.setLastRange(_this.createRangeFromList(anchors).select()); }); /** * setting color * * @param {Object} sObjColor color code * @param {String} sObjColor.foreColor foreground color * @param {String} sObjColor.backColor background color */ this.color = this.wrapCommand(function (colorInfo) { var foreColor = colorInfo.foreColor; var backColor = colorInfo.backColor; if (foreColor) { document.execCommand('foreColor', false, foreColor); } if (backColor) { document.execCommand('backColor', false, backColor); } }); /** * Set foreground color * * @param {String} colorCode foreground color code */ this.foreColor = this.wrapCommand(function (colorInfo) { document.execCommand('foreColor', false, colorInfo); }); /** * insert Table * * @param {String} dimension of table (ex : "5x5") */ this.insertTable = this.wrapCommand(function (dim) { var dimension = dim.split('x'); var rng = _this.getLastRange().deleteContents(); rng.insertNode(_this.table.createTable(dimension[0], dimension[1], _this.options)); }); /** * remove media object and Figure Elements if media object is img with Figure. */ this.removeMedia = this.wrapCommand(function () { var $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(_this.restoreTarget()).parent(); if ($target.closest('figure').length) { $target.closest('figure').remove(); } else { $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(_this.restoreTarget()).detach(); } _this.context.triggerEvent('media.delete', $target, _this.$editable); }); /** * float me * * @param {String} value */ this.floatMe = this.wrapCommand(function (value) { var $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(_this.restoreTarget()); $target.toggleClass('note-float-left', value === 'left'); $target.toggleClass('note-float-right', value === 'right'); $target.css('float', value === 'none' ? '' : value); }); /** * resize overlay element * @param {String} value */ this.resize = this.wrapCommand(function (value) { var $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(_this.restoreTarget()); value = parseFloat(value); if (value === 0) { $target.css('width', ''); } else { $target.css({ width: value * 100 + '%', height: '' }); } }); } Editor_createClass(Editor, [{ key: "initialize", value: function initialize() { var _this2 = this; // bind custom events this.$editable.on('keydown', function (event) { if (event.keyCode === core_key.code.ENTER) { _this2.context.triggerEvent('enter', event); } _this2.context.triggerEvent('keydown', event); // keep a snapshot to limit text on input event _this2.snapshot = _this2.history.makeSnapshot(); _this2.hasKeyShortCut = false; if (!event.isDefaultPrevented()) { if (_this2.options.shortcuts) { _this2.hasKeyShortCut = _this2.handleKeyMap(event); } else { _this2.preventDefaultEditableShortCuts(event); } } if (_this2.isLimited(1, event)) { var lastRange = _this2.getLastRange(); if (lastRange.eo - lastRange.so === 0) { return false; } } _this2.setLastRange(); // record undo in the key event except keyMap. if (_this2.options.recordEveryKeystroke) { if (_this2.hasKeyShortCut === false) { _this2.history.recordUndo(); } } }).on('keyup', function (event) { _this2.setLastRange(); _this2.context.triggerEvent('keyup', event); }).on('focus', function (event) { _this2.setLastRange(); _this2.context.triggerEvent('focus', event); }).on('blur', function (event) { _this2.context.triggerEvent('blur', event); }).on('mousedown', function (event) { _this2.context.triggerEvent('mousedown', event); }).on('mouseup', function (event) { _this2.setLastRange(); _this2.history.recordUndo(); _this2.context.triggerEvent('mouseup', event); }).on('scroll', function (event) { _this2.context.triggerEvent('scroll', event); }).on('paste', function (event) { _this2.setLastRange(); _this2.context.triggerEvent('paste', event); }).on('input', function () { // To limit composition characters (e.g. Korean) if (_this2.isLimited(0) && _this2.snapshot) { _this2.history.applySnapshot(_this2.snapshot); } }); this.$editable.attr('spellcheck', this.options.spellCheck); this.$editable.attr('autocorrect', this.options.spellCheck); if (this.options.disableGrammar) { this.$editable.attr('data-gramm', false); } // init content before set event this.$editable.html(dom.html(this.$note) || dom.emptyPara); this.$editable.on(env.inputEventName, func.debounce(function () { _this2.context.triggerEvent('change', _this2.$editable.html(), _this2.$editable); }, 10)); this.$editable.on('focusin', function (event) { _this2.context.triggerEvent('focusin', event); }).on('focusout', function (event) { _this2.context.triggerEvent('focusout', event); }); if (this.options.airMode) { if (this.options.overrideContextMenu) { this.$editor.on('contextmenu', function (event) { _this2.context.triggerEvent('contextmenu', event); return false; }); } } else { if (this.options.width) { this.$editor.outerWidth(this.options.width); } if (this.options.height) { this.$editable.outerHeight(this.options.height); } if (this.options.maxHeight) { this.$editable.css('max-height', this.options.maxHeight); } if (this.options.minHeight) { this.$editable.css('min-height', this.options.minHeight); } } this.history.recordUndo(); this.setLastRange(); } }, { key: "destroy", value: function destroy() { this.$editable.off(); } }, { key: "handleKeyMap", value: function handleKeyMap(event) { var keyMap = this.options.keyMap[env.isMac ? 'mac' : 'pc']; var keys = []; if (event.metaKey) { keys.push('CMD'); } if (event.ctrlKey && !event.altKey) { keys.push('CTRL'); } if (event.shiftKey) { keys.push('SHIFT'); } var keyName = core_key.nameFromCode[event.keyCode]; if (keyName) { keys.push(keyName); } var eventName = keyMap[keys.join('+')]; if (keyName === 'TAB' && !this.options.tabDisable) { this.afterCommand(); } else if (eventName) { if (this.context.invoke(eventName) !== false) { event.preventDefault(); // if keyMap action was invoked return true; } } else if (core_key.isEdit(event.keyCode)) { this.afterCommand(); } return false; } }, { key: "preventDefaultEditableShortCuts", value: function preventDefaultEditableShortCuts(event) { // B(Bold, 66) / I(Italic, 73) / U(Underline, 85) if ((event.ctrlKey || event.metaKey) && lists.contains([66, 73, 85], event.keyCode)) { event.preventDefault(); } } }, { key: "isLimited", value: function isLimited(pad, event) { pad = pad || 0; if (typeof event !== 'undefined') { if (core_key.isMove(event.keyCode) || core_key.isNavigation(event.keyCode) || event.ctrlKey || event.metaKey || lists.contains([core_key.code.BACKSPACE, core_key.code.DELETE], event.keyCode)) { return false; } } if (this.options.maxTextLength > 0) { if (this.$editable.text().length + pad > this.options.maxTextLength) { return true; } } return false; } /** * create range * @return {WrappedRange} */ }, { key: "createRange", value: function createRange() { this.focus(); this.setLastRange(); return this.getLastRange(); } /** * create a new range from the list of elements * * @param {list} dom element list * @return {WrappedRange} */ }, { key: "createRangeFromList", value: function createRangeFromList(lst) { var startRange = range.createFromNodeBefore(lists.head(lst)); var startPoint = startRange.getStartPoint(); var endRange = range.createFromNodeAfter(lists.last(lst)); var endPoint = endRange.getEndPoint(); return range.create(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset); } /** * set the last range * * if given rng is exist, set rng as the last range * or create a new range at the end of the document * * @param {WrappedRange} rng */ }, { key: "setLastRange", value: function setLastRange(rng) { if (rng) { this.lastRange = rng; } else { this.lastRange = range.create(this.editable); if (external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.lastRange.sc).closest('.note-editable').length === 0) { this.lastRange = range.createFromBodyElement(this.editable); } } } /** * get the last range * * if there is a saved last range, return it * or create a new range and return it * * @return {WrappedRange} */ }, { key: "getLastRange", value: function getLastRange() { if (!this.lastRange) { this.setLastRange(); } return this.lastRange; } /** * saveRange * * save current range * * @param {Boolean} [thenCollapse=false] */ }, { key: "saveRange", value: function saveRange(thenCollapse) { if (thenCollapse) { this.getLastRange().collapse().select(); } } /** * restoreRange * * restore lately range */ }, { key: "restoreRange", value: function restoreRange() { if (this.lastRange) { this.lastRange.select(); this.focus(); } } }, { key: "saveTarget", value: function saveTarget(node) { this.$editable.data('target', node); } }, { key: "clearTarget", value: function clearTarget() { this.$editable.removeData('target'); } }, { key: "restoreTarget", value: function restoreTarget() { return this.$editable.data('target'); } /** * currentStyle * * current style * @return {Object|Boolean} unfocus */ }, { key: "currentStyle", value: function currentStyle() { var rng = range.create(); if (rng) { rng = rng.normalize(); } return rng ? this.style.current(rng) : this.style.fromNode(this.$editable); } /** * style from node * * @param {jQuery} $node * @return {Object} */ }, { key: "styleFromNode", value: function styleFromNode($node) { return this.style.fromNode($node); } /** * undo */ }, { key: "undo", value: function undo() { this.context.triggerEvent('before.command', this.$editable.html()); this.history.undo(); this.context.triggerEvent('change', this.$editable.html(), this.$editable); } /* * commit */ }, { key: "commit", value: function commit() { this.context.triggerEvent('before.command', this.$editable.html()); this.history.commit(); this.context.triggerEvent('change', this.$editable.html(), this.$editable); } /** * redo */ }, { key: "redo", value: function redo() { this.context.triggerEvent('before.command', this.$editable.html()); this.history.redo(); this.context.triggerEvent('change', this.$editable.html(), this.$editable); } /** * before command */ }, { key: "beforeCommand", value: function beforeCommand() { this.context.triggerEvent('before.command', this.$editable.html()); // Set styleWithCSS before run a command document.execCommand('styleWithCSS', false, this.options.styleWithCSS); // keep focus on editable before command execution this.focus(); } /** * after command * @param {Boolean} isPreventTrigger */ }, { key: "afterCommand", value: function afterCommand(isPreventTrigger) { this.normalizeContent(); this.history.recordUndo(); if (!isPreventTrigger) { this.context.triggerEvent('change', this.$editable.html(), this.$editable); } } /** * handle tab key */ }, { key: "tab", value: function tab() { var rng = this.getLastRange(); if (rng.isCollapsed() && rng.isOnCell()) { this.table.tab(rng); } else { if (this.options.tabSize === 0) { return false; } if (!this.isLimited(this.options.tabSize)) { this.beforeCommand(); this.typing.insertTab(rng, this.options.tabSize); this.afterCommand(); } } } /** * handle shift+tab key */ }, { key: "untab", value: function untab() { var rng = this.getLastRange(); if (rng.isCollapsed() && rng.isOnCell()) { this.table.tab(rng, true); } else { if (this.options.tabSize === 0) { return false; } } } /** * run given function between beforeCommand and afterCommand */ }, { key: "wrapCommand", value: function wrapCommand(fn) { return function () { this.beforeCommand(); fn.apply(this, arguments); this.afterCommand(); }; } /** * insert image * * @param {String} src * @param {String|Function} param * @return {Promise} */ }, { key: "insertImage", value: function insertImage(src, param) { var _this3 = this; return createImage(src, param).then(function ($image) { _this3.beforeCommand(); if (typeof param === 'function') { param($image); } else { if (typeof param === 'string') { $image.attr('data-filename', param); } $image.css('width', Math.min(_this3.$editable.width(), $image.width())); } $image.show(); _this3.getLastRange().insertNode($image[0]); _this3.setLastRange(range.createFromNodeAfter($image[0]).select()); _this3.afterCommand(); }).fail(function (e) { _this3.context.triggerEvent('image.upload.error', e); }); } /** * insertImages * @param {File[]} files */ }, { key: "insertImagesAsDataURL", value: function insertImagesAsDataURL(files) { var _this4 = this; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(files, function (idx, file) { var filename = file.name; if (_this4.options.maximumImageFileSize && _this4.options.maximumImageFileSize < file.size) { _this4.context.triggerEvent('image.upload.error', _this4.lang.image.maximumFileSizeError); } else { readFileAsDataURL(file).then(function (dataURL) { return _this4.insertImage(dataURL, filename); }).fail(function () { _this4.context.triggerEvent('image.upload.error'); }); } }); } /** * insertImagesOrCallback * @param {File[]} files */ }, { key: "insertImagesOrCallback", value: function insertImagesOrCallback(files) { var callbacks = this.options.callbacks; // If onImageUpload set, if (callbacks.onImageUpload) { this.context.triggerEvent('image.upload', files); // else insert Image as dataURL } else { this.insertImagesAsDataURL(files); } } /** * return selected plain text * @return {String} text */ }, { key: "getSelectedText", value: function getSelectedText() { var rng = this.getLastRange(); // if range on anchor, expand range with anchor if (rng.isOnAnchor()) { rng = range.createFromNode(dom.ancestor(rng.sc, dom.isAnchor)); } return rng.toString(); } }, { key: "onFormatBlock", value: function onFormatBlock(tagName, $target) { // [workaround] for MSIE, IE need `<` document.execCommand('FormatBlock', false, env.isMSIE ? '<' + tagName + '>' : tagName); // support custom class if ($target && $target.length) { // find the exact element has given tagName if ($target[0].tagName.toUpperCase() !== tagName.toUpperCase()) { $target = $target.find(tagName); } if ($target && $target.length) { var className = $target[0].className || ''; if (className) { var currentRange = this.createRange(); var $parent = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()([currentRange.sc, currentRange.ec]).closest(tagName); $parent.addClass(className); } } } } }, { key: "formatPara", value: function formatPara() { this.formatBlock('P'); } }, { key: "fontStyling", value: function fontStyling(target, value) { var rng = this.getLastRange(); if (rng !== '') { var spans = this.style.styleNodes(rng); this.$editor.find('.note-status-output').html(''); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(spans).css(target, value); // [workaround] added styled bogus span for style // - also bogus character needed for cursor position if (rng.isCollapsed()) { var firstSpan = lists.head(spans); if (firstSpan && !dom.nodeLength(firstSpan)) { firstSpan.innerHTML = dom.ZERO_WIDTH_NBSP_CHAR; range.createFromNode(firstSpan.firstChild).select(); this.setLastRange(); this.$editable.data(KEY_BOGUS, firstSpan); } } else { this.setLastRange(this.createRangeFromList(spans).select()); } } else { var noteStatusOutput = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.now(); this.$editor.find('.note-status-output').html('
                    ' + this.lang.output.noSelection + '
                    '); setTimeout(function () { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('#note-status-output-' + noteStatusOutput).remove(); }, 5000); } } /** * unlink * * @type command */ }, { key: "unlink", value: function unlink() { var rng = this.getLastRange(); if (rng.isOnAnchor()) { var anchor = dom.ancestor(rng.sc, dom.isAnchor); rng = range.createFromNode(anchor); rng.select(); this.setLastRange(); this.beforeCommand(); document.execCommand('unlink'); this.afterCommand(); } } /** * returns link info * * @return {Object} * @return {WrappedRange} return.range * @return {String} return.text * @return {Boolean} [return.isNewWindow=true] * @return {String} [return.url=""] */ }, { key: "getLinkInfo", value: function getLinkInfo() { var rng = this.getLastRange().expand(dom.isAnchor); // Get the first anchor on range(for edit). var $anchor = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(lists.head(rng.nodes(dom.isAnchor))); var linkInfo = { range: rng, text: rng.toString(), url: $anchor.length ? $anchor.attr('href') : '' }; // When anchor exists, if ($anchor.length) { // Set isNewWindow by checking its target. linkInfo.isNewWindow = $anchor.attr('target') === '_blank'; } return linkInfo; } }, { key: "addRow", value: function addRow(position) { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.addRow(rng, position); this.afterCommand(); } } }, { key: "addCol", value: function addCol(position) { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.addCol(rng, position); this.afterCommand(); } } }, { key: "deleteRow", value: function deleteRow() { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.deleteRow(rng); this.afterCommand(); } } }, { key: "deleteCol", value: function deleteCol() { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.deleteCol(rng); this.afterCommand(); } } }, { key: "deleteTable", value: function deleteTable() { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.deleteTable(rng); this.afterCommand(); } } /** * @param {Position} pos * @param {jQuery} $target - target element * @param {Boolean} [bKeepRatio] - keep ratio */ }, { key: "resizeTo", value: function resizeTo(pos, $target, bKeepRatio) { var imageSize; if (bKeepRatio) { var newRatio = pos.y / pos.x; var ratio = $target.data('ratio'); imageSize = { width: ratio > newRatio ? pos.x : pos.y / ratio, height: ratio > newRatio ? pos.x * ratio : pos.y }; } else { imageSize = { width: pos.x, height: pos.y }; } $target.css(imageSize); } /** * returns whether editable area has focus or not. */ }, { key: "hasFocus", value: function hasFocus() { return this.$editable.is(':focus'); } /** * set focus */ }, { key: "focus", value: function focus() { // [workaround] Screen will move when page is scolled in IE. // - do focus when not focused if (!this.hasFocus()) { this.$editable.focus(); } } /** * returns whether contents is empty or not. * @return {Boolean} */ }, { key: "isEmpty", value: function isEmpty() { return dom.isEmpty(this.$editable[0]) || dom.emptyPara === this.$editable.html(); } /** * Removes all contents and restores the editable instance to an _emptyPara_. */ }, { key: "empty", value: function empty() { this.context.invoke('code', dom.emptyPara); } /** * normalize content */ }, { key: "normalizeContent", value: function normalizeContent() { this.$editable[0].normalize(); } }]); return Editor; }(); // CONCATENATED MODULE: ./src/js/base/module/Clipboard.js function Clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Clipboard_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Clipboard_createClass(Constructor, protoProps, staticProps) { if (protoProps) Clipboard_defineProperties(Constructor.prototype, protoProps); if (staticProps) Clipboard_defineProperties(Constructor, staticProps); return Constructor; } var Clipboard_Clipboard = /*#__PURE__*/function () { function Clipboard(context) { Clipboard_classCallCheck(this, Clipboard); this.context = context; this.$editable = context.layoutInfo.editable; } Clipboard_createClass(Clipboard, [{ key: "initialize", value: function initialize() { this.$editable.on('paste', this.pasteByEvent.bind(this)); } /** * paste by clipboard event * * @param {Event} event */ }, { key: "pasteByEvent", value: function pasteByEvent(event) { var _this = this; var clipboardData = event.originalEvent.clipboardData; if (clipboardData && clipboardData.items && clipboardData.items.length) { var item = clipboardData.items.length > 1 ? clipboardData.items[1] : lists.head(clipboardData.items); if (item.kind === 'file' && item.type.indexOf('image/') !== -1) { // paste img file this.context.invoke('editor.insertImagesOrCallback', [item.getAsFile()]); event.preventDefault(); } else if (item.kind === 'string') { // paste text with maxTextLength check if (this.context.invoke('editor.isLimited', clipboardData.getData('Text').length)) { event.preventDefault(); } } } else if (window.clipboardData) { // for IE var text = window.clipboardData.getData('text'); if (this.context.invoke('editor.isLimited', text.length)) { event.preventDefault(); } } // Call editor.afterCommand after proceeding default event handler setTimeout(function () { _this.context.invoke('editor.afterCommand'); }, 10); } }]); return Clipboard; }(); // CONCATENATED MODULE: ./src/js/base/module/Dropzone.js function Dropzone_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Dropzone_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Dropzone_createClass(Constructor, protoProps, staticProps) { if (protoProps) Dropzone_defineProperties(Constructor.prototype, protoProps); if (staticProps) Dropzone_defineProperties(Constructor, staticProps); return Constructor; } var Dropzone_Dropzone = /*#__PURE__*/function () { function Dropzone(context) { Dropzone_classCallCheck(this, Dropzone); this.context = context; this.$eventListener = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); this.$editor = context.layoutInfo.editor; this.$editable = context.layoutInfo.editable; this.options = context.options; this.lang = this.options.langInfo; this.documentEventHandlers = {}; this.$dropzone = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(['
                    ', '
                    ', '
                    '].join('')).prependTo(this.$editor); } /** * attach Drag and Drop Events */ Dropzone_createClass(Dropzone, [{ key: "initialize", value: function initialize() { if (this.options.disableDragAndDrop) { // prevent default drop event this.documentEventHandlers.onDrop = function (e) { e.preventDefault(); }; // do not consider outside of dropzone this.$eventListener = this.$dropzone; this.$eventListener.on('drop', this.documentEventHandlers.onDrop); } else { this.attachDragAndDropEvent(); } } /** * attach Drag and Drop Events */ }, { key: "attachDragAndDropEvent", value: function attachDragAndDropEvent() { var _this = this; var collection = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(); var $dropzoneMessage = this.$dropzone.find('.note-dropzone-message'); this.documentEventHandlers.onDragenter = function (e) { var isCodeview = _this.context.invoke('codeview.isActivated'); var hasEditorSize = _this.$editor.width() > 0 && _this.$editor.height() > 0; if (!isCodeview && !collection.length && hasEditorSize) { _this.$editor.addClass('dragover'); _this.$dropzone.width(_this.$editor.width()); _this.$dropzone.height(_this.$editor.height()); $dropzoneMessage.text(_this.lang.image.dragImageHere); } collection = collection.add(e.target); }; this.documentEventHandlers.onDragleave = function (e) { collection = collection.not(e.target); // If nodeName is BODY, then just make it over (fix for IE) if (!collection.length || e.target.nodeName === 'BODY') { collection = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(); _this.$editor.removeClass('dragover'); } }; this.documentEventHandlers.onDrop = function () { collection = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(); _this.$editor.removeClass('dragover'); }; // show dropzone on dragenter when dragging a object to document // -but only if the editor is visible, i.e. has a positive width and height this.$eventListener.on('dragenter', this.documentEventHandlers.onDragenter).on('dragleave', this.documentEventHandlers.onDragleave).on('drop', this.documentEventHandlers.onDrop); // change dropzone's message on hover. this.$dropzone.on('dragenter', function () { _this.$dropzone.addClass('hover'); $dropzoneMessage.text(_this.lang.image.dropImage); }).on('dragleave', function () { _this.$dropzone.removeClass('hover'); $dropzoneMessage.text(_this.lang.image.dragImageHere); }); // attach dropImage this.$dropzone.on('drop', function (event) { var dataTransfer = event.originalEvent.dataTransfer; // stop the browser from opening the dropped content event.preventDefault(); if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { _this.$editable.focus(); _this.context.invoke('editor.insertImagesOrCallback', dataTransfer.files); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(dataTransfer.types, function (idx, type) { // skip moz-specific types if (type.toLowerCase().indexOf('_moz_') > -1) { return; } var content = dataTransfer.getData(type); if (type.toLowerCase().indexOf('text') > -1) { _this.context.invoke('editor.pasteHTML', content); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(content).each(function (idx, item) { _this.context.invoke('editor.insertNode', item); }); } }); } }).on('dragover', false); // prevent default dragover event } }, { key: "destroy", value: function destroy() { var _this2 = this; Object.keys(this.documentEventHandlers).forEach(function (key) { _this2.$eventListener.off(key.substr(2).toLowerCase(), _this2.documentEventHandlers[key]); }); this.documentEventHandlers = {}; } }]); return Dropzone; }(); // CONCATENATED MODULE: ./src/js/base/module/Codeview.js function _createForOfIteratorHelper(o) { if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) { var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var it, normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function Codeview_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Codeview_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Codeview_createClass(Constructor, protoProps, staticProps) { if (protoProps) Codeview_defineProperties(Constructor.prototype, protoProps); if (staticProps) Codeview_defineProperties(Constructor, staticProps); return Constructor; } /** * @class Codeview */ var Codeview_CodeView = /*#__PURE__*/function () { function CodeView(context) { Codeview_classCallCheck(this, CodeView); this.context = context; this.$editor = context.layoutInfo.editor; this.$editable = context.layoutInfo.editable; this.$codable = context.layoutInfo.codable; this.options = context.options; this.CodeMirrorConstructor = window.CodeMirror; if (this.options.codemirror.CodeMirrorConstructor) { this.CodeMirrorConstructor = this.options.codemirror.CodeMirrorConstructor; } } Codeview_createClass(CodeView, [{ key: "sync", value: function sync(html) { var isCodeview = this.isActivated(); var CodeMirror = this.CodeMirrorConstructor; if (isCodeview) { if (html) { if (CodeMirror) { this.$codable.data('cmEditor').getDoc().setValue(html); } else { this.$codable.val(html); } } else { if (CodeMirror) { this.$codable.data('cmEditor').save(); } } } } }, { key: "initialize", value: function initialize() { var _this = this; this.$codable.on('keyup', function (event) { if (event.keyCode === core_key.code.ESCAPE) { _this.deactivate(); } }); } /** * @return {Boolean} */ }, { key: "isActivated", value: function isActivated() { return this.$editor.hasClass('codeview'); } /** * toggle codeview */ }, { key: "toggle", value: function toggle() { if (this.isActivated()) { this.deactivate(); } else { this.activate(); } this.context.triggerEvent('codeview.toggled'); } /** * purify input value * @param value * @returns {*} */ }, { key: "purify", value: function purify(value) { if (this.options.codeviewFilter) { // filter code view regex value = value.replace(this.options.codeviewFilterRegex, ''); // allow specific iframe tag if (this.options.codeviewIframeFilter) { var whitelist = this.options.codeviewIframeWhitelistSrc.concat(this.options.codeviewIframeWhitelistSrcBase); value = value.replace(/(.*?(?:<\/iframe>)?)/gi, function (tag) { // remove if src attribute is duplicated if (/<.+src(?==?('|"|\s)?)[\s\S]+src(?=('|"|\s)?)[^>]*?>/i.test(tag)) { return ''; } var _iterator = _createForOfIteratorHelper(whitelist), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var src = _step.value; // pass if src is trusted if (new RegExp('src="(https?:)?\/\/' + src.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '\/(.+)"').test(tag)) { return tag; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return ''; }); } } return value; } /** * activate code view */ }, { key: "activate", value: function activate() { var _this2 = this; var CodeMirror = this.CodeMirrorConstructor; this.$codable.val(dom.html(this.$editable, this.options.prettifyHtml)); this.$codable.height(this.$editable.height()); this.context.invoke('toolbar.updateCodeview', true); this.context.invoke('airPopover.updateCodeview', true); this.$editor.addClass('codeview'); this.$codable.focus(); // activate CodeMirror as codable if (CodeMirror) { var cmEditor = CodeMirror.fromTextArea(this.$codable[0], this.options.codemirror); // CodeMirror TernServer if (this.options.codemirror.tern) { var server = new CodeMirror.TernServer(this.options.codemirror.tern); cmEditor.ternServer = server; cmEditor.on('cursorActivity', function (cm) { server.updateArgHints(cm); }); } cmEditor.on('blur', function (event) { _this2.context.triggerEvent('blur.codeview', cmEditor.getValue(), event); }); cmEditor.on('change', function () { _this2.context.triggerEvent('change.codeview', cmEditor.getValue(), cmEditor); }); // CodeMirror hasn't Padding. cmEditor.setSize(null, this.$editable.outerHeight()); this.$codable.data('cmEditor', cmEditor); } else { this.$codable.on('blur', function (event) { _this2.context.triggerEvent('blur.codeview', _this2.$codable.val(), event); }); this.$codable.on('input', function () { _this2.context.triggerEvent('change.codeview', _this2.$codable.val(), _this2.$codable); }); } } /** * deactivate code view */ }, { key: "deactivate", value: function deactivate() { var CodeMirror = this.CodeMirrorConstructor; // deactivate CodeMirror as codable if (CodeMirror) { var cmEditor = this.$codable.data('cmEditor'); this.$codable.val(cmEditor.getValue()); cmEditor.toTextArea(); } var value = this.purify(dom.value(this.$codable, this.options.prettifyHtml) || dom.emptyPara); var isChange = this.$editable.html() !== value; this.$editable.html(value); this.$editable.height(this.options.height ? this.$codable.height() : 'auto'); this.$editor.removeClass('codeview'); if (isChange) { this.context.triggerEvent('change', this.$editable.html(), this.$editable); } this.$editable.focus(); this.context.invoke('toolbar.updateCodeview', false); this.context.invoke('airPopover.updateCodeview', false); } }, { key: "destroy", value: function destroy() { if (this.isActivated()) { this.deactivate(); } } }]); return CodeView; }(); // CONCATENATED MODULE: ./src/js/base/module/Statusbar.js function Statusbar_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Statusbar_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Statusbar_createClass(Constructor, protoProps, staticProps) { if (protoProps) Statusbar_defineProperties(Constructor.prototype, protoProps); if (staticProps) Statusbar_defineProperties(Constructor, staticProps); return Constructor; } var EDITABLE_PADDING = 24; var Statusbar_Statusbar = /*#__PURE__*/function () { function Statusbar(context) { Statusbar_classCallCheck(this, Statusbar); this.$document = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); this.$statusbar = context.layoutInfo.statusbar; this.$editable = context.layoutInfo.editable; this.options = context.options; } Statusbar_createClass(Statusbar, [{ key: "initialize", value: function initialize() { var _this = this; if (this.options.airMode || this.options.disableResizeEditor) { this.destroy(); return; } this.$statusbar.on('mousedown', function (event) { event.preventDefault(); event.stopPropagation(); var editableTop = _this.$editable.offset().top - _this.$document.scrollTop(); var onMouseMove = function onMouseMove(event) { var height = event.clientY - (editableTop + EDITABLE_PADDING); height = _this.options.minheight > 0 ? Math.max(height, _this.options.minheight) : height; height = _this.options.maxHeight > 0 ? Math.min(height, _this.options.maxHeight) : height; _this.$editable.height(height); }; _this.$document.on('mousemove', onMouseMove).one('mouseup', function () { _this.$document.off('mousemove', onMouseMove); }); }); } }, { key: "destroy", value: function destroy() { this.$statusbar.off(); this.$statusbar.addClass('locked'); } }]); return Statusbar; }(); // CONCATENATED MODULE: ./src/js/base/module/Fullscreen.js function Fullscreen_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Fullscreen_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Fullscreen_createClass(Constructor, protoProps, staticProps) { if (protoProps) Fullscreen_defineProperties(Constructor.prototype, protoProps); if (staticProps) Fullscreen_defineProperties(Constructor, staticProps); return Constructor; } var Fullscreen_Fullscreen = /*#__PURE__*/function () { function Fullscreen(context) { var _this = this; Fullscreen_classCallCheck(this, Fullscreen); this.context = context; this.$editor = context.layoutInfo.editor; this.$toolbar = context.layoutInfo.toolbar; this.$editable = context.layoutInfo.editable; this.$codable = context.layoutInfo.codable; this.$window = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(window); this.$scrollbar = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('html, body'); this.onResize = function () { _this.resizeTo({ h: _this.$window.height() - _this.$toolbar.outerHeight() }); }; } Fullscreen_createClass(Fullscreen, [{ key: "resizeTo", value: function resizeTo(size) { this.$editable.css('height', size.h); this.$codable.css('height', size.h); if (this.$codable.data('cmeditor')) { this.$codable.data('cmeditor').setsize(null, size.h); } } /** * toggle fullscreen */ }, { key: "toggle", value: function toggle() { this.$editor.toggleClass('fullscreen'); if (this.isFullscreen()) { this.$editable.data('orgHeight', this.$editable.css('height')); this.$editable.data('orgMaxHeight', this.$editable.css('maxHeight')); this.$editable.css('maxHeight', ''); this.$window.on('resize', this.onResize).trigger('resize'); this.$scrollbar.css('overflow', 'hidden'); } else { this.$window.off('resize', this.onResize); this.resizeTo({ h: this.$editable.data('orgHeight') }); this.$editable.css('maxHeight', this.$editable.css('orgMaxHeight')); this.$scrollbar.css('overflow', 'visible'); } this.context.invoke('toolbar.updateFullscreen', this.isFullscreen()); } }, { key: "isFullscreen", value: function isFullscreen() { return this.$editor.hasClass('fullscreen'); } }]); return Fullscreen; }(); // CONCATENATED MODULE: ./src/js/base/module/Handle.js function Handle_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Handle_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Handle_createClass(Constructor, protoProps, staticProps) { if (protoProps) Handle_defineProperties(Constructor.prototype, protoProps); if (staticProps) Handle_defineProperties(Constructor, staticProps); return Constructor; } var Handle_Handle = /*#__PURE__*/function () { function Handle(context) { var _this = this; Handle_classCallCheck(this, Handle); this.context = context; this.$document = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); this.$editingArea = context.layoutInfo.editingArea; this.options = context.options; this.lang = this.options.langInfo; this.events = { 'summernote.mousedown': function summernoteMousedown(we, e) { if (_this.update(e.target, e)) { e.preventDefault(); } }, 'summernote.keyup summernote.scroll summernote.change summernote.dialog.shown': function summernoteKeyupSummernoteScrollSummernoteChangeSummernoteDialogShown() { _this.update(); }, 'summernote.disable summernote.blur': function summernoteDisableSummernoteBlur() { _this.hide(); }, 'summernote.codeview.toggled': function summernoteCodeviewToggled() { _this.update(); } }; } Handle_createClass(Handle, [{ key: "initialize", value: function initialize() { var _this2 = this; this.$handle = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(['
                    ', '
                    ', '
                    ', '
                    ', '
                    ', '
                    ', '
                    ', this.options.disableResizeImage ? '' : '
                    ', '
                    ', '
                    '].join('')).prependTo(this.$editingArea); this.$handle.on('mousedown', function (event) { if (dom.isControlSizing(event.target)) { event.preventDefault(); event.stopPropagation(); var $target = _this2.$handle.find('.note-control-selection').data('target'); var posStart = $target.offset(); var scrollTop = _this2.$document.scrollTop(); var onMouseMove = function onMouseMove(event) { _this2.context.invoke('editor.resizeTo', { x: event.clientX - posStart.left, y: event.clientY - (posStart.top - scrollTop) }, $target, !event.shiftKey); _this2.update($target[0], event); }; _this2.$document.on('mousemove', onMouseMove).one('mouseup', function (e) { e.preventDefault(); _this2.$document.off('mousemove', onMouseMove); _this2.context.invoke('editor.afterCommand'); }); if (!$target.data('ratio')) { // original ratio. $target.data('ratio', $target.height() / $target.width()); } } }); // Listen for scrolling on the handle overlay. this.$handle.on('wheel', function (e) { e.preventDefault(); _this2.update(); }); } }, { key: "destroy", value: function destroy() { this.$handle.remove(); } }, { key: "update", value: function update(target, event) { if (this.context.isDisabled()) { return false; } var isImage = dom.isImg(target); var $selection = this.$handle.find('.note-control-selection'); this.context.invoke('imagePopover.update', target, event); if (isImage) { var $image = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(target); var position = $image.position(); var pos = { left: position.left + parseInt($image.css('marginLeft'), 10), top: position.top + parseInt($image.css('marginTop'), 10) }; // exclude margin var imageSize = { w: $image.outerWidth(false), h: $image.outerHeight(false) }; $selection.css({ display: 'block', left: pos.left, top: pos.top, width: imageSize.w, height: imageSize.h }).data('target', $image); // save current image element. var origImageObj = new Image(); origImageObj.src = $image.attr('src'); var sizingText = imageSize.w + 'x' + imageSize.h + ' (' + this.lang.image.original + ': ' + origImageObj.width + 'x' + origImageObj.height + ')'; $selection.find('.note-control-selection-info').text(sizingText); this.context.invoke('editor.saveTarget', target); } else { this.hide(); } return isImage; } /** * hide * * @param {jQuery} $handle */ }, { key: "hide", value: function hide() { this.context.invoke('editor.clearTarget'); this.$handle.children().hide(); } }]); return Handle; }(); // CONCATENATED MODULE: ./src/js/base/module/AutoLink.js function AutoLink_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function AutoLink_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function AutoLink_createClass(Constructor, protoProps, staticProps) { if (protoProps) AutoLink_defineProperties(Constructor.prototype, protoProps); if (staticProps) AutoLink_defineProperties(Constructor, staticProps); return Constructor; } var defaultScheme = 'http://'; var linkPattern = /^([A-Za-z][A-Za-z0-9+-.]*\:[\/]{2}|tel:|mailto:[A-Z0-9._%+-]+@)?(www\.)?(.+)$/i; var AutoLink_AutoLink = /*#__PURE__*/function () { function AutoLink(context) { var _this = this; AutoLink_classCallCheck(this, AutoLink); this.context = context; this.options = context.options; this.events = { 'summernote.keyup': function summernoteKeyup(we, e) { if (!e.isDefaultPrevented()) { _this.handleKeyup(e); } }, 'summernote.keydown': function summernoteKeydown(we, e) { _this.handleKeydown(e); } }; } AutoLink_createClass(AutoLink, [{ key: "initialize", value: function initialize() { this.lastWordRange = null; } }, { key: "destroy", value: function destroy() { this.lastWordRange = null; } }, { key: "replace", value: function replace() { if (!this.lastWordRange) { return; } var keyword = this.lastWordRange.toString(); var match = keyword.match(linkPattern); if (match && (match[1] || match[2])) { var link = match[1] ? keyword : defaultScheme + keyword; var urlText = this.options.showDomainOnlyForAutolink ? keyword.replace(/^(?:https?:\/\/)?(?:tel?:?)?(?:mailto?:?)?(?:www\.)?/i, '').split('/')[0] : keyword; var node = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('').html(urlText).attr('href', link)[0]; if (this.context.options.linkTargetBlank) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(node).attr('target', '_blank'); } this.lastWordRange.insertNode(node); this.lastWordRange = null; this.context.invoke('editor.focus'); } } }, { key: "handleKeydown", value: function handleKeydown(e) { if (lists.contains([core_key.code.ENTER, core_key.code.SPACE], e.keyCode)) { var wordRange = this.context.invoke('editor.createRange').getWordRange(); this.lastWordRange = wordRange; } } }, { key: "handleKeyup", value: function handleKeyup(e) { if (lists.contains([core_key.code.ENTER, core_key.code.SPACE], e.keyCode)) { this.replace(); } } }]); return AutoLink; }(); // CONCATENATED MODULE: ./src/js/base/module/AutoSync.js function AutoSync_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function AutoSync_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function AutoSync_createClass(Constructor, protoProps, staticProps) { if (protoProps) AutoSync_defineProperties(Constructor.prototype, protoProps); if (staticProps) AutoSync_defineProperties(Constructor, staticProps); return Constructor; } /** * textarea auto sync. */ var AutoSync_AutoSync = /*#__PURE__*/function () { function AutoSync(context) { var _this = this; AutoSync_classCallCheck(this, AutoSync); this.$note = context.layoutInfo.note; this.events = { 'summernote.change': function summernoteChange() { _this.$note.val(context.invoke('code')); } }; } AutoSync_createClass(AutoSync, [{ key: "shouldInitialize", value: function shouldInitialize() { return dom.isTextarea(this.$note[0]); } }]); return AutoSync; }(); // CONCATENATED MODULE: ./src/js/base/module/AutoReplace.js function AutoReplace_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function AutoReplace_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function AutoReplace_createClass(Constructor, protoProps, staticProps) { if (protoProps) AutoReplace_defineProperties(Constructor.prototype, protoProps); if (staticProps) AutoReplace_defineProperties(Constructor, staticProps); return Constructor; } var AutoReplace_AutoReplace = /*#__PURE__*/function () { function AutoReplace(context) { var _this = this; AutoReplace_classCallCheck(this, AutoReplace); this.context = context; this.options = context.options.replace || {}; this.keys = [core_key.code.ENTER, core_key.code.SPACE, core_key.code.PERIOD, core_key.code.COMMA, core_key.code.SEMICOLON, core_key.code.SLASH]; this.previousKeydownCode = null; this.events = { 'summernote.keyup': function summernoteKeyup(we, e) { if (!e.isDefaultPrevented()) { _this.handleKeyup(e); } }, 'summernote.keydown': function summernoteKeydown(we, e) { _this.handleKeydown(e); } }; } AutoReplace_createClass(AutoReplace, [{ key: "shouldInitialize", value: function shouldInitialize() { return !!this.options.match; } }, { key: "initialize", value: function initialize() { this.lastWord = null; } }, { key: "destroy", value: function destroy() { this.lastWord = null; } }, { key: "replace", value: function replace() { if (!this.lastWord) { return; } var self = this; var keyword = this.lastWord.toString(); this.options.match(keyword, function (match) { if (match) { var node = ''; if (typeof match === 'string') { node = dom.createText(match); } else if (match instanceof jQuery) { node = match[0]; } else if (match instanceof Node) { node = match; } if (!node) return; self.lastWord.insertNode(node); self.lastWord = null; self.context.invoke('editor.focus'); } }); } }, { key: "handleKeydown", value: function handleKeydown(e) { // this forces it to remember the last whole word, even if multiple termination keys are pressed // before the previous key is let go. if (this.previousKeydownCode && lists.contains(this.keys, this.previousKeydownCode)) { this.previousKeydownCode = e.keyCode; return; } if (lists.contains(this.keys, e.keyCode)) { var wordRange = this.context.invoke('editor.createRange').getWordRange(); this.lastWord = wordRange; } this.previousKeydownCode = e.keyCode; } }, { key: "handleKeyup", value: function handleKeyup(e) { if (lists.contains(this.keys, e.keyCode)) { this.replace(); } } }]); return AutoReplace; }(); // CONCATENATED MODULE: ./src/js/base/module/Placeholder.js function Placeholder_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Placeholder_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Placeholder_createClass(Constructor, protoProps, staticProps) { if (protoProps) Placeholder_defineProperties(Constructor.prototype, protoProps); if (staticProps) Placeholder_defineProperties(Constructor, staticProps); return Constructor; } var Placeholder_Placeholder = /*#__PURE__*/function () { function Placeholder(context) { var _this = this; Placeholder_classCallCheck(this, Placeholder); this.context = context; this.$editingArea = context.layoutInfo.editingArea; this.options = context.options; if (this.options.inheritPlaceholder === true) { // get placeholder value from the original element this.options.placeholder = this.context.$note.attr('placeholder') || this.options.placeholder; } this.events = { 'summernote.init summernote.change': function summernoteInitSummernoteChange() { _this.update(); }, 'summernote.codeview.toggled': function summernoteCodeviewToggled() { _this.update(); } }; } Placeholder_createClass(Placeholder, [{ key: "shouldInitialize", value: function shouldInitialize() { return !!this.options.placeholder; } }, { key: "initialize", value: function initialize() { var _this2 = this; this.$placeholder = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
                    '); this.$placeholder.on('click', function () { _this2.context.invoke('focus'); }).html(this.options.placeholder).prependTo(this.$editingArea); this.update(); } }, { key: "destroy", value: function destroy() { this.$placeholder.remove(); } }, { key: "update", value: function update() { var isShow = !this.context.invoke('codeview.isActivated') && this.context.invoke('editor.isEmpty'); this.$placeholder.toggle(isShow); } }]); return Placeholder; }(); // CONCATENATED MODULE: ./src/js/base/module/Buttons.js function Buttons_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Buttons_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Buttons_createClass(Constructor, protoProps, staticProps) { if (protoProps) Buttons_defineProperties(Constructor.prototype, protoProps); if (staticProps) Buttons_defineProperties(Constructor, staticProps); return Constructor; } var Buttons_Buttons = /*#__PURE__*/function () { function Buttons(context) { Buttons_classCallCheck(this, Buttons); this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.context = context; this.$toolbar = context.layoutInfo.toolbar; this.options = context.options; this.lang = this.options.langInfo; this.invertedKeyMap = func.invertObject(this.options.keyMap[env.isMac ? 'mac' : 'pc']); } Buttons_createClass(Buttons, [{ key: "representShortcut", value: function representShortcut(editorMethod) { var shortcut = this.invertedKeyMap[editorMethod]; if (!this.options.shortcuts || !shortcut) { return ''; } if (env.isMac) { shortcut = shortcut.replace('CMD', '⌘').replace('SHIFT', '⇧'); } shortcut = shortcut.replace('BACKSLASH', '\\').replace('SLASH', '/').replace('LEFTBRACKET', '[').replace('RIGHTBRACKET', ']'); return ' (' + shortcut + ')'; } }, { key: "button", value: function button(o) { if (!this.options.tooltip && o.tooltip) { delete o.tooltip; } o.container = this.options.container; return this.ui.button(o); } }, { key: "initialize", value: function initialize() { this.addToolbarButtons(); this.addImagePopoverButtons(); this.addLinkPopoverButtons(); this.addTablePopoverButtons(); this.fontInstalledMap = {}; } }, { key: "destroy", value: function destroy() { delete this.fontInstalledMap; } }, { key: "isFontInstalled", value: function isFontInstalled(name) { if (!Object.prototype.hasOwnProperty.call(this.fontInstalledMap, name)) { this.fontInstalledMap[name] = env.isFontInstalled(name) || lists.contains(this.options.fontNamesIgnoreCheck, name); } return this.fontInstalledMap[name]; } }, { key: "isFontDeservedToAdd", value: function isFontDeservedToAdd(name) { name = name.toLowerCase(); return name !== '' && this.isFontInstalled(name) && env.genericFontFamilies.indexOf(name) === -1; } }, { key: "colorPalette", value: function colorPalette(className, tooltip, backColor, foreColor) { var _this = this; return this.ui.buttonGroup({ className: 'note-color ' + className, children: [this.button({ className: 'note-current-color-button', contents: this.ui.icon(this.options.icons.font + ' note-recent-color'), tooltip: tooltip, click: function click(e) { var $button = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(e.currentTarget); if (backColor && foreColor) { _this.context.invoke('editor.color', { backColor: $button.attr('data-backColor'), foreColor: $button.attr('data-foreColor') }); } else if (backColor) { _this.context.invoke('editor.color', { backColor: $button.attr('data-backColor') }); } else if (foreColor) { _this.context.invoke('editor.color', { foreColor: $button.attr('data-foreColor') }); } }, callback: function callback($button) { var $recentColor = $button.find('.note-recent-color'); if (backColor) { $recentColor.css('background-color', _this.options.colorButton.backColor); $button.attr('data-backColor', _this.options.colorButton.backColor); } if (foreColor) { $recentColor.css('color', _this.options.colorButton.foreColor); $button.attr('data-foreColor', _this.options.colorButton.foreColor); } else { $recentColor.css('color', 'transparent'); } } }), this.button({ className: 'dropdown-toggle', contents: this.ui.dropdownButtonContents('', this.options), tooltip: this.lang.color.more, data: { toggle: 'dropdown' } }), this.ui.dropdown({ items: (backColor ? ['
                    ', '
                    ' + this.lang.color.background + '
                    ', '
                    ', '', '
                    ', '
                    ', '
                    ', '', '', '
                    ', '
                    ', '
                    '].join('') : '') + (foreColor ? ['
                    ', '
                    ' + this.lang.color.foreground + '
                    ', '
                    ', '', '
                    ', '
                    ', '
                    ', '', '', '
                    ', // Fix missing Div, Commented to find easily if it's wrong '
                    ', '
                    '].join('') : ''), callback: function callback($dropdown) { $dropdown.find('.note-holder').each(function (idx, item) { var $holder = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); $holder.append(_this.ui.palette({ colors: _this.options.colors, colorsName: _this.options.colorsName, eventName: $holder.data('event'), container: _this.options.container, tooltip: _this.options.tooltip }).render()); }); /* TODO: do we have to record recent custom colors within cookies? */ var customColors = [['#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF']]; $dropdown.find('.note-holder-custom').each(function (idx, item) { var $holder = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); $holder.append(_this.ui.palette({ colors: customColors, colorsName: customColors, eventName: $holder.data('event'), container: _this.options.container, tooltip: _this.options.tooltip }).render()); }); $dropdown.find('input[type=color]').each(function (idx, item) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item).change(function () { var $chip = $dropdown.find('#' + external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this).data('event')).find('.note-color-btn').first(); var color = this.value.toUpperCase(); $chip.css('background-color', color).attr('aria-label', color).attr('data-value', color).attr('data-original-title', color); $chip.click(); }); }); }, click: function click(event) { event.stopPropagation(); var $parent = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('.' + className).find('.note-dropdown-menu'); var $button = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(event.target); var eventName = $button.data('event'); var value = $button.attr('data-value'); if (eventName === 'openPalette') { var $picker = $parent.find('#' + value); var $palette = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()($parent.find('#' + $picker.data('event')).find('.note-color-row')[0]); // Shift palette chips var $chip = $palette.find('.note-color-btn').last().detach(); // Set chip attributes var color = $picker.val(); $chip.css('background-color', color).attr('aria-label', color).attr('data-value', color).attr('data-original-title', color); $palette.prepend($chip); $picker.click(); } else { if (lists.contains(['backColor', 'foreColor'], eventName)) { var key = eventName === 'backColor' ? 'background-color' : 'color'; var $color = $button.closest('.note-color').find('.note-recent-color'); var $currentButton = $button.closest('.note-color').find('.note-current-color-button'); $color.css(key, value); $currentButton.attr('data-' + eventName, value); } _this.context.invoke('editor.' + eventName, value); } } })] }).render(); } }, { key: "addToolbarButtons", value: function addToolbarButtons() { var _this2 = this; this.context.memo('button.style', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents(_this2.ui.icon(_this2.options.icons.magic), _this2.options), tooltip: _this2.lang.style.style, data: { toggle: 'dropdown' } }), _this2.ui.dropdown({ className: 'dropdown-style', items: _this2.options.styleTags, title: _this2.lang.style.style, template: function template(item) { // TBD: need to be simplified if (typeof item === 'string') { item = { tag: item, title: Object.prototype.hasOwnProperty.call(_this2.lang.style, item) ? _this2.lang.style[item] : item }; } var tag = item.tag; var title = item.title; var style = item.style ? ' style="' + item.style + '" ' : ''; var className = item.className ? ' class="' + item.className + '"' : ''; return '<' + tag + style + className + '>' + title + ''; }, click: _this2.context.createInvokeHandler('editor.formatBlock') })]).render(); }); var _loop = function _loop(styleIdx, styleLen) { var item = _this2.options.styleTags[styleIdx]; _this2.context.memo('button.style.' + item, function () { return _this2.button({ className: 'note-btn-style-' + item, contents: '
                    ' + item.toUpperCase() + '
                    ', tooltip: _this2.lang.style[item], click: _this2.context.createInvokeHandler('editor.formatBlock') }).render(); }); }; for (var styleIdx = 0, styleLen = this.options.styleTags.length; styleIdx < styleLen; styleIdx++) { _loop(styleIdx, styleLen); } this.context.memo('button.bold', function () { return _this2.button({ className: 'note-btn-bold', contents: _this2.ui.icon(_this2.options.icons.bold), tooltip: _this2.lang.font.bold + _this2.representShortcut('bold'), click: _this2.context.createInvokeHandlerAndUpdateState('editor.bold') }).render(); }); this.context.memo('button.italic', function () { return _this2.button({ className: 'note-btn-italic', contents: _this2.ui.icon(_this2.options.icons.italic), tooltip: _this2.lang.font.italic + _this2.representShortcut('italic'), click: _this2.context.createInvokeHandlerAndUpdateState('editor.italic') }).render(); }); this.context.memo('button.underline', function () { return _this2.button({ className: 'note-btn-underline', contents: _this2.ui.icon(_this2.options.icons.underline), tooltip: _this2.lang.font.underline + _this2.representShortcut('underline'), click: _this2.context.createInvokeHandlerAndUpdateState('editor.underline') }).render(); }); this.context.memo('button.clear', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.eraser), tooltip: _this2.lang.font.clear + _this2.representShortcut('removeFormat'), click: _this2.context.createInvokeHandler('editor.removeFormat') }).render(); }); this.context.memo('button.strikethrough', function () { return _this2.button({ className: 'note-btn-strikethrough', contents: _this2.ui.icon(_this2.options.icons.strikethrough), tooltip: _this2.lang.font.strikethrough + _this2.representShortcut('strikethrough'), click: _this2.context.createInvokeHandlerAndUpdateState('editor.strikethrough') }).render(); }); this.context.memo('button.superscript', function () { return _this2.button({ className: 'note-btn-superscript', contents: _this2.ui.icon(_this2.options.icons.superscript), tooltip: _this2.lang.font.superscript, click: _this2.context.createInvokeHandlerAndUpdateState('editor.superscript') }).render(); }); this.context.memo('button.subscript', function () { return _this2.button({ className: 'note-btn-subscript', contents: _this2.ui.icon(_this2.options.icons.subscript), tooltip: _this2.lang.font.subscript, click: _this2.context.createInvokeHandlerAndUpdateState('editor.subscript') }).render(); }); this.context.memo('button.fontname', function () { var styleInfo = _this2.context.invoke('editor.currentStyle'); if (_this2.options.addDefaultFonts) { // Add 'default' fonts into the fontnames array if not exist external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(styleInfo['font-family'].split(','), function (idx, fontname) { fontname = fontname.trim().replace(/['"]+/g, ''); if (_this2.isFontDeservedToAdd(fontname)) { if (_this2.options.fontNames.indexOf(fontname) === -1) { _this2.options.fontNames.push(fontname); } } }); } return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents('', _this2.options), tooltip: _this2.lang.font.name, data: { toggle: 'dropdown' } }), _this2.ui.dropdownCheck({ className: 'dropdown-fontname', checkClassName: _this2.options.icons.menuCheck, items: _this2.options.fontNames.filter(_this2.isFontInstalled.bind(_this2)), title: _this2.lang.font.name, template: function template(item) { return '' + item + ''; }, click: _this2.context.createInvokeHandlerAndUpdateState('editor.fontName') })]).render(); }); this.context.memo('button.fontsize', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents('', _this2.options), tooltip: _this2.lang.font.size, data: { toggle: 'dropdown' } }), _this2.ui.dropdownCheck({ className: 'dropdown-fontsize', checkClassName: _this2.options.icons.menuCheck, items: _this2.options.fontSizes, title: _this2.lang.font.size, click: _this2.context.createInvokeHandlerAndUpdateState('editor.fontSize') })]).render(); }); this.context.memo('button.fontsizeunit', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents('', _this2.options), tooltip: _this2.lang.font.sizeunit, data: { toggle: 'dropdown' } }), _this2.ui.dropdownCheck({ className: 'dropdown-fontsizeunit', checkClassName: _this2.options.icons.menuCheck, items: _this2.options.fontSizeUnits, title: _this2.lang.font.sizeunit, click: _this2.context.createInvokeHandlerAndUpdateState('editor.fontSizeUnit') })]).render(); }); this.context.memo('button.color', function () { return _this2.colorPalette('note-color-all', _this2.lang.color.recent, true, true); }); this.context.memo('button.forecolor', function () { return _this2.colorPalette('note-color-fore', _this2.lang.color.foreground, false, true); }); this.context.memo('button.backcolor', function () { return _this2.colorPalette('note-color-back', _this2.lang.color.background, true, false); }); this.context.memo('button.ul', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.unorderedlist), tooltip: _this2.lang.lists.unordered + _this2.representShortcut('insertUnorderedList'), click: _this2.context.createInvokeHandler('editor.insertUnorderedList') }).render(); }); this.context.memo('button.ol', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.orderedlist), tooltip: _this2.lang.lists.ordered + _this2.representShortcut('insertOrderedList'), click: _this2.context.createInvokeHandler('editor.insertOrderedList') }).render(); }); var justifyLeft = this.button({ contents: this.ui.icon(this.options.icons.alignLeft), tooltip: this.lang.paragraph.left + this.representShortcut('justifyLeft'), click: this.context.createInvokeHandler('editor.justifyLeft') }); var justifyCenter = this.button({ contents: this.ui.icon(this.options.icons.alignCenter), tooltip: this.lang.paragraph.center + this.representShortcut('justifyCenter'), click: this.context.createInvokeHandler('editor.justifyCenter') }); var justifyRight = this.button({ contents: this.ui.icon(this.options.icons.alignRight), tooltip: this.lang.paragraph.right + this.representShortcut('justifyRight'), click: this.context.createInvokeHandler('editor.justifyRight') }); var justifyFull = this.button({ contents: this.ui.icon(this.options.icons.alignJustify), tooltip: this.lang.paragraph.justify + this.representShortcut('justifyFull'), click: this.context.createInvokeHandler('editor.justifyFull') }); var outdent = this.button({ contents: this.ui.icon(this.options.icons.outdent), tooltip: this.lang.paragraph.outdent + this.representShortcut('outdent'), click: this.context.createInvokeHandler('editor.outdent') }); var indent = this.button({ contents: this.ui.icon(this.options.icons.indent), tooltip: this.lang.paragraph.indent + this.representShortcut('indent'), click: this.context.createInvokeHandler('editor.indent') }); this.context.memo('button.justifyLeft', func.invoke(justifyLeft, 'render')); this.context.memo('button.justifyCenter', func.invoke(justifyCenter, 'render')); this.context.memo('button.justifyRight', func.invoke(justifyRight, 'render')); this.context.memo('button.justifyFull', func.invoke(justifyFull, 'render')); this.context.memo('button.outdent', func.invoke(outdent, 'render')); this.context.memo('button.indent', func.invoke(indent, 'render')); this.context.memo('button.paragraph', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents(_this2.ui.icon(_this2.options.icons.alignLeft), _this2.options), tooltip: _this2.lang.paragraph.paragraph, data: { toggle: 'dropdown' } }), _this2.ui.dropdown([_this2.ui.buttonGroup({ className: 'note-align', children: [justifyLeft, justifyCenter, justifyRight, justifyFull] }), _this2.ui.buttonGroup({ className: 'note-list', children: [outdent, indent] })])]).render(); }); this.context.memo('button.height', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents(_this2.ui.icon(_this2.options.icons.textHeight), _this2.options), tooltip: _this2.lang.font.height, data: { toggle: 'dropdown' } }), _this2.ui.dropdownCheck({ items: _this2.options.lineHeights, checkClassName: _this2.options.icons.menuCheck, className: 'dropdown-line-height', title: _this2.lang.font.height, click: _this2.context.createInvokeHandler('editor.lineHeight') })]).render(); }); this.context.memo('button.table', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents(_this2.ui.icon(_this2.options.icons.table), _this2.options), tooltip: _this2.lang.table.table, data: { toggle: 'dropdown' } }), _this2.ui.dropdown({ title: _this2.lang.table.table, className: 'note-table', items: ['
                    ', '
                    ', '
                    ', '
                    ', '
                    ', '
                    1 x 1
                    '].join('') })], { callback: function callback($node) { var $catcher = $node.find('.note-dimension-picker-mousecatcher'); $catcher.css({ width: _this2.options.insertTableMaxSize.col + 'em', height: _this2.options.insertTableMaxSize.row + 'em' }).mousedown(_this2.context.createInvokeHandler('editor.insertTable')).on('mousemove', _this2.tableMoveHandler.bind(_this2)); } }).render(); }); this.context.memo('button.link', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.link), tooltip: _this2.lang.link.link + _this2.representShortcut('linkDialog.show'), click: _this2.context.createInvokeHandler('linkDialog.show') }).render(); }); this.context.memo('button.picture', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.picture), tooltip: _this2.lang.image.image, click: _this2.context.createInvokeHandler('imageDialog.show') }).render(); }); this.context.memo('button.video', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.video), tooltip: _this2.lang.video.video, click: _this2.context.createInvokeHandler('videoDialog.show') }).render(); }); this.context.memo('button.hr', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.minus), tooltip: _this2.lang.hr.insert + _this2.representShortcut('insertHorizontalRule'), click: _this2.context.createInvokeHandler('editor.insertHorizontalRule') }).render(); }); this.context.memo('button.fullscreen', function () { return _this2.button({ className: 'btn-fullscreen note-codeview-keep', contents: _this2.ui.icon(_this2.options.icons.arrowsAlt), tooltip: _this2.lang.options.fullscreen, click: _this2.context.createInvokeHandler('fullscreen.toggle') }).render(); }); this.context.memo('button.codeview', function () { return _this2.button({ className: 'btn-codeview note-codeview-keep', contents: _this2.ui.icon(_this2.options.icons.code), tooltip: _this2.lang.options.codeview, click: _this2.context.createInvokeHandler('codeview.toggle') }).render(); }); this.context.memo('button.redo', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.redo), tooltip: _this2.lang.history.redo + _this2.representShortcut('redo'), click: _this2.context.createInvokeHandler('editor.redo') }).render(); }); this.context.memo('button.undo', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.undo), tooltip: _this2.lang.history.undo + _this2.representShortcut('undo'), click: _this2.context.createInvokeHandler('editor.undo') }).render(); }); this.context.memo('button.help', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.question), tooltip: _this2.lang.options.help, click: _this2.context.createInvokeHandler('helpDialog.show') }).render(); }); } /** * image: [ * ['imageResize', ['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone']], * ['float', ['floatLeft', 'floatRight', 'floatNone']], * ['remove', ['removeMedia']], * ], */ }, { key: "addImagePopoverButtons", value: function addImagePopoverButtons() { var _this3 = this; // Image Size Buttons this.context.memo('button.resizeFull', function () { return _this3.button({ contents: '100%', tooltip: _this3.lang.image.resizeFull, click: _this3.context.createInvokeHandler('editor.resize', '1') }).render(); }); this.context.memo('button.resizeHalf', function () { return _this3.button({ contents: '50%', tooltip: _this3.lang.image.resizeHalf, click: _this3.context.createInvokeHandler('editor.resize', '0.5') }).render(); }); this.context.memo('button.resizeQuarter', function () { return _this3.button({ contents: '25%', tooltip: _this3.lang.image.resizeQuarter, click: _this3.context.createInvokeHandler('editor.resize', '0.25') }).render(); }); this.context.memo('button.resizeNone', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.rollback), tooltip: _this3.lang.image.resizeNone, click: _this3.context.createInvokeHandler('editor.resize', '0') }).render(); }); // Float Buttons this.context.memo('button.floatLeft', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.floatLeft), tooltip: _this3.lang.image.floatLeft, click: _this3.context.createInvokeHandler('editor.floatMe', 'left') }).render(); }); this.context.memo('button.floatRight', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.floatRight), tooltip: _this3.lang.image.floatRight, click: _this3.context.createInvokeHandler('editor.floatMe', 'right') }).render(); }); this.context.memo('button.floatNone', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.rollback), tooltip: _this3.lang.image.floatNone, click: _this3.context.createInvokeHandler('editor.floatMe', 'none') }).render(); }); // Remove Buttons this.context.memo('button.removeMedia', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.trash), tooltip: _this3.lang.image.remove, click: _this3.context.createInvokeHandler('editor.removeMedia') }).render(); }); } }, { key: "addLinkPopoverButtons", value: function addLinkPopoverButtons() { var _this4 = this; this.context.memo('button.linkDialogShow', function () { return _this4.button({ contents: _this4.ui.icon(_this4.options.icons.link), tooltip: _this4.lang.link.edit, click: _this4.context.createInvokeHandler('linkDialog.show') }).render(); }); this.context.memo('button.unlink', function () { return _this4.button({ contents: _this4.ui.icon(_this4.options.icons.unlink), tooltip: _this4.lang.link.unlink, click: _this4.context.createInvokeHandler('editor.unlink') }).render(); }); } /** * table : [ * ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']], * ['delete', ['deleteRow', 'deleteCol', 'deleteTable']] * ], */ }, { key: "addTablePopoverButtons", value: function addTablePopoverButtons() { var _this5 = this; this.context.memo('button.addRowUp', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.rowAbove), tooltip: _this5.lang.table.addRowAbove, click: _this5.context.createInvokeHandler('editor.addRow', 'top') }).render(); }); this.context.memo('button.addRowDown', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.rowBelow), tooltip: _this5.lang.table.addRowBelow, click: _this5.context.createInvokeHandler('editor.addRow', 'bottom') }).render(); }); this.context.memo('button.addColLeft', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.colBefore), tooltip: _this5.lang.table.addColLeft, click: _this5.context.createInvokeHandler('editor.addCol', 'left') }).render(); }); this.context.memo('button.addColRight', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.colAfter), tooltip: _this5.lang.table.addColRight, click: _this5.context.createInvokeHandler('editor.addCol', 'right') }).render(); }); this.context.memo('button.deleteRow', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.rowRemove), tooltip: _this5.lang.table.delRow, click: _this5.context.createInvokeHandler('editor.deleteRow') }).render(); }); this.context.memo('button.deleteCol', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.colRemove), tooltip: _this5.lang.table.delCol, click: _this5.context.createInvokeHandler('editor.deleteCol') }).render(); }); this.context.memo('button.deleteTable', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.trash), tooltip: _this5.lang.table.delTable, click: _this5.context.createInvokeHandler('editor.deleteTable') }).render(); }); } }, { key: "build", value: function build($container, groups) { for (var groupIdx = 0, groupLen = groups.length; groupIdx < groupLen; groupIdx++) { var group = groups[groupIdx]; var groupName = Array.isArray(group) ? group[0] : group; var buttons = Array.isArray(group) ? group.length === 1 ? [group[0]] : group[1] : [group]; var $group = this.ui.buttonGroup({ className: 'note-' + groupName }).render(); for (var idx = 0, len = buttons.length; idx < len; idx++) { var btn = this.context.memo('button.' + buttons[idx]); if (btn) { $group.append(typeof btn === 'function' ? btn(this.context) : btn); } } $group.appendTo($container); } } /** * @param {jQuery} [$container] */ }, { key: "updateCurrentStyle", value: function updateCurrentStyle($container) { var _this6 = this; var $cont = $container || this.$toolbar; var styleInfo = this.context.invoke('editor.currentStyle'); this.updateBtnStates($cont, { '.note-btn-bold': function noteBtnBold() { return styleInfo['font-bold'] === 'bold'; }, '.note-btn-italic': function noteBtnItalic() { return styleInfo['font-italic'] === 'italic'; }, '.note-btn-underline': function noteBtnUnderline() { return styleInfo['font-underline'] === 'underline'; }, '.note-btn-subscript': function noteBtnSubscript() { return styleInfo['font-subscript'] === 'subscript'; }, '.note-btn-superscript': function noteBtnSuperscript() { return styleInfo['font-superscript'] === 'superscript'; }, '.note-btn-strikethrough': function noteBtnStrikethrough() { return styleInfo['font-strikethrough'] === 'strikethrough'; } }); if (styleInfo['font-family']) { var fontNames = styleInfo['font-family'].split(',').map(function (name) { return name.replace(/[\'\"]/g, '').replace(/\s+$/, '').replace(/^\s+/, ''); }); var fontName = lists.find(fontNames, this.isFontInstalled.bind(this)); $cont.find('.dropdown-fontname a').each(function (idx, item) { var $item = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); // always compare string to avoid creating another func. var isChecked = $item.data('value') + '' === fontName + ''; $item.toggleClass('checked', isChecked); }); $cont.find('.note-current-fontname').text(fontName).css('font-family', fontName); } if (styleInfo['font-size']) { var fontSize = styleInfo['font-size']; $cont.find('.dropdown-fontsize a').each(function (idx, item) { var $item = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); // always compare with string to avoid creating another func. var isChecked = $item.data('value') + '' === fontSize + ''; $item.toggleClass('checked', isChecked); }); $cont.find('.note-current-fontsize').text(fontSize); var fontSizeUnit = styleInfo['font-size-unit']; $cont.find('.dropdown-fontsizeunit a').each(function (idx, item) { var $item = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); var isChecked = $item.data('value') + '' === fontSizeUnit + ''; $item.toggleClass('checked', isChecked); }); $cont.find('.note-current-fontsizeunit').text(fontSizeUnit); } if (styleInfo['line-height']) { var lineHeight = styleInfo['line-height']; $cont.find('.dropdown-line-height li a').each(function (idx, item) { // always compare with string to avoid creating another func. var isChecked = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item).data('value') + '' === lineHeight + ''; _this6.className = isChecked ? 'checked' : ''; }); } } }, { key: "updateBtnStates", value: function updateBtnStates($container, infos) { var _this7 = this; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(infos, function (selector, pred) { _this7.ui.toggleBtnActive($container.find(selector), pred()); }); } }, { key: "tableMoveHandler", value: function tableMoveHandler(event) { var PX_PER_EM = 18; var $picker = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(event.target.parentNode); // target is mousecatcher var $dimensionDisplay = $picker.next(); var $catcher = $picker.find('.note-dimension-picker-mousecatcher'); var $highlighted = $picker.find('.note-dimension-picker-highlighted'); var $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted'); var posOffset; // HTML5 with jQuery - e.offsetX is undefined in Firefox if (event.offsetX === undefined) { var posCatcher = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(event.target).offset(); posOffset = { x: event.pageX - posCatcher.left, y: event.pageY - posCatcher.top }; } else { posOffset = { x: event.offsetX, y: event.offsetY }; } var dim = { c: Math.ceil(posOffset.x / PX_PER_EM) || 1, r: Math.ceil(posOffset.y / PX_PER_EM) || 1 }; $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' }); $catcher.data('value', dim.c + 'x' + dim.r); if (dim.c > 3 && dim.c < this.options.insertTableMaxSize.col) { $unhighlighted.css({ width: dim.c + 1 + 'em' }); } if (dim.r > 3 && dim.r < this.options.insertTableMaxSize.row) { $unhighlighted.css({ height: dim.r + 1 + 'em' }); } $dimensionDisplay.html(dim.c + ' x ' + dim.r); } }]); return Buttons; }(); // CONCATENATED MODULE: ./src/js/base/module/Toolbar.js function Toolbar_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Toolbar_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Toolbar_createClass(Constructor, protoProps, staticProps) { if (protoProps) Toolbar_defineProperties(Constructor.prototype, protoProps); if (staticProps) Toolbar_defineProperties(Constructor, staticProps); return Constructor; } var Toolbar_Toolbar = /*#__PURE__*/function () { function Toolbar(context) { Toolbar_classCallCheck(this, Toolbar); this.context = context; this.$window = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(window); this.$document = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.$note = context.layoutInfo.note; this.$editor = context.layoutInfo.editor; this.$toolbar = context.layoutInfo.toolbar; this.$editable = context.layoutInfo.editable; this.$statusbar = context.layoutInfo.statusbar; this.options = context.options; this.isFollowing = false; this.followScroll = this.followScroll.bind(this); } Toolbar_createClass(Toolbar, [{ key: "shouldInitialize", value: function shouldInitialize() { return !this.options.airMode; } }, { key: "initialize", value: function initialize() { var _this = this; this.options.toolbar = this.options.toolbar || []; if (!this.options.toolbar.length) { this.$toolbar.hide(); } else { this.context.invoke('buttons.build', this.$toolbar, this.options.toolbar); } if (this.options.toolbarContainer) { this.$toolbar.appendTo(this.options.toolbarContainer); } this.changeContainer(false); this.$note.on('summernote.keyup summernote.mouseup summernote.change', function () { _this.context.invoke('buttons.updateCurrentStyle'); }); this.context.invoke('buttons.updateCurrentStyle'); if (this.options.followingToolbar) { this.$window.on('scroll resize', this.followScroll); } } }, { key: "destroy", value: function destroy() { this.$toolbar.children().remove(); if (this.options.followingToolbar) { this.$window.off('scroll resize', this.followScroll); } } }, { key: "followScroll", value: function followScroll() { if (this.$editor.hasClass('fullscreen')) { return false; } var editorHeight = this.$editor.outerHeight(); var editorWidth = this.$editor.width(); var toolbarHeight = this.$toolbar.height(); var statusbarHeight = this.$statusbar.height(); // check if the web app is currently using another static bar var otherBarHeight = 0; if (this.options.otherStaticBar) { otherBarHeight = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.options.otherStaticBar).outerHeight(); } var currentOffset = this.$document.scrollTop(); var editorOffsetTop = this.$editor.offset().top; var editorOffsetBottom = editorOffsetTop + editorHeight; var activateOffset = editorOffsetTop - otherBarHeight; var deactivateOffsetBottom = editorOffsetBottom - otherBarHeight - toolbarHeight - statusbarHeight; if (!this.isFollowing && currentOffset > activateOffset && currentOffset < deactivateOffsetBottom - toolbarHeight) { this.isFollowing = true; this.$editable.css({ marginTop: this.$toolbar.outerHeight() }); this.$toolbar.css({ position: 'fixed', top: otherBarHeight, width: editorWidth, zIndex: 1000 }); } else if (this.isFollowing && (currentOffset < activateOffset || currentOffset > deactivateOffsetBottom)) { this.isFollowing = false; this.$toolbar.css({ position: 'relative', top: 0, width: '100%', zIndex: 'auto' }); this.$editable.css({ marginTop: '' }); } } }, { key: "changeContainer", value: function changeContainer(isFullscreen) { if (isFullscreen) { this.$toolbar.prependTo(this.$editor); } else { if (this.options.toolbarContainer) { this.$toolbar.appendTo(this.options.toolbarContainer); } } if (this.options.followingToolbar) { this.followScroll(); } } }, { key: "updateFullscreen", value: function updateFullscreen(isFullscreen) { this.ui.toggleBtnActive(this.$toolbar.find('.btn-fullscreen'), isFullscreen); this.changeContainer(isFullscreen); } }, { key: "updateCodeview", value: function updateCodeview(isCodeview) { this.ui.toggleBtnActive(this.$toolbar.find('.btn-codeview'), isCodeview); if (isCodeview) { this.deactivate(); } else { this.activate(); } } }, { key: "activate", value: function activate(isIncludeCodeview) { var $btn = this.$toolbar.find('button'); if (!isIncludeCodeview) { $btn = $btn.not('.note-codeview-keep'); } this.ui.toggleBtn($btn, true); } }, { key: "deactivate", value: function deactivate(isIncludeCodeview) { var $btn = this.$toolbar.find('button'); if (!isIncludeCodeview) { $btn = $btn.not('.note-codeview-keep'); } this.ui.toggleBtn($btn, false); } }]); return Toolbar; }(); // CONCATENATED MODULE: ./src/js/base/module/LinkDialog.js function LinkDialog_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function LinkDialog_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function LinkDialog_createClass(Constructor, protoProps, staticProps) { if (protoProps) LinkDialog_defineProperties(Constructor.prototype, protoProps); if (staticProps) LinkDialog_defineProperties(Constructor, staticProps); return Constructor; } var LinkDialog_LinkDialog = /*#__PURE__*/function () { function LinkDialog(context) { LinkDialog_classCallCheck(this, LinkDialog); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.$body = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document.body); this.$editor = context.layoutInfo.editor; this.options = context.options; this.lang = this.options.langInfo; context.memo('help.linkDialog.show', this.options.langInfo.help['linkDialog.show']); } LinkDialog_createClass(LinkDialog, [{ key: "initialize", value: function initialize() { var $container = this.options.dialogsInBody ? this.$body : this.options.container; var body = ['
                    ', ""), ""), '
                    ', '
                    ', ""), ""), '
                    ', !this.options.disableLinkTarget ? external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
                    ').append(this.ui.checkbox({ className: 'sn-checkbox-open-in-new-window', text: this.lang.link.openInNewWindow, checked: true }).render()).html() : '', external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
                    ').append(this.ui.checkbox({ className: 'sn-checkbox-use-protocol', text: this.lang.link.useProtocol, checked: true }).render()).html()].join(''); var buttonClass = 'btn btn-primary note-btn note-btn-primary note-link-btn'; var footer = ""); this.$dialog = this.ui.dialog({ className: 'link-dialog', title: this.lang.link.insert, fade: this.options.dialogsFade, body: body, footer: footer }).render().appendTo($container); } }, { key: "destroy", value: function destroy() { this.ui.hideDialog(this.$dialog); this.$dialog.remove(); } }, { key: "bindEnterKey", value: function bindEnterKey($input, $btn) { $input.on('keypress', function (event) { if (event.keyCode === core_key.code.ENTER) { event.preventDefault(); $btn.trigger('click'); } }); } /** * toggle update button */ }, { key: "toggleLinkBtn", value: function toggleLinkBtn($linkBtn, $linkText, $linkUrl) { this.ui.toggleBtn($linkBtn, $linkText.val() && $linkUrl.val()); } /** * Show link dialog and set event handlers on dialog controls. * * @param {Object} linkInfo * @return {Promise} */ }, { key: "showLinkDialog", value: function showLinkDialog(linkInfo) { var _this = this; return external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.Deferred(function (deferred) { var $linkText = _this.$dialog.find('.note-link-text'); var $linkUrl = _this.$dialog.find('.note-link-url'); var $linkBtn = _this.$dialog.find('.note-link-btn'); var $openInNewWindow = _this.$dialog.find('.sn-checkbox-open-in-new-window input[type=checkbox]'); var $useProtocol = _this.$dialog.find('.sn-checkbox-use-protocol input[type=checkbox]'); _this.ui.onDialogShown(_this.$dialog, function () { _this.context.triggerEvent('dialog.shown'); // If no url was given and given text is valid URL then copy that into URL Field if (!linkInfo.url && func.isValidUrl(linkInfo.text)) { linkInfo.url = linkInfo.text; } $linkText.on('input paste propertychange', function () { // If linktext was modified by input events, // cloning text from linkUrl will be stopped. linkInfo.text = $linkText.val(); _this.toggleLinkBtn($linkBtn, $linkText, $linkUrl); }).val(linkInfo.text); $linkUrl.on('input paste propertychange', function () { // Display same text on `Text to display` as default // when linktext has no text if (!linkInfo.text) { $linkText.val($linkUrl.val()); } _this.toggleLinkBtn($linkBtn, $linkText, $linkUrl); }).val(linkInfo.url); if (!env.isSupportTouch) { $linkUrl.trigger('focus'); } _this.toggleLinkBtn($linkBtn, $linkText, $linkUrl); _this.bindEnterKey($linkUrl, $linkBtn); _this.bindEnterKey($linkText, $linkBtn); var isNewWindowChecked = linkInfo.isNewWindow !== undefined ? linkInfo.isNewWindow : _this.context.options.linkTargetBlank; $openInNewWindow.prop('checked', isNewWindowChecked); var useProtocolChecked = linkInfo.url ? false : _this.context.options.useProtocol; $useProtocol.prop('checked', useProtocolChecked); $linkBtn.one('click', function (event) { event.preventDefault(); deferred.resolve({ range: linkInfo.range, url: $linkUrl.val(), text: $linkText.val(), isNewWindow: $openInNewWindow.is(':checked'), checkProtocol: $useProtocol.is(':checked') }); _this.ui.hideDialog(_this.$dialog); }); }); _this.ui.onDialogHidden(_this.$dialog, function () { // detach events $linkText.off(); $linkUrl.off(); $linkBtn.off(); if (deferred.state() === 'pending') { deferred.reject(); } }); _this.ui.showDialog(_this.$dialog); }).promise(); } /** * @param {Object} layoutInfo */ }, { key: "show", value: function show() { var _this2 = this; var linkInfo = this.context.invoke('editor.getLinkInfo'); this.context.invoke('editor.saveRange'); this.showLinkDialog(linkInfo).then(function (linkInfo) { _this2.context.invoke('editor.restoreRange'); _this2.context.invoke('editor.createLink', linkInfo); }).fail(function () { _this2.context.invoke('editor.restoreRange'); }); } }]); return LinkDialog; }(); // CONCATENATED MODULE: ./src/js/base/module/LinkPopover.js function LinkPopover_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function LinkPopover_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function LinkPopover_createClass(Constructor, protoProps, staticProps) { if (protoProps) LinkPopover_defineProperties(Constructor.prototype, protoProps); if (staticProps) LinkPopover_defineProperties(Constructor, staticProps); return Constructor; } var LinkPopover_LinkPopover = /*#__PURE__*/function () { function LinkPopover(context) { var _this = this; LinkPopover_classCallCheck(this, LinkPopover); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.options = context.options; this.events = { 'summernote.keyup summernote.mouseup summernote.change summernote.scroll': function summernoteKeyupSummernoteMouseupSummernoteChangeSummernoteScroll() { _this.update(); }, 'summernote.disable summernote.dialog.shown summernote.blur': function summernoteDisableSummernoteDialogShownSummernoteBlur() { _this.hide(); } }; } LinkPopover_createClass(LinkPopover, [{ key: "shouldInitialize", value: function shouldInitialize() { return !lists.isEmpty(this.options.popover.link); } }, { key: "initialize", value: function initialize() { this.$popover = this.ui.popover({ className: 'note-link-popover', callback: function callback($node) { var $content = $node.find('.popover-content,.note-popover-content'); $content.prepend(' '); } }).render().appendTo(this.options.container); var $content = this.$popover.find('.popover-content,.note-popover-content'); this.context.invoke('buttons.build', $content, this.options.popover.link); this.$popover.on('mousedown', function (e) { e.preventDefault(); }); } }, { key: "destroy", value: function destroy() { this.$popover.remove(); } }, { key: "update", value: function update() { // Prevent focusing on editable when invoke('code') is executed if (!this.context.invoke('editor.hasFocus')) { this.hide(); return; } var rng = this.context.invoke('editor.getLastRange'); if (rng.isCollapsed() && rng.isOnAnchor()) { var anchor = dom.ancestor(rng.sc, dom.isAnchor); var href = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(anchor).attr('href'); this.$popover.find('a').attr('href', href).text(href); var pos = dom.posFromPlaceholder(anchor); var containerOffset = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.options.container).offset(); pos.top -= containerOffset.top; pos.left -= containerOffset.left; this.$popover.css({ display: 'block', left: pos.left, top: pos.top }); } else { this.hide(); } } }, { key: "hide", value: function hide() { this.$popover.hide(); } }]); return LinkPopover; }(); // CONCATENATED MODULE: ./src/js/base/module/ImageDialog.js function ImageDialog_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function ImageDialog_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function ImageDialog_createClass(Constructor, protoProps, staticProps) { if (protoProps) ImageDialog_defineProperties(Constructor.prototype, protoProps); if (staticProps) ImageDialog_defineProperties(Constructor, staticProps); return Constructor; } var ImageDialog_ImageDialog = /*#__PURE__*/function () { function ImageDialog(context) { ImageDialog_classCallCheck(this, ImageDialog); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.$body = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document.body); this.$editor = context.layoutInfo.editor; this.options = context.options; this.lang = this.options.langInfo; } ImageDialog_createClass(ImageDialog, [{ key: "initialize", value: function initialize() { var imageLimitation = ''; if (this.options.maximumImageFileSize) { var unit = Math.floor(Math.log(this.options.maximumImageFileSize) / Math.log(1024)); var readableSize = (this.options.maximumImageFileSize / Math.pow(1024, unit)).toFixed(2) * 1 + ' ' + ' KMGTP'[unit] + 'B'; imageLimitation = "".concat(this.lang.image.maximumFileSize + ' : ' + readableSize, ""); } var $container = this.options.dialogsInBody ? this.$body : this.options.container; var body = ['
                    ', '', '', imageLimitation, '
                    ', '
                    ', '', '', '
                    '].join(''); var buttonClass = 'btn btn-primary note-btn note-btn-primary note-image-btn'; var footer = ""); this.$dialog = this.ui.dialog({ title: this.lang.image.insert, fade: this.options.dialogsFade, body: body, footer: footer }).render().appendTo($container); } }, { key: "destroy", value: function destroy() { this.ui.hideDialog(this.$dialog); this.$dialog.remove(); } }, { key: "bindEnterKey", value: function bindEnterKey($input, $btn) { $input.on('keypress', function (event) { if (event.keyCode === core_key.code.ENTER) { event.preventDefault(); $btn.trigger('click'); } }); } }, { key: "show", value: function show() { var _this = this; this.context.invoke('editor.saveRange'); this.showImageDialog().then(function (data) { // [workaround] hide dialog before restore range for IE range focus _this.ui.hideDialog(_this.$dialog); _this.context.invoke('editor.restoreRange'); if (typeof data === 'string') { // image url // If onImageLinkInsert set, if (_this.options.callbacks.onImageLinkInsert) { _this.context.triggerEvent('image.link.insert', data); } else { _this.context.invoke('editor.insertImage', data); } } else { // array of files _this.context.invoke('editor.insertImagesOrCallback', data); } }).fail(function () { _this.context.invoke('editor.restoreRange'); }); } /** * show image dialog * * @param {jQuery} $dialog * @return {Promise} */ }, { key: "showImageDialog", value: function showImageDialog() { var _this2 = this; return external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.Deferred(function (deferred) { var $imageInput = _this2.$dialog.find('.note-image-input'); var $imageUrl = _this2.$dialog.find('.note-image-url'); var $imageBtn = _this2.$dialog.find('.note-image-btn'); _this2.ui.onDialogShown(_this2.$dialog, function () { _this2.context.triggerEvent('dialog.shown'); // Cloning imageInput to clear element. $imageInput.replaceWith($imageInput.clone().on('change', function (event) { deferred.resolve(event.target.files || event.target.value); }).val('')); $imageUrl.on('input paste propertychange', function () { _this2.ui.toggleBtn($imageBtn, $imageUrl.val()); }).val(''); if (!env.isSupportTouch) { $imageUrl.trigger('focus'); } $imageBtn.click(function (event) { event.preventDefault(); deferred.resolve($imageUrl.val()); }); _this2.bindEnterKey($imageUrl, $imageBtn); }); _this2.ui.onDialogHidden(_this2.$dialog, function () { $imageInput.off(); $imageUrl.off(); $imageBtn.off(); if (deferred.state() === 'pending') { deferred.reject(); } }); _this2.ui.showDialog(_this2.$dialog); }); } }]); return ImageDialog; }(); // CONCATENATED MODULE: ./src/js/base/module/ImagePopover.js function ImagePopover_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function ImagePopover_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function ImagePopover_createClass(Constructor, protoProps, staticProps) { if (protoProps) ImagePopover_defineProperties(Constructor.prototype, protoProps); if (staticProps) ImagePopover_defineProperties(Constructor, staticProps); return Constructor; } /** * Image popover module * mouse events that show/hide popover will be handled by Handle.js. * Handle.js will receive the events and invoke 'imagePopover.update'. */ var ImagePopover_ImagePopover = /*#__PURE__*/function () { function ImagePopover(context) { var _this = this; ImagePopover_classCallCheck(this, ImagePopover); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.editable = context.layoutInfo.editable[0]; this.options = context.options; this.events = { 'summernote.disable summernote.blur': function summernoteDisableSummernoteBlur() { _this.hide(); } }; } ImagePopover_createClass(ImagePopover, [{ key: "shouldInitialize", value: function shouldInitialize() { return !lists.isEmpty(this.options.popover.image); } }, { key: "initialize", value: function initialize() { this.$popover = this.ui.popover({ className: 'note-image-popover' }).render().appendTo(this.options.container); var $content = this.$popover.find('.popover-content,.note-popover-content'); this.context.invoke('buttons.build', $content, this.options.popover.image); this.$popover.on('mousedown', function (e) { e.preventDefault(); }); } }, { key: "destroy", value: function destroy() { this.$popover.remove(); } }, { key: "update", value: function update(target, event) { if (dom.isImg(target)) { var position = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(target).offset(); var containerOffset = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.options.container).offset(); var pos = {}; if (this.options.popatmouse) { pos.left = event.pageX - 20; pos.top = event.pageY; } else { pos = position; } pos.top -= containerOffset.top; pos.left -= containerOffset.left; this.$popover.css({ display: 'block', left: pos.left, top: pos.top }); } else { this.hide(); } } }, { key: "hide", value: function hide() { this.$popover.hide(); } }]); return ImagePopover; }(); // CONCATENATED MODULE: ./src/js/base/module/TablePopover.js function TablePopover_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function TablePopover_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function TablePopover_createClass(Constructor, protoProps, staticProps) { if (protoProps) TablePopover_defineProperties(Constructor.prototype, protoProps); if (staticProps) TablePopover_defineProperties(Constructor, staticProps); return Constructor; } var TablePopover_TablePopover = /*#__PURE__*/function () { function TablePopover(context) { var _this = this; TablePopover_classCallCheck(this, TablePopover); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.options = context.options; this.events = { 'summernote.mousedown': function summernoteMousedown(we, e) { _this.update(e.target); }, 'summernote.keyup summernote.scroll summernote.change': function summernoteKeyupSummernoteScrollSummernoteChange() { _this.update(); }, 'summernote.disable summernote.blur': function summernoteDisableSummernoteBlur() { _this.hide(); } }; } TablePopover_createClass(TablePopover, [{ key: "shouldInitialize", value: function shouldInitialize() { return !lists.isEmpty(this.options.popover.table); } }, { key: "initialize", value: function initialize() { this.$popover = this.ui.popover({ className: 'note-table-popover' }).render().appendTo(this.options.container); var $content = this.$popover.find('.popover-content,.note-popover-content'); this.context.invoke('buttons.build', $content, this.options.popover.table); // [workaround] Disable Firefox's default table editor if (env.isFF) { document.execCommand('enableInlineTableEditing', false, false); } this.$popover.on('mousedown', function (e) { e.preventDefault(); }); } }, { key: "destroy", value: function destroy() { this.$popover.remove(); } }, { key: "update", value: function update(target) { if (this.context.isDisabled()) { return false; } var isCell = dom.isCell(target); if (isCell) { var pos = dom.posFromPlaceholder(target); var containerOffset = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.options.container).offset(); pos.top -= containerOffset.top; pos.left -= containerOffset.left; this.$popover.css({ display: 'block', left: pos.left, top: pos.top }); } else { this.hide(); } return isCell; } }, { key: "hide", value: function hide() { this.$popover.hide(); } }]); return TablePopover; }(); // CONCATENATED MODULE: ./src/js/base/module/VideoDialog.js function VideoDialog_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function VideoDialog_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function VideoDialog_createClass(Constructor, protoProps, staticProps) { if (protoProps) VideoDialog_defineProperties(Constructor.prototype, protoProps); if (staticProps) VideoDialog_defineProperties(Constructor, staticProps); return Constructor; } var VideoDialog_VideoDialog = /*#__PURE__*/function () { function VideoDialog(context) { VideoDialog_classCallCheck(this, VideoDialog); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.$body = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document.body); this.$editor = context.layoutInfo.editor; this.options = context.options; this.lang = this.options.langInfo; } VideoDialog_createClass(VideoDialog, [{ key: "initialize", value: function initialize() { var $container = this.options.dialogsInBody ? this.$body : this.options.container; var body = ['
                    ', ""), ""), '
                    '].join(''); var buttonClass = 'btn btn-primary note-btn note-btn-primary note-video-btn'; var footer = ""); this.$dialog = this.ui.dialog({ title: this.lang.video.insert, fade: this.options.dialogsFade, body: body, footer: footer }).render().appendTo($container); } }, { key: "destroy", value: function destroy() { this.ui.hideDialog(this.$dialog); this.$dialog.remove(); } }, { key: "bindEnterKey", value: function bindEnterKey($input, $btn) { $input.on('keypress', function (event) { if (event.keyCode === core_key.code.ENTER) { event.preventDefault(); $btn.trigger('click'); } }); } }, { key: "createVideoNode", value: function createVideoNode(url) { // video url patterns(youtube, instagram, vimeo, dailymotion, youku, mp4, ogg, webm) var ytRegExp = /\/\/(?:(?:www|m)\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([\w|-]{11})(?:(?:[\?&]t=)(\S+))?$/; var ytRegExpForStart = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/; var ytMatch = url.match(ytRegExp); var igRegExp = /(?:www\.|\/\/)instagram\.com\/p\/(.[a-zA-Z0-9_-]*)/; var igMatch = url.match(igRegExp); var vRegExp = /\/\/vine\.co\/v\/([a-zA-Z0-9]+)/; var vMatch = url.match(vRegExp); var vimRegExp = /\/\/(player\.)?vimeo\.com\/([a-z]*\/)*(\d+)[?]?.*/; var vimMatch = url.match(vimRegExp); var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/; var dmMatch = url.match(dmRegExp); var youkuRegExp = /\/\/v\.youku\.com\/v_show\/id_(\w+)=*\.html/; var youkuMatch = url.match(youkuRegExp); var qqRegExp = /\/\/v\.qq\.com.*?vid=(.+)/; var qqMatch = url.match(qqRegExp); var qqRegExp2 = /\/\/v\.qq\.com\/x?\/?(page|cover).*?\/([^\/]+)\.html\??.*/; var qqMatch2 = url.match(qqRegExp2); var mp4RegExp = /^.+.(mp4|m4v)$/; var mp4Match = url.match(mp4RegExp); var oggRegExp = /^.+.(ogg|ogv)$/; var oggMatch = url.match(oggRegExp); var webmRegExp = /^.+.(webm)$/; var webmMatch = url.match(webmRegExp); var fbRegExp = /(?:www\.|\/\/)facebook\.com\/([^\/]+)\/videos\/([0-9]+)/; var fbMatch = url.match(fbRegExp); var $video; if (ytMatch && ytMatch[1].length === 11) { var youtubeId = ytMatch[1]; var start = 0; if (typeof ytMatch[2] !== 'undefined') { var ytMatchForStart = ytMatch[2].match(ytRegExpForStart); if (ytMatchForStart) { for (var n = [3600, 60, 1], i = 0, r = n.length; i < r; i++) { start += typeof ytMatchForStart[i + 1] !== 'undefined' ? n[i] * parseInt(ytMatchForStart[i + 1], 10) : 0; } } } $video = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(''; if (isScrollToTop) { $('.mainContent').find('iframe.RuoYi_iframe').hide(); } else { $('.mainContent').find('iframe.RuoYi_iframe').css({"visibility": "hidden", "position": "absolute", "left": "0", "top": "0"}); } $('.mainContent').append(str1); $.modal.loading("数据加载中,请稍候..."); $('.mainContent iframe:visible').on('load', function() { $.modal.closeLoading(); }); // 添加选项卡 $('.menuTabs .page-tabs-content').append(str); scrollToTab($('.menuTab.active')); } return false; } function menuBlank() { // 新窗口打开外网以http://开头,如http://ruoyi.vip var dataUrl = $(this).attr('href'); window.open(dataUrl); return false; } $('.menuItem').on('click', menuItem); $('.menuBlank').on('click', menuBlank); // 关闭选项卡菜单 function closeTab() { var closeTabId = $(this).parents('.menuTab').data('id'); var currentWidth = $(this).parents('.menuTab').width(); var panelUrl = $(this).parents('.menuTab').data('panel'); // 当前元素处于活动状态 if ($(this).parents('.menuTab').hasClass('active')) { // 当前元素后面有同辈元素,使后面的一个元素处于活动状态 if ($(this).parents('.menuTab').next('.menuTab').length) { var activeId = $(this).parents('.menuTab').next('.menuTab:eq(0)').data('id'); $(this).parents('.menuTab').next('.menuTab:eq(0)').addClass('active'); $('.mainContent .RuoYi_iframe').each(function() { if ($(this).data('id') == activeId) { openToCurrentTab(this); return false; } }); var marginLeftVal = parseInt($('.page-tabs-content').css('margin-left')); if (marginLeftVal < 0) { $('.page-tabs-content').animate({ marginLeft: (marginLeftVal + currentWidth) + 'px' }, "fast"); } // 移除当前选项卡 $(this).parents('.menuTab').remove(); // 移除tab对应的内容区 $('.mainContent .RuoYi_iframe').each(function() { if ($(this).data('id') == closeTabId) { $(this).remove(); return false; } }); } // 当前元素后面没有同辈元素,使当前元素的上一个元素处于活动状态 if ($(this).parents('.menuTab').prev('.menuTab').length) { var activeId = $(this).parents('.menuTab').prev('.menuTab:last').data('id'); $(this).parents('.menuTab').prev('.menuTab:last').addClass('active'); $('.mainContent .RuoYi_iframe').each(function() { if ($(this).data('id') == activeId) { openToCurrentTab(this); return false; } }); // 移除当前选项卡 $(this).parents('.menuTab').remove(); // 移除tab对应的内容区 $('.mainContent .RuoYi_iframe').each(function() { if ($(this).data('id') == closeTabId) { $(this).remove(); return false; } }); if ($.common.isNotEmpty(panelUrl)) { $('.menuTab[data-id="' + panelUrl + '"]').addClass('active').siblings('.menuTab').removeClass('active'); $('.mainContent .RuoYi_iframe').each(function() { if ($(this).data('id') == panelUrl) { openToCurrentTab(this); return false; } }); } } } // 当前元素不处于活动状态 else { // 移除当前选项卡 $(this).parents('.menuTab').remove(); // 移除相应tab对应的内容区 $('.mainContent .RuoYi_iframe').each(function() { if ($(this).data('id') == closeTabId) { $(this).remove(); return false; } }); } scrollToTab($('.menuTab.active')); syncMenuTab($.common.isNotEmpty(panelUrl) ? panelUrl : $('.page-tabs-content').find('.active').attr('data-id')); return false; } $('.menuTabs').on('click', '.menuTab i', closeTab); //滚动到已激活的选项卡 function showActiveTab() { scrollToTab($('.menuTab.active')); } $('.tabShowActive').on('click', showActiveTab); // 点击选项卡菜单 function activeTab() { if (!$(this).hasClass('active')) { var currentId = $(this).data('id'); var isRefresh = false; syncMenuTab(currentId); // 显示tab对应的内容区 $('.mainContent .RuoYi_iframe').each(function() { if ($(this).data('id') == currentId) { openToCurrentTab(this); isRefresh = $.common.nullToDefault($(this).data('refresh'), false); return false; } }); $(this).addClass('active').siblings('.menuTab').removeClass('active'); if (isRefresh) { refreshTab(); } scrollToTab(this); } } // 点击选项卡菜单 $('.menuTabs').on('click', '.menuTab', activeTab); // 刷新iframe function refreshTab() { var currentId = $('.page-tabs-content').find('.active').attr('data-id'); var target = $('.RuoYi_iframe[data-id="' + currentId + '"]'); var url = target.attr('src'); target.attr('src', url).ready(); } // 页签全屏 function fullScreenTab() { var currentId = $('.page-tabs-content').find('.active').attr('data-id'); var target = $('.RuoYi_iframe[data-id="' + currentId + '"]'); target.fullScreen(true); } // 关闭当前选项卡 function tabCloseCurrent() { $('.page-tabs-content').find('.active i').trigger("click"); } //关闭其他选项卡 function tabCloseOther() { $('.page-tabs-content').children("[data-id]").not(":first").not(".active").each(function() { $('.RuoYi_iframe[data-id="' + $(this).data('id') + '"]').remove(); $(this).remove(); }); $('.page-tabs-content').animate({ marginLeft: '0px' }, "fast"); } // 关闭全部选项卡 function tabCloseAll() { $('.page-tabs-content').children("[data-id]").not(":first").each(function() { $('.RuoYi_iframe[data-id="' + $(this).data('id') + '"]').remove(); $(this).remove(); }); $('.page-tabs-content').children("[data-id]:first").each(function() { if (isScrollToTop) { $('.RuoYi_iframe[data-id="' + $(this).data('id') + '"]').show(); } else { $('.RuoYi_iframe[data-id="' + $(this).data('id') + '"]').css({"visibility": "visible", "position": "static"}); } $(this).addClass("active"); }); $('.page-tabs-content').css("margin-left", "0"); syncMenuTab($('.page-tabs-content').find('.active').attr('data-id')); } // 全屏显示 $('#fullScreen').on('click', function () { $(document).toggleFullScreen(); }); // 锁定屏幕 $('#lockScreen').on('click', function () { storage.set('lockPath', $('.page-tabs-content').find('.active').attr('data-id')); location.href = ctx + "lockscreen"; }); // 页签刷新按钮 $('.tabReload').on('click', refreshTab); // 页签全屏按钮 $('.tabFullScreen').on('click', fullScreenTab); // 双击选项卡全屏显示 $('.menuTabs').on('dblclick', '.menuTab', activeTabMax); // 左移按扭 $('.tabLeft').on('click', scrollTabLeft); // 右移按扭 $('.tabRight').on('click', scrollTabRight); // 关闭当前 $('.tabCloseCurrent').on('click', tabCloseCurrent); // 关闭其他 $('.tabCloseOther').on('click', tabCloseOther); // 关闭全部 $('.tabCloseAll').on('click', tabCloseAll); // tab全屏显示 $('.tabMaxCurrent').on('click', function () { $('.page-tabs-content').find('.active').trigger("dblclick"); }); // 关闭全屏 $('#ax_close_max').click(function(){ $('#content-main').toggleClass('max'); $('#ax_close_max').hide(); }) // 双击选项卡全屏显示 function activeTabMax() { $('#content-main').toggleClass('max'); $('#ax_close_max').show(); } $(window).keydown(function(event) { if (event.keyCode == 27) { $('#content-main').removeClass('max'); $('#ax_close_max').hide(); } }); window.onhashchange = function() { var hash = location.hash; var url = hash.substring(1, hash.length); $('a[href$="' + url + '"]').click(); }; // 右键菜单实现 $.contextMenu({ selector: ".menuTab", trigger: 'right', autoHide: true, items: { "close_current": { name: "关闭当前", icon: "fa-close", callback: function(key, opt) { opt.$trigger.find('i').trigger("click"); } }, "close_other": { name: "关闭其他", icon: "fa-window-close-o", callback: function(key, opt) { setActiveTab(this); tabCloseOther(); } }, "close_left": { name: "关闭左侧", icon: "fa-reply", callback: function(key, opt) { setActiveTab(this); this.prevAll('.menuTab').not(":last").each(function() { if ($(this).hasClass('active')) { setActiveTab(this); } $('.RuoYi_iframe[data-id="' + $(this).data('id') + '"]').remove(); $(this).remove(); }); $('.page-tabs-content').animate({ marginLeft: '0px' }, "fast"); } }, "close_right": { name: "关闭右侧", icon: "fa-share", callback: function(key, opt) { setActiveTab(this); this.nextAll('.menuTab').each(function() { $('.RuoYi_iframe[data-id="' + $(this).data('id') + '"]').remove(); $(this).remove(); }); } }, "close_all": { name: "全部关闭", icon: "fa-window-close", callback: function(key, opt) { tabCloseAll(); } }, "step": "---------", "full": { name: "全屏显示", icon: "fa-arrows-alt", callback: function(key, opt) { setActiveTab(this); var target = $('.RuoYi_iframe[data-id="' + this.data('id') + '"]'); target.fullScreen(true); } }, "refresh": { name: "刷新页面", icon: "fa-refresh", callback: function(key, opt) { setActiveTab(this); var target = $('.RuoYi_iframe[data-id="' + this.data('id') + '"]'); var url = target.attr('src'); $.modal.loading("数据加载中,请稍候..."); target.attr('src', url).on('load', function() { $.modal.closeLoading(); }); } }, "open": { name: "新窗口打开", icon: "fa-link", callback: function(key, opt) { var target = $('.RuoYi_iframe[data-id="' + this.data('id') + '"]'); window.open(target.attr('src')); } }, } }); }); ================================================ FILE: ruoyi-admin/src/main/resources/static/ruoyi/js/common.js ================================================ /** * 通用方法封装处理 * Copyright (c) 2019 ruoyi */ var startLayDate; var endLayDate; var isScrollToTop = parent.isScrollToTop; $(function() { // 回到顶部绑定 if ($.fn.toTop !== undefined) { $('#scroll-up').toTop(); } // select2复选框事件绑定 if ($.fn.select2 !== undefined) { $.fn.select2.defaults.set( "theme", "bootstrap" ); $("select.form-control:not(.noselect2)").each(function () { $(this).select2().on("change", function () { $(this).valid(); }) }) } // iCheck单选框及复选框事件绑定 if ($.fn.iCheck !== undefined) { $(".check-box:not(.noicheck),.radio-box:not(.noicheck)").each(function() { $(this).iCheck({ checkboxClass: 'icheckbox-blue', radioClass: 'iradio-blue', }) }) } // 取消回车自动提交表单 $(document).on("keypress", ":input:not(textarea):not([type=submit])", function(event) { if (event.keyCode == 13) { event.preventDefault(); } }); // laydate 时间控件绑定 if ($(".select-time").length > 0 && $('#startTime').length > 0 && $('#endTime').length > 0) { layui.use('laydate', function() { var laydate = layui.laydate; startLayDate = laydate.render({ elem: '#startTime', max: $('#endTime').val(), theme: 'molv', type: $('#startTime').attr("data-type") || 'date', trigger: 'click', done: function(value, date) { // 结束时间大于开始时间 if (value !== '') { endLayDate.config.min.year = date.year; endLayDate.config.min.month = date.month - 1; endLayDate.config.min.date = date.date; } else { endLayDate.config.min.year = ''; endLayDate.config.min.month = ''; endLayDate.config.min.date = ''; } $('#endTime').trigger('click'); } }); endLayDate = laydate.render({ elem: '#endTime', min: $('#startTime').val(), theme: 'molv', type: $('#endTime').attr("data-type") || 'date', trigger: 'click', done: function(value, date) { // 开始时间小于结束时间 if (value !== '') { startLayDate.config.max.year = date.year; startLayDate.config.max.month = date.month - 1; startLayDate.config.max.date = date.date; } else { startLayDate.config.max.year = '2099'; startLayDate.config.max.month = '12'; startLayDate.config.max.date = '31'; } } }); }); } // laydate time-input 时间控件绑定 if ($(".time-input").length > 0) { layui.use('laydate', function () { var com = layui.laydate; $(".time-input").each(function (index, item) { var time = $(item); // 控制控件外观 var type = time.attr("data-type") || 'date'; // 控制回显格式 var format = time.attr("data-format") || 'yyyy-MM-dd'; // 控制日期控件按钮 var buttons = time.attr("data-btn") || 'clear|now|confirm', newBtnArr = []; // 日期控件选择完成后回调处理 var callback = time.attr("data-callback") || {}; if (buttons) { if (buttons.indexOf("|") > 0) { var btnArr = buttons.split("|"), btnLen = btnArr.length; for (var j = 0; j < btnLen; j++) { if ("clear" === btnArr[j] || "now" === btnArr[j] || "confirm" === btnArr[j]) { newBtnArr.push(btnArr[j]); } } } else { if ("clear" === buttons || "now" === buttons || "confirm" === buttons) { newBtnArr.push(buttons); } } } else { newBtnArr = ['clear', 'now', 'confirm']; } com.render({ elem: item, theme: 'molv', trigger: 'click', type: type, format: format, btns: newBtnArr, done: function (value, data) { if (typeof window[callback] != 'undefined' && window[callback] instanceof Function) { window[callback](value, data); } } }); }); }); } // tree 关键字搜索绑定 if ($("#keyword").length > 0) { $("#keyword").bind("focus", function focusKey(e) { if ($("#keyword").hasClass("empty")) { $("#keyword").removeClass("empty"); } }).bind("blur", function blurKey(e) { if ($("#keyword").val() === "") { $("#keyword").addClass("empty"); } $.tree.searchNode(e); }).bind("input propertychange", $.tree.searchNode); } // tree表格树 展开/折叠 var expandFlag; $("#expandAllBtn").click(function() { var dataExpand = $.common.isEmpty(table.options.expandAll) ? true : table.options.expandAll; expandFlag = $.common.isEmpty(expandFlag) ? dataExpand : expandFlag; if (!expandFlag) { $.bttTable.bootstrapTreeTable('expandAll'); } else { $.bttTable.bootstrapTreeTable('collapseAll'); } expandFlag = expandFlag ? false: true; }) // 按下ESC按钮关闭弹层 $('body', document).on('keyup', function(e) { if (e.which === 27) { $.modal.closeAll(); } }); }); (function ($) { 'use strict'; $.fn.toTop = function(opt) { var elem = this; var win = (opt && opt.hasOwnProperty('win')) ? opt.win : $(window); var doc = (opt && opt.hasOwnProperty('doc')) ? opt.doc : $('html, body'); var options = $.extend({ autohide: true, offset: 50, speed: 500, position: true, right: 15, bottom: 5 }, opt); elem.css({ 'cursor': 'pointer' }); if (options.autohide) { elem.css('display', 'none'); } if (options.position) { elem.css({ 'position': 'fixed', 'right': options.right, 'bottom': options.bottom, }); } elem.click(function() { doc.animate({ scrollTop: 0 }, options.speed); }); win.scroll(function() { var scrolling = win.scrollTop(); if (options.autohide) { if (scrolling > options.offset) { elem.fadeIn(options.speed); } else elem.fadeOut(options.speed); } }); }; })(jQuery); /** 刷新选项卡 */ var refreshItem = function(){ var topWindow = $(window.parent.document); var currentId = $('.page-tabs-content', topWindow).find('.active').attr('data-id'); var target = $('.RuoYi_iframe[data-id="' + currentId + '"]', topWindow); var url = target.attr('src'); target.attr('src', url).ready(); } /** 关闭选项卡 */ var closeItem = function(dataId){ var topWindow = $(window.parent.document); if ($.common.isNotEmpty(dataId)) { window.parent.$.modal.closeLoading(); // 根据dataId关闭指定选项卡 $('.menuTab[data-id="' + dataId + '"]', topWindow).remove(); // 移除相应tab对应的内容区 $('.mainContent .RuoYi_iframe[data-id="' + dataId + '"]', topWindow).remove(); return; } var panelUrl = window.frameElement.getAttribute('data-panel'); $('.page-tabs-content .active i', topWindow).click(); if ($.common.isNotEmpty(panelUrl)) { $('.menuTab[data-id="' + panelUrl + '"]', topWindow).addClass('active').siblings('.menuTab').removeClass('active'); $('.mainContent .RuoYi_iframe', topWindow).each(function() { if ($(this).data('id') == panelUrl) { openToCurrentTab(this); return false; } }); } } /** 创建选项卡 */ function createMenuItem(dataUrl, menuName, isRefresh) { var panelUrl = window.frameElement.getAttribute('data-id'), dataIndex = $.common.random(1, 100), flag = true; if (dataUrl == undefined || $.trim(dataUrl).length == 0) return false; var topWindow = $(window.parent.document); // 选项卡菜单已存在 $('.menuTab', topWindow).each(function() { if ($(this).data('id') == dataUrl) { if (!$(this).hasClass('active')) { $(this).addClass('active').siblings('.menuTab').removeClass('active'); scrollToTab(this); $('.page-tabs-content').animate({ marginLeft: ""}, "fast"); // 显示tab对应的内容区 $('.mainContent .RuoYi_iframe', topWindow).each(function() { if ($(this).data('id') == dataUrl) { openToCurrentTab(this); return false; } }); } if (isRefresh) { refreshTab(); } flag = false; return false; } }); // 选项卡菜单不存在 if (flag) { var str = '' + menuName + ' '; $('.menuTab', topWindow).removeClass('active'); // 添加选项卡对应的iframe var str1 = ''; if (isScrollToTop) { $('.mainContent', topWindow).find('iframe.RuoYi_iframe').hide(); } else { $('.mainContent', topWindow).find('iframe.RuoYi_iframe').css({"visibility": "hidden", "position": "absolute", "left": "0", "top": "0"}); } $('.mainContent', topWindow).append(str1); window.parent.$.modal.loading("数据加载中,请稍候..."); $('.mainContent iframe:visible', topWindow).on('load', function() { window.parent.$.modal.closeLoading(); }); // 添加选项卡 $('.menuTabs .page-tabs-content', topWindow).append(str); scrollToTab($('.menuTab.active', topWindow)); } return false; } // 刷新iframe function refreshTab() { var topWindow = $(window.parent.document); var currentId = $('.page-tabs-content', topWindow).find('.active').attr('data-id'); var target = $('.RuoYi_iframe[data-id="' + currentId + '"]', topWindow); var url = target.attr('src'); target.attr('src', url).ready(); } // 滚动到指定选项卡 function scrollToTab(element) { var topWindow = $(window.parent.document); var marginLeftVal = calSumWidth($(element).prevAll()), marginRightVal = calSumWidth($(element).nextAll()); // 可视区域非tab宽度 var tabOuterWidth = calSumWidth($(".content-tabs", topWindow).children().not(".menuTabs")); //可视区域tab宽度 var visibleWidth = $(".content-tabs", topWindow).outerWidth(true) - tabOuterWidth; //实际滚动宽度 var scrollVal = 0; if ($(".page-tabs-content", topWindow).outerWidth() < visibleWidth) { scrollVal = 0; } else if (marginRightVal <= (visibleWidth - $(element).outerWidth(true) - $(element).next().outerWidth(true))) { if ((visibleWidth - $(element).next().outerWidth(true)) > marginRightVal) { scrollVal = marginLeftVal; var tabElement = element; while ((scrollVal - $(tabElement).outerWidth()) > ($(".page-tabs-content", topWindow).outerWidth() - visibleWidth)) { scrollVal -= $(tabElement).prev().outerWidth(); tabElement = $(tabElement).prev(); } } } else if (marginLeftVal > (visibleWidth - $(element).outerWidth(true) - $(element).prev().outerWidth(true))) { scrollVal = marginLeftVal - $(element).prev().outerWidth(true); } $('.page-tabs-content', topWindow).animate({ marginLeft: 0 - scrollVal + 'px' }, "fast"); } // 计算元素集合的总宽度 function calSumWidth(elements) { var width = 0; $(elements).each(function() { width += $(this).outerWidth(true); }); return width; } // 返回当前激活的Tab页面关联的iframe的Windows对象 function activeWindow() { var topWindow = $(window.parent.document); var currentId = $('.page-tabs-content', topWindow).find('.active').attr('data-id'); if (!currentId) { return window.parent; } return $('.RuoYi_iframe[data-id="' + currentId + '"]', topWindow)[0].contentWindow; } function openToCurrentTab(obj) { if (isScrollToTop) { $(obj).show().siblings('.RuoYi_iframe').hide(); } else { $(obj).css({"visibility": "visible", "position": "static"}).siblings('.RuoYi_iframe').css({"visibility": "hidden", "position": "absolute", "left": "0", "top": "0"}); } } /** 密码规则范围验证 */ function checkpwd(chrtype, password) { if (chrtype == 1) { if (!$.common.numValid(password)) { $.modal.alertWarning("密码只能为0-9数字"); return false; } } else if (chrtype == 2) { if (!$.common.enValid(password)) { $.modal.alertWarning("密码只能为a-z和A-Z字母"); return false; } } else if (chrtype == 3) { if (!$.common.enNumValid(password)) { $.modal.alertWarning("密码必须包含字母以及数字"); return false; } } else if (chrtype == 4) { if (!$.common.charValid(password)) { $.modal.alertWarning("密码必须包含字母、数字、以及特殊符号~!@#$%^&*()-=_+"); return false; } } return true; } /** 开始时间/时分秒 */ function beginOfTime(date) { if ($.common.isNotEmpty(date)) { return $.common.sprintf("%s 00:00:00", date); } } /** 结束时间/时分秒 */ function endOfTime(date) { if ($.common.isNotEmpty(date)) { return $.common.sprintf("%s 23:59:59", date); } } /** 重置日期/年月日 */ function resetDate() { if ($.common.isNotEmpty(startLayDate) && $.common.isNotEmpty(endLayDate)) { endLayDate.config.min.year = ''; endLayDate.config.min.month = ''; endLayDate.config.min.date = ''; startLayDate.config.max.year = '2099'; startLayDate.config.max.month = '12'; startLayDate.config.max.date = '31'; } } // 日志打印封装处理 var log = { log: function(msg) { console.log(msg); }, info: function(msg) { console.info(msg); }, warn: function(msg) { console.warn(msg); }, error: function(msg) { console.error(msg); } }; // 本地缓存处理 var storage = { set: function(key, value) { window.localStorage.setItem(key, value); }, get: function(key) { return window.localStorage.getItem(key); }, remove: function(key) { window.localStorage.removeItem(key); }, clear: function() { window.localStorage.clear(); } }; // 主子表操作封装处理 var sub = { editRow: function() { var dataColumns = []; for (var columnIndex = 0; columnIndex < table.options.columns.length; columnIndex++) { if (table.options.columns[columnIndex].visible != false) { dataColumns.push(table.options.columns[columnIndex]); } } var params = new Array(); var data = $("#" + table.options.id).bootstrapTable('getData'); var count = data.length; for (var dataIndex = 0; dataIndex < count; dataIndex++) { var columns = $('#' + table.options.id + ' tr[data-index="' + dataIndex + '"] td:visible'); var obj = new Object(); for (var i = 0; i < columns.length; i++) { var inputValue = $(columns[i]).find('input'); var selectValue = $(columns[i]).find('select'); var textareaValue = $(columns[i]).find('textarea'); var key = dataColumns[i].field; if ($.common.isNotEmpty(inputValue.val())) { obj[key] = inputValue.val(); } else if ($.common.isNotEmpty(selectValue.val())) { obj[key] = selectValue.val(); } else if ($.common.isNotEmpty(textareaValue.val())) { obj[key] = textareaValue.val(); } else { if (key == "index" && $.common.isNotEmpty(data[dataIndex].index)) { obj[key] = data[dataIndex].index; } else { obj[key] = ""; } } } var item = data[dataIndex]; var extendObj = $.extend({}, item, obj); params.push({ index: dataIndex, row: extendObj }); } $("#" + table.options.id).bootstrapTable("updateRow", params); }, delRow: function(column) { sub.editRow(); var subColumn = $.common.isEmpty(column) ? "index" : column; var ids = $.table.selectColumns(subColumn); if (ids.length == 0) { $.modal.alertWarning("请至少选择一条记录"); return; } $("#" + table.options.id).bootstrapTable('remove', { field: subColumn, values: ids }); }, delRowByIndex: function(value, tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; sub.editRow(); $("#" + currentId).bootstrapTable('remove', { field: "index", values: [value] }); sub.editRow(); }, addRow: function(row, tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; table.set(currentId); var count = $("#" + currentId).bootstrapTable('getData').length; sub.editRow(); $("#" + currentId).bootstrapTable('insertRow', { index: count + 1, row: row }); } }; // 动态加载css文件 function loadCss(file, headElem) { var link = document.createElement('link'); link.href = file; link.rel = 'stylesheet'; link.type = 'text/css'; if (headElem) headElem.appendChild(link); else document.getElementsByTagName('head')[0].appendChild(link); } // 动态加载js文件 function loadJs(file, headElem) { var script = document.createElement('script'); script.src = file; script.type = 'text/javascript'; if (headElem) headElem.appendChild(script); else document.getElementsByTagName('head')[0].appendChild(script); } // 禁止后退键(Backspace) window.onload = function() { document.getElementsByTagName("body")[0].onkeydown = function() { // 获取事件对象 var elem = event.relatedTarget || event.srcElement || event.target || event.currentTarget; // 判断按键为backSpace键 if (event.keyCode == 8) { // 判断是否需要阻止按下键盘的事件默认传递 var name = elem.nodeName; var className = elem.className; // 屏蔽特定的样式名称 if (className.indexOf('note-editable') != -1) { return true; } if (name != 'INPUT' && name != 'TEXTAREA') { return _stopIt(event); } var type_e = elem.type.toUpperCase(); if (name == 'INPUT' && (type_e != 'TEXT' && type_e != 'TEXTAREA' && type_e != 'PASSWORD' && type_e != 'FILE' && type_e != 'SEARCH' && type_e != 'NUMBER' && type_e != 'EMAIL' && type_e != 'URL')) { return _stopIt(event); } if (name == 'INPUT' && (elem.readOnly == true || elem.disabled == true)) { return _stopIt(event); } } }; }; function _stopIt(e) { if (e.returnValue) { e.returnValue = false; } if (e.preventDefault) { e.preventDefault(); } return false; } /** 设置全局ajax处理 */ $.ajaxSetup({ beforeSend: function (xhr, settings) { var csrftoken = $('meta[name=csrf-token]').attr('content') if (($.common.equalsIgnoreCase(settings.type, "POST"))) { xhr.setRequestHeader("X-CSRF-Token", csrftoken) } }, complete: function(XMLHttpRequest, textStatus) { if (textStatus == 'timeout') { $.modal.alertWarning("服务器超时,请稍后再试!"); $.modal.enable(); $.modal.closeLoading(); } else if (textStatus == "parsererror" || textStatus == "error") { $.modal.alertWarning("服务器错误,请联系管理员!"); $.modal.enable(); $.modal.closeLoading(); } } }); ================================================ FILE: ruoyi-admin/src/main/resources/static/ruoyi/js/ry-ui.js ================================================ /** * 通用js方法封装处理 * Copyright (c) 2019 ruoyi */ // 当前table相关信息 var table = { config: {}, // 当前实例配置 options: {}, // 设置实例配置 set: function(id) { if ($.common.getLength(table.config) > 1 && $.common.isNotEmpty(event)) { var tableId = $.common.isEmpty(id) ? $(event.currentTarget).parents(".bootstrap-table").find("table.table").attr("id") || $(event.currentTarget).parents(".bootstrap-tree-table").find("table.table").attr("id") : id; if ($.common.isNotEmpty(tableId)) { table.options = table.get(tableId); } } }, // 获取实例配置 get: function(id) { return table.config[id]; }, // 记住选择实例组 rememberSelecteds: {}, // 记住选择ID组 rememberSelectedIds: {} }; (function ($) { $.extend({ _tree: {}, bttTable: {}, // 表格封装处理 table: { // 初始化表格参数 init: function(options) { var defaults = { id: "bootstrap-table", type: 0, // 0 代表bootstrapTable 1代表bootstrapTreeTable method: 'post', height: undefined, sidePagination: "server", undefinedText: '-', sortName: undefined, sortOrder: "asc", pagination: true, paginationLoop: false, pageSize: 10, pageNumber: 1, pageList: [10, 25, 50, 100], toolbar: "toolbar", loadingFontSize: 13, striped: false, escape: true, firstLoad: true, showFooter: false, search: false, showSearch: true, showPageGo: false, showRefresh: true, showColumns: true, showToggle: true, showExport: false, showPrint: false, exportDataType: 'all', exportTypes: ['csv', 'txt', 'doc', 'excel'], clickToSelect: false, singleSelect: false, mobileResponsive: true, maintainSelected: false, rememberSelected: false, fixedColumns: false, fixedNumber: 0, fixedRightNumber: 0, queryParams: $.table.queryParams, rowStyle: undefined }; var options = $.extend(defaults, options); table.options = options; table.config[options.id] = options; $.table.initEvent(); $('#' + options.id).bootstrapTable({ id: options.id, url: options.url, // 请求后台的URL(*) contentType: "application/x-www-form-urlencoded", // 编码类型 method: options.method, // 请求方式(*) cache: false, // 是否使用缓存 height: options.height, // 表格的高度 striped: options.striped, // 是否显示行间隔色 undefinedText: options.undefinedText, // 数据值为空时显示的内容 sortable: true, // 是否启用排序 sortStable: true, // 设置为 true 将获得稳定的排序 sortName: options.sortName, // 排序列名称 sortOrder: options.sortOrder, // 排序方式 asc 或者 desc pagination: options.pagination, // 是否显示分页(*) paginationLoop: options.paginationLoop, // 是否启用分页条无限循环的功能 pageNumber: 1, // 初始化加载第一页,默认第一页 pageSize: options.pageSize, // 每页的记录行数(*) pageList: options.pageList, // 可供选择的每页的行数(*) firstLoad: options.firstLoad, // 是否首次请求加载数据,对于数据较大可以配置false escape: options.escape, // 转义HTML字符串 showFooter: options.showFooter, // 是否显示表尾 iconSize: 'outline', // 图标大小:undefined默认的按钮尺寸 xs超小按钮sm小按钮lg大按钮 toolbar: '#' + options.toolbar, // 指定工作栏 virtualScroll: options.virtualScroll, // 是否启动虚拟滚动(大量数据纯展示时使用) loadingFontSize: options.loadingFontSize, // 自定义加载文本的字体大小 sidePagination: options.sidePagination, // server启用服务端分页client客户端分页 search: options.search, // 是否显示搜索框功能 searchText: options.searchText, // 搜索框初始显示的内容,默认为空 showSearch: options.showSearch, // 是否显示检索信息 showPageGo: options.showPageGo, // 是否显示跳转页 showRefresh: options.showRefresh, // 是否显示刷新按钮 showColumns: options.showColumns, // 是否显示隐藏某列下拉框 showToggle: options.showToggle, // 是否显示详细视图和列表视图的切换按钮 showExport: options.showExport, // 是否支持导出文件 showPrint: options.showPrint, // 是否支持打印页面 showHeader: options.showHeader, // 是否显示表头 showFullscreen: options.showFullscreen, // 是否显示全屏按钮 uniqueId: options.uniqueId, // 唯一的标识符 clickToSelect: options.clickToSelect, // 是否启用点击选中行 singleSelect: options.singleSelect, // 是否单选checkbox mobileResponsive: options.mobileResponsive, // 是否支持移动端适配 cardView: options.cardView, // 是否启用显示卡片视图 detailView: options.detailView, // 是否启用显示细节视图 onCheck: options.onCheck, // 当选择此行时触发 onUncheck: options.onUncheck, // 当取消此行时触发 onCheckAll: options.onCheckAll, // 当全选行时触发 onUncheckAll: options.onUncheckAll, // 当取消全选行时触发 onClickRow: options.onClickRow, // 点击某行触发的事件 onDblClickRow: options.onDblClickRow, // 双击某行触发的事件 onClickCell: options.onClickCell, // 单击某格触发的事件 onDblClickCell: options.onDblClickCell, // 双击某格触发的事件 onEditableSave: options.onEditableSave, // 行内编辑保存的事件 onExpandRow: options.onExpandRow, // 点击详细视图的事件 onPostBody: options.onPostBody, // 渲染完成后执行的事件 maintainSelected: options.maintainSelected, // 前端翻页时保留所选行 rememberSelected: options.rememberSelected, // 启用翻页记住前面的选择 fixedColumns: options.fixedColumns, // 是否启用冻结列(左侧) fixedNumber: options.fixedNumber, // 列冻结的个数(左侧) fixedRightNumber: options.fixedRightNumber, // 列冻结的个数(右侧) onReorderRow: options.onReorderRow, // 当拖拽结束后处理函数 queryParams: options.queryParams, // 传递参数(*) rowStyle: options.rowStyle, // 通过自定义函数设置行样式 footerStyle: options.footerStyle, // 通过自定义函数设置页脚样式 headerStyle: options.headerStyle, // 通过自定义函数设置标题样式 selectItemName: options.selectItemName, // 自定义radio/checkbox的name值 columns: options.columns, // 显示列信息(*) data: options.data, // 被加载的数据 responseHandler: $.table.responseHandler, // 在加载服务器发送来的数据之前处理函数 onLoadSuccess: $.table.onLoadSuccess, // 当所有数据被加载时触发处理函数 exportOptions: options.exportOptions, // 前端导出忽略列索引 exportDataType: options.exportDataType, // 导出方式(默认all:导出所有数据;basic:导出当前页的数据;selected:导出选中的数据) exportTypes: options.exportTypes, // 导出文件类型 (json、xml、png、csv、txt、sql、doc、excel、xlsx、powerpoint、pdf) printPageBuilder: options.printPageBuilder, // 自定义打印页面模板 detailFormatter: options.detailFormatter, // 在行下面展示其他数据列表 }); }, // 获取实例ID,如存在多个返回#id1,#id2 delimeter分隔符 getOptionsIds: function(separator) { var _separator = $.common.isEmpty(separator) ? "," : separator; var optionsIds = ""; $.each(table.config, function(key, value){ optionsIds += "#" + key + _separator; }); return optionsIds.substring(0, optionsIds.length - 1); }, // 查询条件 queryParams: function(params) { table.set(); var curParams = { // 传递参数查询参数 pageSize: params.limit, pageNum: params.offset / params.limit + 1, searchValue: params.search, orderByColumn: params.sort, isAsc: params.order }; var currentId = $.common.isEmpty(table.options.formId) ? $('form').attr('id') : table.options.formId; return $.extend(curParams, $.common.formToJSON(currentId)); }, // 请求获取数据后处理回调函数 responseHandler: function(res) { if (typeof table.get(this.id).responseHandler == "function") { table.get(this.id).responseHandler(res); } var thisOptions = table.config[this.id]; if (res.code == web_status.SUCCESS) { if ($.common.isNotEmpty(thisOptions.sidePagination) && thisOptions.sidePagination == 'client') { return res.rows; } else { if ($.common.isNotEmpty(thisOptions.rememberSelected) && thisOptions.rememberSelected) { var column = $.common.isEmpty(thisOptions.uniqueId) ? thisOptions.columns[1].field : thisOptions.uniqueId; $.each(res.rows, function(i, row) { row.state = $.inArray(row[column], table.rememberSelectedIds[thisOptions.id]) !== -1; }) } return { rows: res.rows, total: res.total }; } } else { $.modal.alertWarning(res.msg); return { rows: [], total: 0 }; } }, // 初始化事件 initEvent: function() { // 实例ID信息 var optionsIds = $.table.getOptionsIds(); // 监听事件处理 $(optionsIds).on(TABLE_EVENTS, function () { table.set($(this).attr("id")); }); // 在表格体渲染完成,并在 DOM 中可见后触发(事件) $(optionsIds).on("post-body.bs.table", function (e, args) { // 浮动提示框特效 $(".table [data-toggle='tooltip']").tooltip(); // 气泡弹出框特效 $('.table [data-toggle="popover"]').popover(); }); // 选中、取消、全部选中、全部取消(事件) $(optionsIds).on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", function (e, rowsAfter, rowsBefore) { // 复选框分页保留保存选中数组 var rows = $.common.equals("uncheck-all", e.type) ? rowsBefore : rowsAfter; var rowIds = $.table.affectedRowIds(rows); if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { func = $.inArray(e.type, ['check', 'check-all']) > -1 ? 'union' : 'difference'; var selectedIds = table.rememberSelectedIds[table.options.id]; if ($.common.isNotEmpty(selectedIds)) { table.rememberSelectedIds[table.options.id] = _[func](selectedIds, rowIds); } else { table.rememberSelectedIds[table.options.id] = _[func]([], rowIds); } var selectedRows = table.rememberSelecteds[table.options.id]; if ($.common.isNotEmpty(selectedRows)) { table.rememberSelecteds[table.options.id] = _[func](selectedRows, rows); } else { table.rememberSelecteds[table.options.id] = _[func]([], rows); } } }); // 加载成功、选中、取消、全部选中、全部取消(事件) $(optionsIds).on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table load-success.bs.table", function () { var toolbar = table.options.toolbar; var uniqueId = table.options.uniqueId; // 工具栏按钮控制 var rows = $.common.isEmpty(uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(uniqueId); // 非多个禁用 $('#' + toolbar + ' .multiple').toggleClass('disabled', !rows.length); // 非单个禁用 $('#' + toolbar + ' .single').toggleClass('disabled', rows.length!=1); }); // 图片预览事件 $(optionsIds).off("click").on("click", '.img-circle', function() { var src = $(this).attr('src'); var target = $(this).data('target'); if ($.common.equals("self", target)) { var height = $(this).data('height'); var width = $(this).data('width'); top.layer.open({ title: false, type: 1, closeBtn: true, shadeClose: true, content: "" }); } else if ($.common.equals("blank", target)) { window.open(src); } }); // 单击tooltip事件 $(optionsIds).on("click", '.tooltip-show', function() { var target = $(this).data('target'); var input = $(this).prev(); if ($.common.equals("copy", target)) { input.select(); document.execCommand("copy"); } else if ($.common.equals("open", target)) { top.layer.alert(input.val(), { title: "信息内容", area: ['400px', ''], shadeClose: true, btn: ['关闭'], btnclass: ['btn btn-primary'], }); } }); }, // 当所有数据被加载时触发 onLoadSuccess: function(data) { if (typeof table.options.onLoadSuccess == "function") { table.options.onLoadSuccess(data); } }, // 表格销毁 destroy: function (tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; $("#" + currentId).bootstrapTable('destroy'); delete table.rememberSelectedIds[currentId]; delete table.rememberSelecteds[currentId]; }, // 序列号生成 serialNumber: function (index, tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; var tableParams = $("#" + currentId).bootstrapTable('getOptions'); var pageSize = $.common.isNotEmpty(tableParams.pageSize) ? tableParams.pageSize: table.options.pageSize; var pageNumber = $.common.isNotEmpty(tableParams.pageNumber) ? tableParams.pageNumber: table.options.pageNumber; if (table.options.sidePagination == 'client') { return index + 1; } return pageSize * (pageNumber - 1) + index + 1; }, // 列超出指定长度浮动提示 target(copy单击复制文本 open弹窗打开文本) tooltip: function (value, length, target) { var _length = $.common.isEmpty(length) ? 20 : length; var _text = ""; var _value = $.common.nullToStr(value); var _target = $.common.isEmpty(target) ? 'copy' : target; if (_value.length > _length) { _text = _value.substr(0, _length) + "..."; _value = _value.replace(/\'/g,"'"); _value = _value.replace(/\"/g,"""); var actions = []; actions.push($.common.sprintf('', _value)); actions.push($.common.sprintf('%s', _target, _value, _text)); return actions.join(''); } else { _text = _value; return _text; } }, // 下拉按钮切换 dropdownToggle: function (value) { var actions = []; actions.push('
                    '); actions.push(''); actions.push(''); actions.push('
                    '); return actions.join(''); }, // 图片预览 imageView: function (value, height, width, target) { if ($.common.isEmpty(width)) { width = 'auto'; } if ($.common.isEmpty(height)) { height = 'auto'; } // blank or self var _target = $.common.isEmpty(target) ? 'self' : target; if ($.common.isNotEmpty(value)) { return $.common.sprintf("", height, width, _target, value); } else { return $.common.nullToStr(value); } }, // 搜索-默认第一个form search: function(formId, tableId, pageNumber, pageSize) { table.set(tableId); table.options.formId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; var params = $.common.isEmpty(tableId) ? $("#" + table.options.id).bootstrapTable('getOptions') : $("#" + tableId).bootstrapTable('getOptions'); if ($.common.isNotEmpty(pageNumber)) { params.pageNumber = pageNumber; } if ($.common.isNotEmpty(pageSize)) { params.pageSize = pageSize; } if ($.common.isNotEmpty(tableId)) { $("#" + tableId).bootstrapTable('refresh', params); } else{ $("#" + table.options.id).bootstrapTable('refresh', params); } }, // 导出数据 exportExcel: function(formId) { table.set(); $.modal.confirm("确定导出所有" + table.options.modalName + "吗?", function() { var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; var params = $("#" + table.options.id).bootstrapTable('getOptions'); var dataParam = $("#" + currentId).serializeArray(); dataParam.push({ "name": "orderByColumn", "value": params.sortName }); dataParam.push({ "name": "isAsc", "value": params.sortOrder }); $.modal.loading("正在导出数据,请稍候..."); $.post(table.options.exportUrl, dataParam, function(result) { if (result.code == web_status.SUCCESS) { window.location.href = ctx + "common/download?fileName=" + encodeURI(result.msg) + "&delete=" + true; } else if (result.code == web_status.WARNING) { $.modal.alertWarning(result.msg) } else { $.modal.alertError(result.msg); } $.modal.closeLoading(); }); }); }, // 下载模板 importTemplate: function() { $.get(activeWindow().table.options.importTemplateUrl, function(result) { if (result.code == web_status.SUCCESS) { window.location.href = ctx + "common/download?fileName=" + encodeURI(result.msg) + "&delete=" + true; } else if (result.code == web_status.WARNING) { $.modal.alertWarning(result.msg) } else { $.modal.alertError(result.msg); } }); }, // 导入数据 importExcel: function(formId, width, height) { table.set(); var currentId = $.common.isEmpty(formId) ? 'importTpl' : formId; var _width = $.common.isEmpty(width) ? "400" : width; var _height = $.common.isEmpty(height) ? "230" : height; top.layer.open({ type: 1, area: [_width + 'px', _height + 'px'], fix: false, //不固定 maxmin: true, shade: 0.3, title: '导入' + table.options.modalName + '数据', content: $('#' + currentId).html(), btn: [' 导入', ' 取消'], // 弹层外区域关闭 shadeClose: true, btn1: function(index, layero){ var file = layero.find('#file').val(); if (file == '' || (!$.common.endWith(file, '.xls') && !$.common.endWith(file, '.xlsx'))) { $.modal.msgWarning("请选择后缀为 “xls”或“xlsx”的文件。"); return false; } var index = top.layer.load(2, {shade: false}); $.modal.disable(); var formData = new FormData(layero.find('form')[0]); $.ajax({ url: table.options.importUrl, data: formData, cache: false, contentType: false, processData: false, type: 'POST', success: function (result) { if (result.code == web_status.SUCCESS) { $.modal.close(index); $.modal.closeAll(); $.modal.alertSuccess(result.msg); $.table.refresh(); } else if (result.code == web_status.WARNING) { $.modal.close(index); $.modal.enable(); $.modal.alertWarning(result.msg) } else { $.modal.close(index); $.modal.enable(); $.modal.alertError(result.msg); } }, complete: function () { layero.find('#file').val(''); } }); } }); }, // 刷新表格 refresh: function(tableId, pageNumber, pageSize, url) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; var params = $("#" + currentId).bootstrapTable('getOptions'); if ($.common.isEmpty(pageNumber)) { pageNumber = params.pageNumber; } if ($.common.isEmpty(pageSize)) { pageSize = params.pageSize; } if ($.common.isEmpty(url)) { url = $.common.isEmpty(url) ? params.url : url; } $("#" + currentId).bootstrapTable('refresh', { silent: true, url: url, pageNumber: pageNumber, pageSize: pageSize }); }, // 刷新options配置 refreshOptions: function(options, tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; $("#" + currentId).bootstrapTable('refreshOptions', options); }, // 查询表格指定列值 deDuplication( true去重、false不去重) selectColumns: function(column, deDuplication) { var distinct = $.common.isEmpty(deDuplication) ? true : deDuplication; var rows = $.map($("#" + table.options.id).bootstrapTable('getSelections'), function (row) { return $.common.getItemField(row, column); }); if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { var selectedRows = table.rememberSelecteds[table.options.id]; if ($.common.isNotEmpty(selectedRows)) { rows = $.map(table.rememberSelecteds[table.options.id], function (row) { return $.common.getItemField(row, column); }); } } return distinct ? $.common.uniqueFn(rows) : rows; }, // 获取当前页选中或者取消的行ID affectedRowIds: function(rows) { var column = $.common.isEmpty(table.options.uniqueId) ? table.options.columns[1].field : table.options.uniqueId; var rowIds; if ($.isArray(rows)) { rowIds = $.map(rows, function(row) { return $.common.getItemField(row, column); }); } else { rowIds = [rows[column]]; } return rowIds; }, // 查询表格首列值deDuplication( true去重、false不去重) selectFirstColumns: function(deDuplication) { var distinct = $.common.isEmpty(deDuplication) ? true : deDuplication; var rows = $.map($("#" + table.options.id).bootstrapTable('getSelections'), function (row) { return $.common.getItemField(row, table.options.columns[1].field); }); if ($.common.isNotEmpty(table.options.rememberSelected) && table.options.rememberSelected) { var selectedRows = table.rememberSelecteds[table.options.id]; if ($.common.isNotEmpty(selectedRows)) { rows = $.map(selectedRows, function (row) { return $.common.getItemField(row, table.options.columns[1].field); }); } } return distinct ? $.common.uniqueFn(rows) : rows; }, // 回显数据字典 selectDictLabel: function(datas, value) { if ($.common.isEmpty(datas) || $.common.isEmpty(value)) { return ''; } var actions = []; $.each(datas, function(index, dict) { if (dict.dictValue == ('' + value)) { var listClass = $.common.equals("default", dict.listClass) || $.common.isEmpty(dict.listClass) ? "" : "badge badge-" + dict.listClass; var cssClass = $.common.isNotEmpty(dict.cssClass) ? dict.cssClass : listClass; actions.push($.common.sprintf("%s", cssClass, dict.dictLabel)); return false; } }); if (actions.length === 0) { actions.push($.common.sprintf("%s", value)) } return actions.join(''); }, // 回显数据字典(字符串数组) selectDictLabels: function(datas, value, separator) { if ($.common.isEmpty(datas) || $.common.isEmpty(value)) { return ''; } var currentSeparator = $.common.isEmpty(separator) ? "," : separator; var actions = []; $.each(value.split(currentSeparator), function(i, val) { var match = false $.each(datas, function(index, dict) { if (dict.dictValue == ('' + val)) { var listClass = $.common.equals("default", dict.listClass) || $.common.isEmpty(dict.listClass) ? "" : "badge badge-" + dict.listClass; actions.push($.common.sprintf("%s", listClass, dict.dictLabel)); match = true return false; } }); if (!match) { actions.push($.common.sprintf(" %s ", val)); } }); return actions.join(''); }, // 显示表格指定列 showColumn: function(column, tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; $("#" + currentId).bootstrapTable('showColumn', column); }, // 隐藏表格指定列 hideColumn: function(column, tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; $("#" + currentId).bootstrapTable('hideColumn', column); }, // 显示所有表格列 showAllColumns: function(tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; $("#" + currentId).bootstrapTable('showAllColumns'); }, // 隐藏所有表格列 hideAllColumns: function(tableId) { var currentId = $.common.isEmpty(tableId) ? table.options.id : tableId; $("#" + currentId).bootstrapTable('hideAllColumns'); } }, // 表格树封装处理 treeTable: { // 初始化表格 init: function(options) { var defaults = { id: "bootstrap-tree-table", type: 1, // 0 代表bootstrapTable 1代表bootstrapTreeTable height: 0, rootIdValue: 0, ajaxParams: {}, toolbar: "toolbar", striped: false, pagination: false, pageSize: 10, pageList: [10, 25, 50], expandColumn: 1, showSearch: true, showRefresh: true, showColumns: true, expandAll: true, expandFirst: true }; var options = $.extend(defaults, options); table.options = options; table.config[options.id] = options; $.table.initEvent(); $.bttTable = $('#' + options.id).bootstrapTreeTable({ code: options.code, // 用于设置父子关系 parentCode: options.parentCode, // 用于设置父子关系 type: 'post', // 请求方式(*) url: options.url, // 请求后台的URL(*) data: options.data, // 无url时用于渲染的数据 ajaxParams: options.ajaxParams, // 请求数据的ajax的data属性 rootIdValue: options.rootIdValue, // 设置指定根节点id值 height: options.height, // 表格树的高度 pagination: options.pagination, // 是否显示分页 dataUrl: options.dataUrl, // 加载子节点异步请求数据url pageSize: options.pageSize, // 每页的记录行数 pageList: options.pageList, // 可供选择的每页的行数 expandColumn: options.expandColumn, // 在哪一列上面显示展开按钮 striped: options.striped, // 是否显示行间隔色 bordered: options.bordered, // 是否显示边框 toolbar: '#' + options.toolbar, // 指定工作栏 showSearch: options.showSearch, // 是否显示检索信息 showRefresh: options.showRefresh, // 是否显示刷新按钮 showColumns: options.showColumns, // 是否显示隐藏某列下拉框 expandAll: options.expandAll, // 是否全部展开 expandFirst: options.expandFirst, // 是否默认第一级展开--expandAll为false时生效 columns: options.columns, // 显示列信息(*) onClickRow: options.onClickRow, // 单击某行事件 responseHandler: $.treeTable.responseHandler, // 在加载服务器发送来的数据之前处理函数 onLoadSuccess: $.treeTable.onLoadSuccess // 当所有数据被加载时触发处理函数 }); }, // 条件查询 search: function(formId) { var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; var params = $.common.formToJSON(currentId); $.bttTable.bootstrapTreeTable('refresh', $.extend(params, table.options.ajaxParams)); }, // 刷新 refresh: function() { $.bttTable.bootstrapTreeTable('refresh'); }, // 查询表格树指定列值deDuplication( true去重、false不去重) selectColumns: function(column, deDuplication) { var distinct = $.common.isEmpty(deDuplication) ? true : deDuplication; var rows = $.map($.bttTable.bootstrapTreeTable('getSelections'), function (row) { return $.common.getItemField(row, column); }); return distinct ? $.common.uniqueFn(rows) : rows; }, // 请求获取数据后处理回调函数,校验异常状态提醒 responseHandler: function(res) { if (typeof table.options.responseHandler == "function") { table.options.responseHandler(res); } if (res.code != undefined && res.code != web_status.SUCCESS) { $.modal.alertWarning(res.msg); return []; } else { return res; } }, // 当所有数据被加载时触发 onLoadSuccess: function(data) { if (typeof table.options.onLoadSuccess == "function") { table.options.onLoadSuccess(data); } $(".table [data-toggle='tooltip']").tooltip(); }, }, // 表单封装处理 form: { // 表单重置 reset: function(formId, tableId, pageNumber, pageSize) { table.set(tableId); formId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; $("#" + formId)[0].reset(); var tableId = $.common.isEmpty(tableId) ? table.options.id : tableId; if (table.options.type == table_type.bootstrapTable) { var params = $("#" + tableId).bootstrapTable('getOptions'); params.pageNumber = 1; if ($.common.isNotEmpty(pageSize)) { params.pageSize = pageSize; } $("#" + tableId).bootstrapTable('refresh', params); } else if (table.options.type == table_type.bootstrapTreeTable) { $("#" + tableId).bootstrapTreeTable('refresh', table.options.ajaxParams); } resetDate(); }, // 获取选中复选框项 selectCheckeds: function(name) { var checkeds = ""; $('input:checkbox[name="' + name + '"]:checked').each(function(i) { if (0 == i) { checkeds = $(this).val(); } else { checkeds += ("," + $(this).val()); } }); return checkeds; }, // 获取选中下拉框项 selectSelects: function(name) { var selects = ""; $('#' + name + ' option:selected').each(function (i) { if (0 == i) { selects = $(this).val(); } else { selects += ("," + $(this).val()); } }); return selects; } }, // 弹出层封装处理 modal: { // 显示图标 icon: function(type) { var icon = ""; if (type == modal_status.WARNING) { icon = 0; } else if (type == modal_status.SUCCESS) { icon = 1; } else if (type == modal_status.FAIL) { icon = 2; } else { icon = 3; } return icon; }, // 消息提示 msg: function(content, type) { if (type != undefined) { top.layer.msg(content, { icon: $.modal.icon(type), time: 1000, shift: 5 }); } else { top.layer.msg(content); } }, // 错误消息 msgError: function(content) { $.modal.msg(content, modal_status.FAIL); }, // 成功消息 msgSuccess: function(content) { $.modal.msg(content, modal_status.SUCCESS); }, // 警告消息 msgWarning: function(content) { $.modal.msg(content, modal_status.WARNING); }, // 弹出提示 alert: function(content, type) { top.layer.alert(content, { icon: $.modal.icon(type), title: "系统提示", btn: ['关闭'], btnclass: ['btn btn-primary'], }); }, // 错误提示 alertError: function(content) { $.modal.alert(content, modal_status.FAIL); }, // 成功提示 alertSuccess: function(content) { $.modal.alert(content, modal_status.SUCCESS); }, // 警告提示 alertWarning: function(content) { $.modal.alert(content, modal_status.WARNING); }, // 消息提示,重新加载页面 msgReload: function(msg, type) { top.layer.msg(msg, { icon: $.modal.icon(type), time: 500, shade: [0.1, '#8F8F8F'] }, function() { $.modal.reload(); }); }, // 消息提示成功并刷新父窗体 msgSuccessReload: function(msg) { $.modal.msgReload(msg, modal_status.SUCCESS); }, // 获取iframe页的DOM getChildFrame: function (index) { if ($.common.isEmpty(index)) { var index = parent.layer.getFrameIndex(window.name); return parent.layer.getChildFrame('body', index); } else { return top.layer.getChildFrame('body', index); } }, // 关闭窗体 close: function (index) { if ($.common.isEmpty(index)) { var index = parent.layer.getFrameIndex(window.name); parent.layer.close(index); } else { top.layer.close(index); } }, // 关闭全部窗体 closeAll: function () { top.layer.closeAll(); }, // 确认窗体 confirm: function (content, callBack) { top.layer.confirm(content, { icon: 3, title: "系统提示", btn: ['确认', '取消'] }, function (index) { $.modal.close(index); callBack(true); }); }, // 弹出层指定宽度 open: function (title, url, width, height, callback) { // 如果是移动端,就使用自适应大小弹窗 if ($.common.isMobile()) { width = 'auto'; height = 'auto'; } if ($.common.isEmpty(title)) { title = false; } if ($.common.isEmpty(url)) { url = "/404.html"; } if ($.common.isEmpty(width)) { width = 800; } if ($.common.isEmpty(height)) { height = ($(window).height() - 50); } if ($.common.isEmpty(callback)) { callback = function(index, layero) { var iframeWin = layero.find('iframe')[0]; iframeWin.contentWindow.submitHandler(index, layero); } } top.layer.open({ type: 2, area: [width + 'px', height + 'px'], fix: false, //不固定 maxmin: true, shade: 0.3, title: title, content: url, btn: ['确定', '关闭'], // 弹层外区域关闭 shadeClose: true, yes: callback, cancel: function(index) { return true; }, success: function () { $(':focus').blur(); } }); }, // 弹出层指定参数选项 openOptions: function (options) { var _url = $.common.isEmpty(options.url) ? "/404.html" : options.url; var _title = $.common.isEmpty(options.title) ? "系统窗口" : options.title; var _width = $.common.isEmpty(options.width) ? "800" : options.width; var _height = $.common.isEmpty(options.height) ? ($(window).height() - 50) : options.height; var _btn = [' 确认', ' 关闭']; // 如果是移动端,就使用自适应大小弹窗 if ($.common.isMobile()) { _width = 'auto'; _height = 'auto'; } if ($.common.isEmpty(options.yes)) { options.yes = function(index, layero) { options.callBack(index, layero); } } var btnCallback = {}; if (options.btn instanceof Array){ for (var i = 1, len = options.btn.length; i < len; i++) { var btn = options["btn" + (i + 1)]; if (btn) { btnCallback["btn" + (i + 1)] = btn; } } } var index = top.layer.open($.extend({ id: options.id, // 唯一id anim: options.anim, // 弹出动画 0-6 type: 2, maxmin: $.common.isEmpty(options.maxmin) ? true : options.maxmin, shade: 0.3, title: _title, fix: false, area: [_width + 'px', _height + 'px'], content: _url, closeBtn: $.common.isEmpty(options.closeBtn) ? 1 : options.closeBtn, shadeClose: $.common.isEmpty(options.shadeClose) ? true : options.shadeClose, skin: options.skin, // options.btn设置为0表示不显示按钮 btn: $.common.isEmpty(options.btn) ? _btn : options.btn, yes: options.yes, cancel: function () { return true; }, success: function () { $(':focus').blur(); } }, btnCallback)); if ($.common.isNotEmpty(options.full) && options.full === true) { top.layer.full(index); } }, // 弹出层全屏 openFull: function (title, url, width, height) { // 如果是移动端,就使用自适应大小弹窗 if ($.common.isMobile()) { width = 'auto'; height = 'auto'; } if ($.common.isEmpty(title)) { title = false; } if ($.common.isEmpty(url)) { url = "/404.html"; } if ($.common.isEmpty(width)) { width = 800; } if ($.common.isEmpty(height)) { height = ($(window).height() - 50); } var index = top.layer.open({ type: 2, area: [width + 'px', height + 'px'], fix: false, //不固定 maxmin: true, shade: 0.3, title: title, content: url, btn: ['确定', '关闭'], // 弹层外区域关闭 shadeClose: true, yes: function(index, layero) { var iframeWin = layero.find('iframe')[0]; iframeWin.contentWindow.submitHandler(index, layero); }, cancel: function(index) { return true; }, success: function () { $(':focus').blur(); } }); top.layer.full(index); }, // 选卡页方式打开 openTab: function (title, url, isRefresh) { createMenuItem(url, title, isRefresh); }, // 选卡页同一页签打开 parentTab: function (title, url) { var dataId = window.frameElement.getAttribute('data-id'); createMenuItem(url, title); closeItem(dataId); }, // 右侧弹出窗口打开 popupRight: function(title, url){ var width = 150; if (top.location !== self.location) { if ($(top.window).outerWidth() < 400) { width = 50; } } top.layer.open({ type: 2, offset: 'r', anim: 'slideLeft', move: false, title: title, shade: 0.3, shadeClose: true, area: [($(window).outerWidth() - width) + 'px', '100%'], content: url }); }, // 关闭选项卡 closeTab: function (dataId) { closeItem(dataId); }, // 禁用按钮 disable: function() { var doc = window.top == window.parent ? top.window.document : window.parent.document; $("a[class*=layui-layer-btn]", doc).addClass("layer-disabled"); }, // 启用按钮 enable: function() { var doc = window.top == window.parent ? top.window.document : window.parent.document; $("a[class*=layui-layer-btn]", doc).removeClass("layer-disabled"); }, // 打开遮罩层 loading: function (message) { $.blockUI({ message: '
                    ' + message + '
                    ' }); }, // 关闭遮罩层 closeLoading: function () { setTimeout(function(){ $.unblockUI(); }, 50); }, // 重新加载 reload: function () { parent.location.reload(); } }, // 操作封装处理 operate: { // 提交数据 submit: function(url, type, dataType, data, callback) { var config = { url: url, type: type, dataType: dataType, data: data, beforeSend: function (xhr, settings) { var csrftoken = $('meta[name=csrf-token]').attr('content'); if ($.common.equalsIgnoreCase(settings.type, "POST")) { xhr.setRequestHeader("X-CSRF-Token", csrftoken); } $.modal.loading("正在处理中,请稍候..."); }, success: function(result) { if (typeof callback == "function") { callback(result); } $.operate.ajaxSuccess(result); } }; $.ajax(config) }, // post请求传输 post: function(url, data, callback) { $.operate.submit(url, "post", "json", data, callback); }, // get请求传输 get: function(url, callback) { $.operate.submit(url, "get", "json", "", callback); }, // 详细信息 detail: function(id, width, height) { table.set(); var _url = $.operate.detailUrl(id); var options = { title: table.options.modalName + "详细", width: width, height: height, url: _url, btn: 0, yes: function (index, layero) { $.modal.close(index); } }; $.modal.openOptions(options); }, // 详细信息,以tab页展现 detailTab: function(id) { table.set(); $.modal.openTab("详细" + table.options.modalName, $.operate.detailUrl(id)); }, // 详细访问地址 detailUrl: function(id) { var url = "/404.html"; if ($.common.isNotEmpty(id)) { url = table.options.detailUrl.replace("{id}", id); } else { var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); if (id.length == 0) { $.modal.alertWarning("请至少选择一条记录"); return; } url = table.options.detailUrl.replace("{id}", id); } return url; }, // 删除信息 remove: function(id) { table.set(); $.modal.confirm("确定删除该条" + table.options.modalName + "信息吗?", function() { var url = $.common.isEmpty(id) ? table.options.removeUrl : table.options.removeUrl.replace("{id}", id); if (table.options.type == table_type.bootstrapTreeTable) { $.operate.get(url); } else { var data = { "ids": id }; $.operate.submit(url, "post", "json", data); } }); }, // 批量删除信息 removeAll: function() { table.set(); var rows = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); if (rows.length == 0) { $.modal.alertWarning("请至少选择一条记录"); return; } $.modal.confirm("确认要删除选中的" + rows.length + "条数据吗?", function() { var url = table.options.removeUrl; var data = { "ids": rows.join() }; $.operate.submit(url, "post", "json", data); }); }, // 清空信息 clean: function() { table.set(); $.modal.confirm("确定清空所有" + table.options.modalName + "吗?", function() { var url = table.options.cleanUrl; $.operate.submit(url, "post", "json", ""); }); }, // 添加信息 add: function(id) { table.set(); $.modal.open("添加" + table.options.modalName, $.operate.addUrl(id)); }, // 添加信息,以tab页展现 addTab: function (id) { table.set(); $.modal.openTab("添加" + table.options.modalName, $.operate.addUrl(id)); }, // 添加信息 全屏 addFull: function(id) { table.set(); $.modal.openFull("添加" + table.options.modalName, $.operate.addUrl(id)); }, // 添加访问地址 addUrl: function(id) { var url = $.common.isEmpty(id) ? table.options.createUrl.replace("{id}", "") : table.options.createUrl.replace("{id}", id); return url; }, // 修改信息 edit: function(id) { table.set(); if ($.common.isEmpty(id) && table.options.type == table_type.bootstrapTreeTable) { var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0]; if ($.common.isEmpty(row)) { $.modal.alertWarning("请至少选择一条记录"); return; } var url = table.options.updateUrl.replace("{id}", row[table.options.uniqueId]); $.modal.open("修改" + table.options.modalName, url); } else { $.modal.open("修改" + table.options.modalName, $.operate.editUrl(id)); } }, // 修改信息,以tab页展现 editTab: function(id) { table.set(); $.modal.openTab("修改" + table.options.modalName, $.operate.editUrl(id)); }, // 修改信息 全屏 editFull: function(id) { table.set(); var url = "/404.html"; if ($.common.isNotEmpty(id)) { url = table.options.updateUrl.replace("{id}", id); } else { if (table.options.type == table_type.bootstrapTreeTable) { var row = $("#" + table.options.id).bootstrapTreeTable('getSelections')[0]; if ($.common.isEmpty(row)) { $.modal.alertWarning("请至少选择一条记录"); return; } url = table.options.updateUrl.replace("{id}", row[table.options.uniqueId]); } else { var row = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); url = table.options.updateUrl.replace("{id}", row); } } $.modal.openFull("修改" + table.options.modalName, url); }, // 修改访问地址 editUrl: function(id) { var url = "/404.html"; if ($.common.isNotEmpty(id)) { url = table.options.updateUrl.replace("{id}", id); } else { var id = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId); if (id.length == 0) { $.modal.alertWarning("请至少选择一条记录"); return; } url = table.options.updateUrl.replace("{id}", id); } return url; }, // 右侧弹出详情 view: function(id){ table.set(); var url = table.options.viewUrl.replace("{id}", id); $.modal.popupRight(table.options.modalName + "信息详情", url); }, // 保存信息 刷新表格 save: function(url, data, callback) { var config = { url: url, type: "post", dataType: "json", data: data, beforeSend: function (xhr, settings) { var csrftoken = $('meta[name=csrf-token]').attr('content'); if (($.common.equalsIgnoreCase(settings.type, "POST"))) { xhr.setRequestHeader("X-CSRF-Token", csrftoken); } $.modal.loading("正在处理中,请稍候..."); $.modal.disable(); }, success: function(result) { if (typeof callback == "function") { callback(result); } $.operate.successCallback(result); } }; $.ajax(config) }, // 保存信息 弹出结果提示框 saveModal: function(url, data, callback) { var config = { url: url, type: "post", dataType: "json", data: data, beforeSend: function (xhr, settings) { var csrftoken = $('meta[name=csrf-token]').attr('content'); if (($.common.equalsIgnoreCase(settings.type, "POST"))) { xhr.setRequestHeader("X-CSRF-Token", csrftoken); } $.modal.loading("正在处理中,请稍候..."); }, success: function(result) { if (typeof callback == "function") { callback(result); } if (result.code == web_status.SUCCESS) { $.modal.alertSuccess(result.msg) } else if (result.code == web_status.WARNING) { $.modal.alertWarning(result.msg) } else { $.modal.alertError(result.msg); } $.modal.closeLoading(); } }; $.ajax(config) }, // 保存选项卡信息 saveTab: function(url, data, callback) { var config = { url: url, type: "post", dataType: "json", data: data, beforeSend: function (xhr, settings) { var csrftoken = $('meta[name=csrf-token]').attr('content'); if (($.common.equalsIgnoreCase(settings.type, "POST"))) { xhr.setRequestHeader("X-CSRF-Token", csrftoken); } $.modal.loading("正在处理中,请稍候..."); }, success: function(result) { if (typeof callback == "function") { callback(result); } $.operate.successTabCallback(result); } }; $.ajax(config) }, // 保存结果弹出msg刷新table表格 ajaxSuccess: function (result) { if (result.code == web_status.SUCCESS && table.options.type == table_type.bootstrapTable) { $.modal.msgSuccess(result.msg); $.table.refresh(); } else if (result.code == web_status.SUCCESS && table.options.type == table_type.bootstrapTreeTable) { $.modal.msgSuccess(result.msg); $.treeTable.refresh(); } else if (result.code == web_status.SUCCESS && $.common.isEmpty(table.options.type)) { $.modal.msgSuccess(result.msg) } else if (result.code == web_status.WARNING) { $.modal.alertWarning(result.msg) } else { $.modal.alertError(result.msg); } $.modal.closeLoading(); }, // 保存结果重新加载页面 saveReload: function (result) { if (result.code == web_status.SUCCESS) { $.modal.msgSuccessReload(result.msg); } else if (result.code == web_status.WARNING) { $.modal.alertWarning(result.msg) } else { $.modal.alertError(result.msg); } $.modal.closeLoading(); }, // 成功回调执行事件(父窗体静默更新) successCallback: function(result) { if (result.code == web_status.SUCCESS) { var parent = activeWindow(); if ($.common.isEmpty(parent.table)) { $.modal.msgSuccessReload(result.msg); } else if (parent.table.options.type == table_type.bootstrapTable) { parent.$.modal.msgSuccess(result.msg); parent.$.table.refresh(); } else if (parent.table.options.type == table_type.bootstrapTreeTable) { parent.$.modal.msgSuccess(result.msg); parent.$.treeTable.refresh(); } else { parent.$.modal.msgSuccess(result.msg); } $.modal.close(); } else if (result.code == web_status.WARNING) { $.modal.alertWarning(result.msg) } else { $.modal.alertError(result.msg); } $.modal.closeLoading(); $.modal.enable(); }, // 选项卡成功回调执行事件(父窗体静默更新) successTabCallback: function(result) { if (result.code == web_status.SUCCESS) { var topWindow = $(window.parent.document); var currentId = $('.page-tabs-content', topWindow).find('.active').attr('data-panel'); var topWindow = $('.RuoYi_iframe[data-id="' + currentId + '"]', topWindow)[0]; if ($.common.isNotEmpty(topWindow) && $.common.isNotEmpty(currentId)) { var $contentWindow = topWindow.contentWindow; $contentWindow.$.modal.msgSuccess(result.msg); $contentWindow.$(".layui-layer-padding").removeAttr("style"); if ($contentWindow.table.options.type == table_type.bootstrapTable) { $contentWindow.$.table.refresh(); } else if ($contentWindow.table.options.type == table_type.bootstrapTreeTable) { $contentWindow.$.treeTable.refresh(); } } else { $.modal.msgSuccess(result.msg); } $.modal.close(); $.modal.closeTab(); } else if (result.code == web_status.WARNING) { $.modal.alertWarning(result.msg) } else { $.modal.alertError(result.msg); } $.modal.closeLoading(); } }, // 校验封装处理 validate: { // 表单验证 form: function (formId) { var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; return $("#" + currentId).validate().form(); }, // 重置表单验证(清除提示信息) reset: function (formId) { var currentId = $.common.isEmpty(formId) ? $('form').attr('id') : formId; return $("#" + currentId).validate().resetForm(); } }, // 树插件封装处理 tree: { _option: {}, _lastValue: {}, // 初始化树结构 init: function(options) { var defaults = { id: "tree", // 属性ID expandLevel: 0, // 展开等级节点 view: { selectedMulti: false, // 设置是否允许同时选中多个节点 nameIsHTML: true // 设置 name 属性是否支持 HTML 脚本 }, check: { enable: false, // 置 zTree 的节点上是否显示 checkbox / radio nocheckInherit: true, // 设置子节点是否自动继承 chkboxType: { "Y": "ps", "N": "ps" } // 父子节点的关联关系 }, data: { key: { title: "title" // 节点数据保存节点提示信息的属性名称 }, simpleData: { enable: true // true / false 分别表示 使用 / 不使用 简单数据模式 } }, }; var options = $.extend(defaults, options); $.tree._option = options; // 树结构初始化加载 var setting = { callback: { onClick: options.onClick, // 用于捕获节点被点击的事件回调函数 onCheck: options.onCheck, // 用于捕获 checkbox / radio 被勾选 或 取消勾选的事件回调函数 onDblClick: options.onDblClick // 用于捕获鼠标双击之后的事件回调函数 }, check: options.check, view: options.view, data: options.data }; $.get(options.url, function(data) { var treeId = $("#treeId").val(); tree = $.fn.zTree.init($("#" + options.id), setting, data); $._tree = tree; for (var i = 0; i < options.expandLevel; i++) { var nodes = tree.getNodesByParam("level", i); for (var j = 0; j < nodes.length; j++) { tree.expandNode(nodes[j], true, false, false); } } var node = tree.getNodesByParam("id", treeId, null)[0]; $.tree.selectByIdName(treeId, node); // 回调tree方法 if (typeof(options.callBack) === "function"){ options.callBack(tree); } }); }, // 搜索节点 searchNode: function() { // 取得输入的关键字的值 var value = $.common.trim($("#keyword").val()); if ($.tree._lastValue == value) { return; } // 保存最后一次搜索名称 $.tree._lastValue = value; var nodes = $._tree.getNodes(); // 如果要查空字串,就退出不查了。 if (value == "") { $.tree.showAllNode(nodes); return; } $.tree.hideAllNode(nodes); // 根据搜索值模糊匹配 $.tree.updateNodes($._tree.getNodesByParamFuzzy("name", value)); }, // 根据Id和Name选中指定节点 selectByIdName: function(treeId, node) { if ($.common.isNotEmpty(treeId) && node && treeId == node.id) { $._tree.selectNode(node, true); } }, // 显示所有节点 showAllNode: function(nodes) { nodes = $._tree.transformToArray(nodes); for (var i = nodes.length - 1; i >= 0; i--) { if (nodes[i].getParentNode() != null) { $._tree.expandNode(nodes[i], true, false, false, false); } else { $._tree.expandNode(nodes[i], true, true, false, false); } $._tree.showNode(nodes[i]); $.tree.showAllNode(nodes[i].children); } }, // 隐藏所有节点 hideAllNode: function(nodes) { var nodes = $._tree.transformToArray(nodes); for (var i = nodes.length - 1; i >= 0; i--) { $._tree.hideNode(nodes[i]); } }, // 显示所有父节点 showParent: function(treeNode) { var parentNode; while ((parentNode = treeNode.getParentNode()) != null) { $._tree.showNode(parentNode); $._tree.expandNode(parentNode, true, false, false); treeNode = parentNode; } }, // 显示所有孩子节点 showChildren: function(treeNode) { if (treeNode.isParent) { for (var idx in treeNode.children) { var node = treeNode.children[idx]; $._tree.showNode(node); $.tree.showChildren(node); } } }, // 更新节点状态 updateNodes: function(nodeList) { $._tree.showNodes(nodeList); for (var i = 0, l = nodeList.length; i < l; i++) { var treeNode = nodeList[i]; $.tree.showChildren(treeNode); $.tree.showParent(treeNode) } }, // 获取当前被勾选集合 getCheckedNodes: function(column) { var _column = $.common.isEmpty(column) ? "id" : column; var nodes = $._tree.getCheckedNodes(true); return $.map(nodes, function (row) { return row[_column]; }).join(); }, // 不允许根父节点选择 notAllowParents: function(_tree) { var nodes = _tree.getSelectedNodes(); if (nodes.length == 0){ $.modal.msgError("请选择节点后提交"); return false; } for (var i = 0; i < nodes.length; i++) { if (nodes[i].level == 0) { $.modal.msgError("不能选择根节点(" + nodes[i].name + ")"); return false; } if (nodes[i].isParent) { $.modal.msgError("不能选择父节点(" + nodes[i].name + ")"); return false; } } return true; }, // 不允许最后层级节点选择 notAllowLastLevel: function(_tree) { var nodes = _tree.getSelectedNodes(); for (var i = 0; i < nodes.length; i++) { if (!nodes[i].isParent) { $.modal.msgError("不能选择最后层级节点(" + nodes[i].name + ")"); return false; } } return true; }, // 隐藏/显示搜索栏 toggleSearch: function() { $('#search').slideToggle(200); $('#btnShow').toggle(); $('#btnHide').toggle(); $('#keyword').focus(); }, // 折叠 collapse: function() { $._tree.expandAll(false); }, // 展开 expand: function() { $._tree.expandAll(true); } }, // 通用方法封装处理 common: { // 判断字符串是否为空 isEmpty: function (value) { if (value == null || this.trim(value) == "" || value == undefined || value == "undefined") { return true; } return false; }, // 判断一个字符串是否为非空串 isNotEmpty: function (value) { return !$.common.isEmpty(value); }, // 如果值是空,则返回指定默认字符串,否则返回字符串本身 nullToDefault: function (value, defaultValue) { return $.common.isEmpty(value) ? defaultValue : value; }, // 空对象转字符串 nullToStr: function(value) { if ($.common.isEmpty(value)) { return "-"; } return value; }, // 是否显示数据 为空默认为显示 visible: function (value) { if ($.common.isEmpty(value) || value == true) { return true; } return false; }, // 空格截取 trim: function (value) { if (value == null) { return ""; } return value.toString().replace(/(^\s*)|(\s*$)|\r|\n/g, ""); }, // 比较两个字符串(大小写敏感) equals: function (str, that) { return str == that; }, // 比较两个字符串(大小写不敏感) equalsIgnoreCase: function (str, that) { return String(str).toUpperCase() === String(that).toUpperCase(); }, // 将字符串按指定字符分割 split: function (str, sep, maxLen) { if ($.common.isEmpty(str)) { return null; } var value = String(str).split(sep); return maxLen ? value.slice(0, maxLen - 1) : value; }, // 字符串格式化(%s ) sprintf: function (str) { var args = arguments, flag = true, i = 1; str = str.replace(/%s/g, function () { var arg = args[i++]; if (typeof arg === 'undefined') { flag = false; return ''; } return arg == null ? '' : arg; }); return flag ? str : ''; }, // 日期格式化 时间戳 -> yyyy-MM-dd HH-mm-ss dateFormat: function(date, format) { var that = this; if (that.isEmpty(date)) return ""; if (!date) return; if (!format) format = "yyyy-MM-dd"; switch (typeof date) { case "string": date = new Date(date.replace(/-/g, "/")); break; case "number": date = new Date(date); break; } if (!date instanceof Date) return; var dict = { "yyyy": date.getFullYear(), "M": date.getMonth() + 1, "d": date.getDate(), "H": date.getHours(), "m": date.getMinutes(), "s": date.getSeconds(), "MM": ("" + (date.getMonth() + 101)).substr(1), "dd": ("" + (date.getDate() + 100)).substr(1), "HH": ("" + (date.getHours() + 100)).substr(1), "mm": ("" + (date.getMinutes() + 100)).substr(1), "ss": ("" + (date.getSeconds() + 100)).substr(1) }; return format.replace(/(yyyy|MM?|dd?|HH?|ss?|mm?)/g, function() { return dict[arguments[0]]; }); }, // 获取节点数据,支持多层级访问 getItemField: function (item, field) { var value = item; if (typeof field !== 'string' || item.hasOwnProperty(field)) { return item[field]; } var props = field.split('.'); for (var p in props) { value = value && value[props[p]]; } return value; }, // 指定随机数返回 random: function (min, max) { return Math.floor((Math.random() * max) + min); }, // 判断字符串是否是以start开头 startWith: function(value, start) { var reg = new RegExp("^" + start); return reg.test(value) }, // 判断字符串是否是以end结尾 endWith: function(value, end) { var reg = new RegExp(end + "$"); return reg.test(value) }, // 数组去重 uniqueFn: function(array) { var result = []; var hashObj = {}; for (var i = 0; i < array.length; i++) { if (!hashObj[array[i]]) { hashObj[array[i]] = true; result.push(array[i]); } } return result; }, // 数组中的所有元素放入一个字符串 join: function(array, separator) { if ($.common.isEmpty(array)) { return null; } return array.join(separator); }, // 获取form下所有的字段并转换为json对象 formToJSON: function(formId) { var json = {}; $.each($("#" + formId).serializeArray(), function(i, field) { if (json[field.name]) { json[field.name] += ("," + field.value); } else { json[field.name] = field.value; } }); return json; }, // 数据字典转下拉框 dictToSelect: function(datas, value, name) { var actions = []; actions.push($.common.sprintf("'); return actions.join(''); }, // 获取obj对象长度 getLength: function(obj) { var count = 0;   for (var i in obj) { if (obj.hasOwnProperty(i)) { count++; }   } return count; }, // 判断移动端 isMobile: function () { return navigator.userAgent.match(/(Android|iPhone|SymbianOS|Windows Phone|iPad|iPod)/i); }, // 数字正则表达式,只能为0-9数字 numValid : function(text){ var patten = new RegExp(/^[0-9]+$/); return patten.test(text); }, // 英文正则表达式,只能为a-z和A-Z字母 enValid : function(text){ var patten = new RegExp(/^[a-zA-Z]+$/); return patten.test(text); }, // 英文、数字正则表达式,必须包含(字母,数字) enNumValid : function(text){ var patten = new RegExp(/^(?=.*[a-zA-Z]+)(?=.*[0-9]+)[a-zA-Z0-9]+$/); return patten.test(text); }, // 英文、数字、特殊字符正则表达式,必须包含(字母,数字,特殊字符!@#$%^&*()-=_+) charValid : function(text){ var patten = new RegExp(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[~!@#\$%\^&\*\(\)\-=_\+])[A-Za-z\d~!@#\$%\^&\*\(\)\-=_\+]{6,}$/); return patten.test(text); }, } }); })(jQuery); /** 表格类型 */ table_type = { bootstrapTable: 0, bootstrapTreeTable: 1 }; /** 消息状态码 */ web_status = { SUCCESS: 0, FAIL: 500, WARNING: 301 }; /** 弹窗状态码 */ modal_status = { SUCCESS: "success", FAIL: "error", WARNING: "warning" }; ================================================ FILE: ruoyi-admin/src/main/resources/static/ruoyi/login.js ================================================ $(function() { validateKickout(); validateRule(); $('.imgcode').click(function() { var url = ctx + "captcha/captchaImage?type=" + captchaType + "&s=" + Math.random(); $(".imgcode").attr("src", url); }); }); function login() { var username = $.common.trim($("input[name='username']").val()); var password = $.common.trim($("input[name='password']").val()); var validateCode = $("input[name='validateCode']").val(); var rememberMe = $("input[name='rememberme']").is(':checked'); if($.common.isEmpty(validateCode) && captchaEnabled) { $.modal.msg("请输入验证码"); return false; } $.ajax({ type: "post", url: ctx + "login", data: { "username": username, "password": password, "validateCode": validateCode, "rememberMe": rememberMe }, beforeSend: function () { $.modal.loading($("#btnSubmit").data("loading")); }, success: function(r) { if (r.code == web_status.SUCCESS) { location.href = ctx + 'index'; } else { $('.imgcode').click(); $(".code").val(""); $.modal.msg(r.msg); } $.modal.closeLoading(); } }); } function validateRule() { var icon = " "; $("#signupForm").validate({ rules: { username: { required: true }, password: { required: true } }, messages: { username: { required: icon + "请输入您的用户名", }, password: { required: icon + "请输入您的密码", } }, submitHandler: function(form) { login(); } }) } function validateKickout() { if (getParam("kickout") == 1) { layer.alert("您已在别处登录,请您修改密码或重新登录", { icon: 0, title: "系统提示" }, function(index) { //关闭弹窗 layer.close(index); if (top != self) { top.location = self.location; } else { var url = location.search; if (url) { var oldUrl = window.location.href; var newUrl = oldUrl.substring(0, oldUrl.indexOf('?')); self.location = newUrl; } } }); } } function getParam(paramName) { var reg = new RegExp("(^|&)" + paramName + "=([^&]*)(&|$)"); var r = window.location.search.substr(1).match(reg); if (r != null) return decodeURI(r[2]); return null; } ================================================ FILE: ruoyi-admin/src/main/resources/static/ruoyi/register.js ================================================ $(function() { validateRule(); $('.imgcode').click(function() { var url = ctx + "captcha/captchaImage?type=" + captchaType + "&s=" + Math.random(); $(".imgcode").attr("src", url); }); }); function register() { var username = $.common.trim($("input[name='username']").val()); var password = $.common.trim($("input[name='password']").val()); var validateCode = $("input[name='validateCode']").val(); if($.common.isEmpty(validateCode) && captchaEnabled) { $.modal.msg("请输入验证码"); return false; } $.ajax({ type: "post", url: ctx + "register", data: { "loginName": username, "password": password, "validateCode": validateCode }, beforeSend: function () { $.modal.loading($("#btnSubmit").data("loading")); }, success: function(r) { if (r.code == web_status.SUCCESS) { layer.alert("恭喜你,您的账号 " + username + " 注册成功!", { icon: 1, title: "系统提示" }, function(index) { //关闭弹窗 layer.close(index); location.href = ctx + 'login'; }); } else { $.modal.closeLoading(); $('.imgcode').click(); $(".code").val(""); $.modal.msg(r.msg); } } }); } function validateRule() { var icon = " "; $("#registerForm").validate({ rules: { username: { required: true, minlength: 2 }, password: { required: true, minlength: 5, specialSign: true }, confirmPassword: { required: true, equalTo: "[name='password']" } }, messages: { username: { required: icon + "请输入您的用户名", minlength: icon + "用户名不能小于2个字符" }, password: { required: icon + "请输入您的密码", minlength: icon + "密码不能小于5个字符", }, confirmPassword: { required: icon + "请再次输入您的密码", equalTo: icon + "两次密码输入不一致" } }, submitHandler: function(form) { register(); } }) } ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/autocomplete.html ================================================
                    搜索自动补全https://github.com/lzwme/bootstrap-suggest-plugin

                    展示下拉菜单按钮。

                    不展示下拉菜单按钮。

                    前端json中获取数据

                    百度搜索

                    支持逗号分隔多关键字

                    淘宝搜索

                    支持逗号分隔多关键字


                    搜索自动补全https://github.com/bassjobsen/Bootstrap-3-Typeahead

                    通过数据属性的基本示例。


                    通过javascript的基本示例。


                    通过javascript的复杂示例。


                    后台url中获取简单数据


                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/basic.html ================================================
                    基本表单 简单登录表单示例

                    登录

                    欢迎登录本站(⊙o⊙)

                    还不是会员?

                    您可以注册一个新账户

                    横向表单

                    欢迎登录本站(⊙o⊙)

                    请输入您注册时所填的E-mail
                    内联表单
                    弹出表单 弹出框登录示例
                    所有表单元素 包括自定义样式的复选和单选按钮
                    帮助文本,可能会超过一行,以块级元素显示

                    ruoyi.vip

                    @
                    .00
                    ¥ .00
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/button.html ================================================
                    按钮颜色

                    可使用class来快速改变按钮的颜色,如.btn-primary

                    普通按钮

                    按钮大小

                    可以通过添加class的值为.btn-lg, .btn-sm, or .btn-xs来修改按钮的大小

                    按钮尺寸




                    线性按钮

                    要使用线性按钮,可添加class.btn-block.btn-outline

                    线性按钮

                    块级按钮

                    可以通过添加.dimclass来使用3D按钮.

                    3D按钮

                    下拉按钮

                    下拉按钮可使用任何颜色任何大小

                    下拉按钮



                    按钮组



                    图标按钮

                    任何按钮都可以在左侧或右侧添加图标

                    图标按钮

                    分享到微信 使用QQ账号登录 收藏

                    按钮切换

                    圆形图标按钮

                    要使用圆形图标按钮,可以通过添加class为.btn-circle实现

                    圆形按钮




                    圆角按钮

                    可以通过添加class的值微.btn-rounded来实现圆角按钮

                    按钮组

                    默认 主要 成果 信息 警告 危险 危险

                    圆角块级带图标按钮

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/cards.html ================================================
                    NEW
                    IT-01 - 设计部

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    48%
                    项目
                    12
                    周期
                    4个月
                    预算
                    ¥200,913
                    IT-04 - 市场部

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    32%
                    项目
                    24
                    周期
                    3个月
                    预算
                    ¥190,325
                    IT-07 - 财务部

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    73%
                    项目
                    11
                    周期
                    6个月
                    预算
                    ¥560,105
                    IT-02 - 开发部

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    61%
                    项目
                    43
                    周期
                    1个月
                    预算
                    ¥705,913
                    截止
                    IT-05 - 管理层

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    14%
                    项目
                    8
                    周期
                    7个月
                    预算
                    ¥40,200
                    IT-08 - 销售部

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    25%
                    项目
                    25
                    周期
                    4个月
                    预算
                    ¥140,105
                    IT-02 - 销售部

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    82%
                    项目
                    68
                    周期
                    2个月
                    预算
                    ¥701,400
                    IT-06 - 销售部

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    26%
                    项目
                    16
                    周期
                    8个月
                    预算
                    ¥160,100
                    IT-09 - 销售部

                    部门简介

                    平面设计(graphic design),也称为视觉传达设计,是以“视觉”作为沟通和表现的方式,透过多种方式来创造和结合符号、图片和文字,借此作出用来传达想法或讯息的视觉表现。

                    当前项目进度:
                    18%
                    项目
                    53
                    周期
                    9个月
                    预算
                    ¥60,140
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/cxselect.html ================================================
                    多级联动下拉https://github.com/ciaoca/cxSelect

                    简单联动示例。


                    国内省市区联动。


                    自定义选项。


                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/datetime.html ================================================
                    日期选择器 https://github.com/smalot/bootstrap-datetimepicker

                    日期选择器 https://github.com/sentsin/laydate

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/duallistbox.html ================================================
                    双重列表框 https://github.com/istvan-ujjmeszaros/bootstrap-duallistbox

                    Bootstrap Dual Listbox是针对Twitter Bootstrap进行了优化的响应式双列表框。它适用于所有现代浏览器和触摸设备。


                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/grid.html ================================================
                    栅格设置

                    通过下表可以详细查看 Bootstrap 的栅格系统是如何在多种屏幕设备上工作的。

                    超小屏幕 手机 (<768px) 小屏幕 平板 (≥768px) 中等屏幕 桌面显示器 (≥992px) 大屏幕 大桌面显示器 (≥1200px)
                    栅格系统行为 总是水平排列 开始是堆叠在一起的,当大于这些阈值时将变为水平排列C
                    .container 最大宽度 None (自动) 750px 970px 1170px
                    类前缀 .col-xs- .col-sm- .col-md- .col-lg-
                    列(column)数 12
                    最大列(column)宽 自动 ~62px ~81px ~97px
                    槽(gutter)宽 30px (每列左右均有 15px)
                    可嵌套
                    偏移(Offsets)
                    列排序
                    从堆叠到水平排列

                    使用单一的一组 .col-md-* 栅格类,就可以创建一个基本的栅格系统,在手机和平板设备上一开始是堆叠在一起的(超小屏幕到小屏幕这一范围),在桌面(中等)屏幕设备上变为水平排列。所有“列(column)必须放在 ” .row 内。

                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-1
                    .col-md-8
                    .col-md-4
                    .col-md-4
                    .col-md-4
                    .col-md-4
                    .col-md-6
                    .col-md-6
                    移动设备和桌面屏幕

                    是否不希望在小屏幕设备上所有列都堆叠在一起?那就使用针对超小屏幕和中等屏幕设备所定义的类吧,即 .col-xs-*.col-md-*。请看下面的实例,研究一下这些是如何工作的。

                    .col-xs-12 .col-md-8
                    .col-xs-6 .col-md-4
                    .col-xs-6 .col-md-4
                    .col-xs-6 .col-md-4
                    .col-xs-6 .col-md-4
                    .col-xs-6
                    .col-xs-6
                    手机、平板、桌面

                    在上面案例的基础上,通过使用针对平板设备的 .col-sm-* 类,我们来创建更加动态和强大的布局吧。

                    .col-xs-12 .col-sm-6 .col-md-8
                    .col-xs-6 .col-md-4
                    .col-xs-6 .col-sm-4
                    .col-xs-6 .col-sm-4
                    .col-xs-6 .col-sm-4
                    多余的列(column)将另起一行排列

                    在等宽的4网格中,网格不等高会碰到问题,为了解决这个问题,可使用.clearfix响应实用工具类

                    .col-xs-6 .col-sm-3
                    调整窗口大小或者在手机上查看本示例
                    .col-xs-6 .col-sm-3
                    .col-xs-6 .col-sm-3
                    .col-xs-6 .col-sm-3

                    使用 .col-md-offset-* 类可以将列向右侧偏移。这些类实际是通过使用 * 选择器为当前元素增加了左侧的边距(margin)。例如,.col-md-offset-4 类将 .col-md-4 元素向右侧偏移了4个列(column)的宽度。

                    .col-md-4
                    .col-md-4 .col-md-offset-4
                    .col-md-3 .col-md-offset-3
                    .col-md-3 .col-md-offset-3
                    .col-md-6 .col-md-offset-3

                    为了使用内置的栅格系统将内容再次嵌套,可以通过添加一个新的 .row 元素和一系列 .col-sm-* 元素到已经存在的 .col-sm-* 元素内。被嵌套的行(row)所包含的列(column)的个数不能超过12(其实,没有要求你必须占满12列)。

                    第一级: .col-md-9
                    第二级: .col-md-6
                    第二级: .col-md-6

                    通过使用 .col-md-push-*.col-md-pull-* 类就可以很容易的改变列(column)的顺序。

                    .col-md-9 .col-md-push-3
                    .col-md-3 .col-md-pull-9
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/invoice.html ================================================
                    北京百度在线网络技术有限公司
                    北京市海淀区上地十街10号
                    总机: (+86 10) 5992 8888

                    单据编号:

                    H+-000567F7-00

                    阿里巴巴集团
                    中国杭州市华星路99号东部软件园创业大厦6层(310099)
                    总机: (86) 571-8502-2088

                    日期: 2014-11-11

                    清单 数量 单价 税率 总价
                    尚都比拉2013冬装新款女装 韩版修身呢子大衣 秋冬气质羊毛呢外套
                    1 ¥26.00 ¥1.20 ¥31,98
                    11*11夏娜 新款斗篷毛呢外套 女秋冬呢子大衣 韩版大码宽松呢大衣
                    双十一特价
                    2 ¥80.00 ¥1.20 ¥196.80
                    2013秋装 新款女装韩版学生秋冬加厚加绒保暖开衫卫衣 百搭女外套
                    3 ¥420.00 ¥1.20 ¥1033.20
                    总价: ¥1026.00
                    税: ¥235.98
                    总计 ¥1261.98
                    注意: 请在30日内完成付款,否则订单会自动取消。
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/jasny.html ================================================
                    文件上传控件 https://github.com/jasny/bootstrap
                    选择文件更改 清除

                    选择文件更改 ×

                    选择图片更改 清除

                    选择图片更改 清除

                    固定格式文本 https://github.com/jasny/bootstrap
                    158-8888-88888
                    0730-8888888
                    yyyy-mm-dd
                    192.168.100.200
                    99-9999999

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/labels_tips.html ================================================
                    徽章 (Badges)

                    要添加徽章,只需要在元素上添加.badge即可,改变徽章的颜色可使用如下class,如.badge-primary

                    badge-primary

                    badge-info

                    badge-success

                    badge-warning

                    badge-danger

                    标签 (Labels)

                    要添加徽章,只需要在元素上添加class.label即可,如果需要修改颜色,添加如下class,如.label-primary

                    label-primary

                    label-info

                    label-success

                    label-warning

                    label-danger

                    通知样式
                    RuoYi是一个很棒的后台UI框架 了解更多.
                    RuoYi是一个很棒的后台UI框架 了解更多.
                    RuoYi是一个很棒的后台UI框架 了解更多.
                    RuoYi是一个很棒的后台UI框架 了解更多.
                    带关闭按钮的通知样式
                    RuoYi是一个很棒的后台UI框架 了解更多.
                    RuoYi是一个很棒的后台UI框架 了解更多.
                    RuoYi是一个很棒的后台UI框架 了解更多.
                    RuoYi是一个很棒的后台UI框架 了解更多.
                    工具提示

                    工具提示示例 深色背景


                    工具提示 - 单击提示

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/localrefresh.html ================================================

                    任务列表

                    点击刷新按钮刷新数据到列表中

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/progress_bars.html ================================================
                    进度条 (Progress Bars)
                    基本
                    35% Complete (success)
                    43% Complete (success)
                    条纹效果
                    50% Complete (success)
                    动画效果
                    75% Complete (success)
                    堆叠效果
                    30% Complete (success)
                    20% Complete (warning)
                    40% Complete (danger)
                    带有提示标签的进度条
                    95%
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/select.html ================================================
                    下拉框 https://github.com/select2/select2

                    下拉框 https://github.com/snapappointments/bootstrap-select

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/sortable.html ================================================

                    任务列表

                    在列表之间拖动任务面板

                    • 加强过程管理,及时统计教育经费使用情况,做到底码清楚,
                      标签 2018.09.01
                    • 支持财会人员的继续培训工作。
                      标记 2018.05.12
                    • 协同教导处搞好助学金、减免教科书费的工作。
                      标记 2018.09.10
                    • 要求会计、出纳人员严格执行财务制度,遵守岗位职责,按时上报各种资料。
                      确定 2018.06.10
                    • 做好职工公费医疗工作,按时发放门诊费。
                      标签 2018.09.09
                    • 有计划地把课本复习三至五遍。
                      确定 2018.08.04
                    • 看一本高质量的高中语法书
                      标记 2018.05.12
                    • 选择一份较好的英语报纸,通过阅读提高英语学习效果。
                      标记 2018.09.10

                    进行中

                    在列表之间拖动任务面板

                    • 全面、较深入地掌握我们“产品”的功能、特色和优势并做到应用自如。
                      标签 2018.09.01
                    • 根据自己以前所了解的和从其他途径搜索到的信息,录入客户资料150家。
                      标记 2018.05.12
                    • 锁定有意向客户20家。
                      标记 2018.09.10
                    • 力争完成销售指标。
                      标签 2018.09.09
                    • 在总结和摸索中前进。
                      确定 2018.08.04
                    • 不断学习行业知识、产品知识,为客户带来实用介绍内容
                      标记 2018.05.12
                    • 先友后单:与客户发展良好友谊,转换销售员角色,处处为客户着想
                      标记 2018.11.04

                    已完成

                    在列表之间拖动任务面板

                    • 制定工作日程表
                      标记 2018.09.10
                    • 每天坚持打40个有效电话,挖掘潜在客户
                      标签 2018.09.09
                    • 拜访客户之前要对该客户做全面的了解(客户的潜在需求、职位、权限以及个人性格和爱好)
                      标签 2018.09.09
                    • 提高自己电话营销技巧,灵活专业地与客户进行电话交流
                      确定 2018.08.04
                    • 通过电话销售过程中了解各盛市的设备仪器使用、采购情况及相关重要追踪人
                      标记 2018.05.12
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/summernote.html ================================================
                    Summernote 富文本编辑器

                    若依后台管理系统

                    ruoyi是一个完全响应式,基于Bootstrap3.4.1最新版本开发的扁平化主题,她采用了主流的左右两栏式布局,使用了Html5+CSS3等现代技术,她提供了诸多的强大的可以重新组合的UI组件,并集成了最新的jQuery版本(v2.1.1),当然,也集成了很多功能强大,用途广泛的就jQuery插件,她可以用于所有的Web应用程序,如网站管理后台网站会员中心CMSCRMOA等等,当然,您也可以对她进行深度定制,以做出更强系统。

                    当前版本:v4.8.2

                    免费开源

                    Summernote

                    Summernote是一个简单的基于Bootstrap的WYSIWYG富文本编辑器

                    官方文档请参考: https://github.com/summernote/summernote
                    编辑/保存为html代码示例

                    你好,若依

                    H+是一个完全响应式,基于Bootstrap3.4.1最新版本开发的扁平化主题,她采用了主流的左右两栏式布局,使用了Html5+CSS3等现代技术,她提供了诸多的强大的可以重新组合的UI组件,并集成了最新的jQuery版本(v2.1.1),当然,也集成了很多功能强大,用途广泛的就jQuery插件,她可以用于所有的Web应用程序,如网站管理后台网站会员中心CMSCRMOA等等,当然,您也可以对她进行深度定制,以做出更强系统。

                    当前版本:v4.8.2

                    开源免费

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/tabs_panels.html ================================================
                    基本面板 这是一个自定义面板

                    Bootstrap

                    简洁、直观、强悍的前端开发框架,让web开发更迅速、简单。

                    HTML5 文档类型

                    Bootstrap 使用到的某些 HTML 元素和 CSS 属性需要将页面设置为 HTML5 文档类型。在你项目中的每个页面都要参照下面的格式进行设置。

                    移动设备优先

                    在 Bootstrap 2 中,我们对框架中的某些关键部分增加了对移动设备友好的样式。而在 Bootstrap 3 中,我们重写了整个框架,使其一开始就是对移动设备友好的。这次不是简单的增加一些可选的针对移动设备的样式,而是直接融合进了框架的内核中。也就是说,Bootstrap 是移动设备优先的。针对移动设备的样式融合进了框架的每个角落,而不是增加一个额外的文件。

                    图标选项卡

                    排版与链接

                    Bootstrap 排版、链接样式设置了基本的全局样式。分别是: 为 body 元素设置 background-color: #fff; 使用 @font-family-base、@font-size-base 和 @line-height-base a变量作为排版的基本参数 为所有链接设置了基本颜色 @link-color ,并且当链接处于 :hover 状态时才添加下划线 这些样式都能在 scaffolding.less 文件中找到对应的源码。

                    Normalize.css

                    为了增强跨浏览器表现的一致性,我们使用了 Normalize.css,这是由 Nicolas Gallagher 和 Jonathan Neal 维护的一个CSS 重置样式库。

                    布局容器

                    Bootstrap 需要为页面内容和栅格系统包裹一个 .container 容器。我们提供了两个作此用处的类。注意,由于 padding 等属性的原因,这两种 容器类不能互相嵌套。

                    栅格系统

                    Bootstrap 提供了一套响应式、移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列。它包含了易于使用的预定义类,还有强大的mixin 用于生成更具语义的布局。

                    排版与链接

                    Bootstrap 排版、链接样式设置了基本的全局样式。分别是: 为 body 元素设置 background-color: #fff; 使用 @font-family-base、@font-size-base 和 @line-height-base a变量作为排版的基本参数 为所有链接设置了基本颜色 @link-color ,并且当链接处于 :hover 状态时才添加下划线 这些样式都能在 scaffolding.less 文件中找到对应的源码。

                    栅格系统

                    Bootstrap 提供了一套响应式、移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列。它包含了易于使用的预定义类,还有强大的mixin 用于生成更具语义的布局。

                    排版与链接

                    Bootstrap 排版、链接样式设置了基本的全局样式。分别是: 为 body 元素设置 background-color: #fff; 使用 @font-family-base、@font-size-base 和 @line-height-base a变量作为排版的基本参数 为所有链接设置了基本颜色 @link-color ,并且当链接处于 :hover 状态时才添加下划线 这些样式都能在 scaffolding.less 文件中找到对应的源码。

                    栅格系统

                    Bootstrap 提供了一套响应式、移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列。它包含了易于使用的预定义类,还有强大的mixin 用于生成更具语义的布局。

                    Bootstrap面板 自定义背景
                    默认面板

                    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

                    主要

                    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

                    成功

                    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

                    信息

                    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

                    警告

                    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

                    危险

                    通过 .panel-heading 可以很简单地为面板加入一个标题容器。你也可以通过添加设置了 .panel-title 类的标签,添加一个预定义样式的标题。 为了给链接设置合适的颜色,务必将链接放到带有 .panel-title 类的标题标签内。

                    折叠面板
                    Bootstrap相关优质项目推荐 这些项目或者是对Bootstrap进行了有益的补充,或者是基于Bootstrap开发的
                    Bootstrap相关优质项目推荐 这些项目或者是对Bootstrap进行了有益的补充,或者是基于Bootstrap开发的
                    Bootstrap相关优质项目推荐 这些项目或者是对Bootstrap进行了有益的补充,或者是基于Bootstrap开发的

                    超大屏幕

                    Bootstrap 支持的另一个特性,超大屏幕(Jumbotron)。顾名思义该组件可以增加标题的大小,并为登录页面内容添加更多的外边距(margin)。使用超大屏幕(Jumbotron)的步骤如下:

                    1. 创建一个带有 class .jumbotron. 的容器
                    2. 除了更大的 <h1>,字体粗细 font-weight 被减为 200px。

                    了解更多

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/timeline.html ================================================ 时间轴
                    打开/关闭颜色/背景或方向版本: 轻型版本 黑色版本

                    会议

                    上一年的销售业绩发布会。总结产品营销和销售趋势及销售的现状。

                    更多信息 今天
                    2月3日

                    给张三发送文档

                    发送上年度《销售业绩报告》

                    下载文档 今天
                    2月3日

                    喝咖啡休息

                    喝咖啡啦,啦啦啦~~

                    更多 昨天
                    2月2日

                    给李四打电话

                    给李四打电话分配本月工作任务

                    昨天
                    2月2日

                    公司年会

                    发年终奖啦,啦啦啦~~

                    前天
                    2月1日
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/upload.html ================================================
                    文件上传控件 https://github.com/kartik-v/bootstrap-fileinput

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/validate.html ================================================
                    jQuery Validate 简介

                    jquery.validate.js 是一款优秀的jQuery表单验证插件。它具有如下特点:

                    • 安装简单
                    • 内置超过20种数据验证方法
                    • 直列错误提示信息
                    • 可扩展的数据验证方法
                    • 使用内置的元数据或插件选项来指定您的验证规则
                    • 优雅的交互设计

                    官网:http://jqueryvalidation.org/

                    简单示例

                    更多示例请访问官方示例页面:查看

                    中文API可参考:http://doc.ruoyi.vip/ruoyi/document/zjwd.html#jquery-validate

                    完整验证表单
                    这里写点提示的内容
                    请再次输入您的密码
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/form/wizard.html ================================================
                    表单向导 https://github.com/techlab/jquery-smartwizard
                    这里写点提示的内容
                    请再次输入您的密码

                    1、如果不需要工具栏固定在页面底部, 将style中下面的部分取消注释

                    .sw>.toolbar-bottom

                    2、如果设置了自动调节高度(autoAdjustHeight)为true, 将style中下面的部分取消注释

                    .sw>.tab-content

                    3、工具栏的按钮样式会被表单插件中.btn样式覆盖导致bootstrap中的按钮样式无效, 如果需要改变按钮样式可以自己定义并提高优先级

                    测试多行显示

                    $('#smartwizard').smartWizard({
                      selected: 0, // Initial selected step, 0 = first step
                      theme: 'default', // theme for the wizard, related css need to include for other than default theme
                      justified: true, // Nav menu justification. true/false
                      darkMode:false, // Enable/disable Dark Mode if the theme supports. true/false
                      autoAdjustHeight: true, // Automatically adjust content height
                      cycleSteps: false, // Allows to cycle the navigation of steps
                      backButtonSupport: true, // Enable the back button support
                      enableURLhash: true, // Enable selection of the step based on url hash
                      transition: {
                          animation: 'none', // Effect on navigation, none/fade/slide-horizontal/slide-vertical/slide-swing
                          speed: '400', // Transition animation speed
                          easing:'' // Transition animation easing. Not supported without a jQuery easing plugin
                      },
                      toolbarSettings: {
                          toolbarPosition: 'bottom', // none, top, bottom, both
                          toolbarButtonPosition: 'right', // left, right, center
                          showNextButton: true, // show/hide a Next button
                          showPreviousButton: true, // show/hide a Previous button
                          toolbarExtraButtons: [] // Extra buttons to show on toolbar, array of jQuery input/buttons elements
                      },
                      anchorSettings: {
                          anchorClickable: true, // Enable/Disable anchor navigation
                          enableAllAnchors: false, // Activates all anchors clickable all times
                          markDoneStep: true, // Add done state on navigation
                          markAllPreviousStepsAsDone: true, // When a step selected by url hash, all previous steps are marked done
                          removeDoneStepOnNavigateBack: false, // While navigate back done step after active step will be cleared
                          enableAnchorOnDoneStep: true // Enable/Disable the done steps navigation
                      },
                      keyboardSettings: {
                          keyNavigation: true, // Enable/Disable keyboard navigation(left and right keys are used if enabled)
                          keyLeft: [37], // Left key code
                          keyRight: [39] // Right key code
                      },
                      lang: { // Language variables for button
                          next: 'Next',
                          previous: 'Previous'
                      },
                      disabledSteps: [], // Array Steps disabled
                      errorSteps: [], // Highlight step with errors
                      hiddenSteps: [] // Hidden steps
                    });
                    										
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/icon/fontawesome.html ================================================
                    所有图标 所有图标集合 - Font Awesome
                    address-book
                    address-book-o
                    address-card
                    address-card-o
                    adjust
                    american-sign-language-interpreting
                    anchor
                    archive
                    area-chart
                    arrows
                    arrows-h
                    arrows-v
                    asl-interpreting (alias)
                    assistive-listening-systems
                    asterisk
                    at
                    audio-description
                    automobile (alias)
                    balance-scale
                    ban
                    bank (alias)
                    bar-chart
                    bar-chart-o (alias)
                    barcode
                    bars
                    bath
                    bathtub (alias)
                    battery (alias)
                    battery-0 (alias)
                    battery-1 (alias)
                    battery-2 (alias)
                    battery-3 (alias)
                    battery-4 (alias)
                    battery-empty
                    battery-full
                    battery-half
                    battery-quarter
                    battery-three-quarters
                    bed
                    beer
                    bell
                    bell-o
                    bell-slash
                    bell-slash-o
                    bicycle
                    binoculars
                    birthday-cake
                    blind
                    bluetooth
                    bluetooth-b
                    bolt
                    bomb
                    book
                    bookmark
                    bookmark-o
                    braille
                    briefcase
                    bug
                    building
                    building-o
                    bullhorn
                    bullseye
                    bus
                    cab (alias)
                    calculator
                    calendar
                    calendar-check-o
                    calendar-minus-o
                    calendar-o
                    calendar-plus-o
                    calendar-times-o
                    camera
                    camera-retro
                    car
                    caret-square-o-down
                    caret-square-o-left
                    caret-square-o-right
                    caret-square-o-up
                    cart-arrow-down
                    cart-plus
                    cc
                    certificate
                    check
                    check-circle
                    check-circle-o
                    check-square
                    check-square-o
                    child
                    circle
                    circle-o
                    circle-o-notch
                    circle-thin
                    clock-o
                    clone
                    close (alias)
                    cloud
                    cloud-download
                    cloud-upload
                    code
                    code-fork
                    coffee
                    cog
                    cogs
                    comment
                    comment-o
                    commenting
                    commenting-o
                    comments
                    comments-o
                    compass
                    copyright
                    creative-commons
                    credit-card
                    credit-card-alt
                    crop
                    crosshairs
                    cube
                    cubes
                    cutlery
                    dashboard (alias)
                    database
                    deaf
                    deafness (alias)
                    desktop
                    diamond
                    dot-circle-o
                    download
                    drivers-license (alias)
                    drivers-license-o (alias)
                    edit (alias)
                    ellipsis-h
                    ellipsis-v
                    envelope
                    envelope-o
                    envelope-open
                    envelope-open-o
                    envelope-square
                    eraser
                    exchange
                    exclamation
                    exclamation-circle
                    exclamation-triangle
                    external-link
                    external-link-square
                    eye
                    eye-slash
                    eyedropper
                    fax
                    feed (alias)
                    female
                    fighter-jet
                    file-archive-o
                    file-audio-o
                    file-code-o
                    file-excel-o
                    file-image-o
                    file-movie-o (alias)
                    file-pdf-o
                    file-photo-o (alias)
                    file-picture-o (alias)
                    file-powerpoint-o
                    file-sound-o (alias)
                    file-video-o
                    file-word-o
                    file-zip-o (alias)
                    film
                    filter
                    fire
                    fire-extinguisher
                    flag
                    flag-checkered
                    flag-o
                    flash (alias)
                    flask
                    folder
                    folder-o
                    folder-open
                    folder-open-o
                    frown-o
                    futbol-o
                    gamepad
                    gavel
                    gear (alias)
                    gears (alias)
                    gift
                    glass
                    globe
                    graduation-cap
                    group (alias)
                    hand-grab-o (alias)
                    hand-lizard-o
                    hand-paper-o
                    hand-peace-o
                    hand-pointer-o
                    hand-rock-o
                    hand-scissors-o
                    hand-spock-o
                    hand-stop-o (alias)
                    handshake-o
                    hard-of-hearing (alias)
                    hashtag
                    hdd-o
                    headphones
                    heart
                    heart-o
                    heartbeat
                    history
                    home
                    hotel (alias)
                    hourglass
                    hourglass-1 (alias)
                    hourglass-2 (alias)
                    hourglass-3 (alias)
                    hourglass-end
                    hourglass-half
                    hourglass-o
                    hourglass-start
                    i-cursor
                    id-badge
                    id-card
                    id-card-o
                    image (alias)
                    inbox
                    industry
                    info
                    info-circle
                    institution (alias)
                    key
                    keyboard-o
                    language
                    laptop
                    leaf
                    legal (alias)
                    lemon-o
                    level-down
                    level-up
                    life-bouy (alias)
                    life-buoy (alias)
                    life-ring
                    life-saver (alias)
                    lightbulb-o
                    line-chart
                    location-arrow
                    lock
                    low-vision
                    magic
                    magnet
                    mail-forward (alias)
                    mail-reply (alias)
                    mail-reply-all (alias)
                    male
                    map
                    map-marker
                    map-o
                    map-pin
                    map-signs
                    meh-o
                    microchip
                    microphone
                    microphone-slash
                    minus
                    minus-circle
                    minus-square
                    minus-square-o
                    mobile
                    mobile-phone (alias)
                    money
                    moon-o
                    mortar-board (alias)
                    motorcycle
                    mouse-pointer
                    music
                    navicon (alias)
                    newspaper-o
                    object-group
                    object-ungroup
                    paint-brush
                    paper-plane
                    paper-plane-o
                    paw
                    pencil
                    pencil-square
                    pencil-square-o
                    percent
                    phone
                    phone-square
                    photo (alias)
                    picture-o
                    pie-chart
                    plane
                    plug
                    plus
                    plus-circle
                    plus-square
                    plus-square-o
                    podcast
                    power-off
                    print
                    puzzle-piece
                    qrcode
                    question
                    question-circle
                    question-circle-o
                    quote-left
                    quote-right
                    random
                    recycle
                    refresh
                    registered
                    remove (alias)
                    reorder (alias)
                    reply
                    reply-all
                    retweet
                    road
                    rocket
                    rss
                    rss-square
                    s15 (alias)
                    search
                    search-minus
                    search-plus
                    send (alias)
                    send-o (alias)
                    server
                    share
                    share-alt
                    share-alt-square
                    share-square
                    share-square-o
                    shield
                    ship
                    shopping-bag
                    shopping-basket
                    shopping-cart
                    shower
                    sign-in
                    sign-language
                    sign-out
                    signal
                    signing (alias)
                    sitemap
                    sliders
                    smile-o
                    snowflake-o
                    soccer-ball-o (alias)
                    sort
                    sort-alpha-asc
                    sort-alpha-desc
                    sort-amount-asc
                    sort-amount-desc
                    sort-asc
                    sort-desc
                    sort-down (alias)
                    sort-numeric-asc
                    sort-numeric-desc
                    sort-up (alias)
                    space-shuttle
                    spinner
                    spoon
                    square
                    square-o
                    star
                    star-half
                    star-half-empty (alias)
                    star-half-full (alias)
                    star-half-o
                    star-o
                    sticky-note
                    sticky-note-o
                    street-view
                    suitcase
                    sun-o
                    support (alias)
                    tablet
                    tachometer
                    tag
                    tags
                    tasks
                    taxi
                    television
                    terminal
                    thermometer (alias)
                    thermometer-0 (alias)
                    thermometer-1 (alias)
                    thermometer-2 (alias)
                    thermometer-3 (alias)
                    thermometer-4 (alias)
                    thermometer-empty
                    thermometer-full
                    thermometer-half
                    thermometer-quarter
                    thermometer-three-quarters
                    thumb-tack
                    thumbs-down
                    thumbs-o-down
                    thumbs-o-up
                    thumbs-up
                    ticket
                    times
                    times-circle
                    times-circle-o
                    times-rectangle (alias)
                    times-rectangle-o (alias)
                    tint
                    toggle-down (alias)
                    toggle-left (alias)
                    toggle-off
                    toggle-on
                    toggle-right (alias)
                    toggle-up (alias)
                    trademark
                    trash
                    trash-o
                    tree
                    trophy
                    truck
                    tty
                    tv (alias)
                    umbrella
                    universal-access
                    university
                    unlock
                    unlock-alt
                    unsorted (alias)
                    upload
                    user
                    user-circle
                    user-circle-o
                    user-o
                    user-plus
                    user-secret
                    user-times
                    users
                    vcard (alias)
                    vcard-o (alias)
                    video-camera
                    volume-control-phone
                    volume-down
                    volume-off
                    volume-up
                    warning (alias)
                    wheelchair
                    wheelchair-alt
                    wifi
                    window-close
                    window-close-o
                    window-maximize
                    window-minimize
                    window-restore
                    wrench
                    american-sign-language-interpreting
                    asl-interpreting (alias)
                    assistive-listening-systems
                    audio-description
                    blind
                    braille
                    cc
                    deaf
                    deafness (alias)
                    hard-of-hearing (alias)
                    low-vision
                    question-circle-o
                    sign-language
                    signing (alias)
                    tty
                    universal-access
                    volume-control-phone
                    wheelchair
                    wheelchair-alt
                    hand-grab-o (alias)
                    hand-lizard-o
                    hand-o-down
                    hand-o-left
                    hand-o-right
                    hand-o-up
                    hand-paper-o
                    hand-peace-o
                    hand-pointer-o
                    hand-rock-o
                    hand-scissors-o
                    hand-spock-o
                    hand-stop-o (alias)
                    thumbs-down
                    thumbs-o-down
                    thumbs-o-up
                    thumbs-up
                    ambulance
                    automobile (alias)
                    bicycle
                    bus
                    cab (alias)
                    car
                    fighter-jet
                    motorcycle
                    plane
                    rocket
                    ship
                    space-shuttle
                    subway
                    taxi
                    train
                    truck
                    wheelchair
                    wheelchair-alt
                    genderless
                    intersex (alias)
                    mars
                    mars-double
                    mars-stroke
                    mars-stroke-h
                    mars-stroke-v
                    mercury
                    neuter
                    transgender
                    transgender-alt
                    venus
                    venus-double
                    venus-mars
                    file
                    file-archive-o
                    file-audio-o
                    file-code-o
                    file-excel-o
                    file-image-o
                    file-movie-o (alias)
                    file-o
                    file-pdf-o
                    file-photo-o (alias)
                    file-picture-o (alias)
                    file-powerpoint-o
                    file-sound-o (alias)
                    file-text
                    file-text-o
                    file-video-o
                    file-word-o
                    file-zip-o (alias)
                    • 给这些图标加上 fa-spin class,就可以表现出加载动画了
                    circle-o-notch
                    cog
                    gear (alias)
                    refresh
                    spinner
                    check-square
                    check-square-o
                    circle
                    circle-o
                    dot-circle-o
                    minus-square
                    minus-square-o
                    plus-square
                    plus-square-o
                    square
                    square-o
                    cc-amex
                    cc-diners-club
                    cc-discover
                    cc-jcb
                    cc-mastercard
                    cc-paypal
                    cc-stripe
                    cc-visa
                    credit-card
                    credit-card-alt
                    google-wallet
                    paypal
                    area-chart
                    bar-chart
                    bar-chart-o (alias)
                    line-chart
                    pie-chart
                    bitcoin (alias)
                    btc
                    cny (alias)
                    dollar (alias)
                    eur
                    euro (alias)
                    gbp
                    gg
                    gg-circle
                    ils
                    inr
                    jpy
                    krw
                    money
                    rmb (alias)
                    rouble (alias)
                    rub
                    ruble (alias)
                    rupee (alias)
                    shekel (alias)
                    sheqel (alias)
                    try
                    turkish-lira (alias)
                    usd
                    won (alias)
                    yen (alias)
                    align-center
                    align-justify
                    align-left
                    align-right
                    bold
                    chain (alias)
                    chain-broken
                    clipboard
                    columns
                    copy (alias)
                    cut (alias)
                    dedent (alias)
                    eraser
                    file
                    file-o
                    file-text
                    file-text-o
                    files-o
                    floppy-o
                    font
                    header
                    indent
                    italic
                    link
                    list
                    list-alt
                    list-ol
                    list-ul
                    outdent
                    paperclip
                    paragraph
                    paste (alias)
                    repeat
                    rotate-left (alias)
                    rotate-right (alias)
                    save (alias)
                    scissors
                    strikethrough
                    subscript
                    superscript
                    table
                    text-height
                    text-width
                    th
                    th-large
                    th-list
                    underline
                    undo
                    unlink (alias)
                    angle-double-down
                    angle-double-left
                    angle-double-right
                    angle-double-up
                    angle-down
                    angle-left
                    angle-right
                    angle-up
                    arrow-circle-down
                    arrow-circle-left
                    arrow-circle-o-down
                    arrow-circle-o-left
                    arrow-circle-o-right
                    arrow-circle-o-up
                    arrow-circle-right
                    arrow-circle-up
                    arrow-down
                    arrow-left
                    arrow-right
                    arrow-up
                    arrows
                    arrows-alt
                    arrows-h
                    arrows-v
                    caret-down
                    caret-left
                    caret-right
                    caret-square-o-down
                    caret-square-o-left
                    caret-square-o-right
                    caret-square-o-up
                    caret-up
                    chevron-circle-down
                    chevron-circle-left
                    chevron-circle-right
                    chevron-circle-up
                    chevron-down
                    chevron-left
                    chevron-right
                    chevron-up
                    exchange
                    hand-o-down
                    hand-o-left
                    hand-o-right
                    hand-o-up
                    long-arrow-down
                    long-arrow-left
                    long-arrow-right
                    long-arrow-up
                    toggle-down (alias)
                    toggle-left (alias)
                    toggle-right (alias)
                    toggle-up (alias)
                    arrows-alt
                    backward
                    compress
                    eject
                    expand
                    fast-backward
                    fast-forward
                    forward
                    pause
                    pause-circle
                    pause-circle-o
                    play
                    play-circle
                    play-circle-o
                    random
                    step-backward
                    step-forward
                    stop
                    stop-circle
                    stop-circle-o
                    youtube-play
                    500px
                    adn
                    amazon
                    android
                    angellist
                    apple
                    bandcamp
                    behance
                    behance-square
                    bitbucket
                    bitbucket-square
                    bitcoin (alias)
                    black-tie
                    bluetooth
                    bluetooth-b
                    btc
                    buysellads
                    cc-amex
                    cc-diners-club
                    cc-discover
                    cc-jcb
                    cc-mastercard
                    cc-paypal
                    cc-stripe
                    cc-visa
                    chrome
                    codepen
                    codiepie
                    connectdevelop
                    contao
                    css3
                    dashcube
                    delicious
                    deviantart
                    digg
                    dribbble
                    dropbox
                    drupal
                    edge
                    eercast
                    empire
                    envira
                    etsy
                    expeditedssl
                    fa (alias)
                    facebook
                    facebook-f (alias)
                    facebook-official
                    facebook-square
                    firefox
                    first-order
                    flickr
                    font-awesome
                    fonticons
                    fort-awesome
                    forumbee
                    foursquare
                    free-code-camp
                    ge (alias)
                    get-pocket
                    gg
                    gg-circle
                    git
                    git-square
                    github
                    github-alt
                    github-square
                    gitlab
                    gittip (alias)
                    glide
                    glide-g
                    google
                    google-plus
                    google-plus-circle (alias)
                    google-plus-official
                    google-plus-square
                    google-wallet
                    gratipay
                    grav
                    hacker-news
                    houzz
                    html5
                    imdb
                    instagram
                    internet-explorer
                    ioxhost
                    joomla
                    jsfiddle
                    lastfm
                    lastfm-square
                    leanpub
                    linkedin
                    linkedin-square
                    linode
                    linux
                    maxcdn
                    meanpath
                    medium
                    meetup
                    mixcloud
                    modx
                    odnoklassniki
                    odnoklassniki-square
                    opencart
                    openid
                    opera
                    optin-monster
                    pagelines
                    paypal
                    pied-piper
                    pied-piper-alt
                    pied-piper-pp
                    pinterest
                    pinterest-p
                    pinterest-square
                    product-hunt
                    qq
                    quora
                    ra (alias)
                    ravelry
                    rebel
                    reddit
                    reddit-alien
                    reddit-square
                    renren
                    resistance (alias)
                    safari
                    scribd
                    sellsy
                    share-alt
                    share-alt-square
                    shirtsinbulk
                    simplybuilt
                    skyatlas
                    skype
                    slack
                    slideshare
                    snapchat
                    snapchat-ghost
                    snapchat-square
                    soundcloud
                    spotify
                    stack-exchange
                    stack-overflow
                    steam
                    steam-square
                    stumbleupon
                    stumbleupon-circle
                    superpowers
                    telegram
                    tencent-weibo
                    themeisle
                    trello
                    tripadvisor
                    tumblr
                    tumblr-square
                    twitch
                    twitter
                    twitter-square
                    usb
                    viacoin
                    viadeo
                    viadeo-square
                    vimeo
                    vimeo-square
                    vine
                    vk
                    wechat (alias)
                    weibo
                    weixin
                    whatsapp
                    wikipedia-w
                    windows
                    wordpress
                    wpbeginner
                    wpexplorer
                    wpforms
                    xing
                    xing-square
                    y-combinator
                    y-combinator-square (alias)
                    yahoo
                    yc (alias)
                    yc-square (alias)
                    yelp
                    yoast
                    youtube
                    youtube-play
                    youtube-square
                    ambulance
                    h-square
                    heart
                    heart-o
                    heartbeat
                    hospital-o
                    medkit
                    plus-square
                    stethoscope
                    user-md
                    wheelchair
                    wheelchair-alt
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/icon/glyphicons.html ================================================

                    Glyphicons 字体图标

                    包括250多个来自 Glyphicon Halflings 的字体图标。Glyphicons Halflings 一般是收费的,但是他们的作者允许 Bootstrap 免费使用。为了表示感谢,希望你在使用时尽量为 Glyphicons 添加一个友情链接。
                    所有图标 所有图标集合 - Glyphicons
                    • glyphicon glyphicon-asterisk
                    • glyphicon glyphicon-plus
                    • glyphicon glyphicon-euro
                    • glyphicon glyphicon-eur
                    • glyphicon glyphicon-minus
                    • glyphicon glyphicon-cloud
                    • glyphicon glyphicon-envelope
                    • glyphicon glyphicon-pencil
                    • glyphicon glyphicon-glass
                    • glyphicon glyphicon-music
                    • glyphicon glyphicon-search
                    • glyphicon glyphicon-heart
                    • glyphicon glyphicon-star
                    • glyphicon glyphicon-star-empty
                    • glyphicon glyphicon-user
                    • glyphicon glyphicon-film
                    • glyphicon glyphicon-th-large
                    • glyphicon glyphicon-th
                    • glyphicon glyphicon-th-list
                    • glyphicon glyphicon-ok
                    • glyphicon glyphicon-remove
                    • glyphicon glyphicon-zoom-in
                    • glyphicon glyphicon-zoom-out
                    • glyphicon glyphicon-off
                    • glyphicon glyphicon-signal
                    • glyphicon glyphicon-cog
                    • glyphicon glyphicon-trash
                    • glyphicon glyphicon-home
                    • glyphicon glyphicon-file
                    • glyphicon glyphicon-time
                    • glyphicon glyphicon-road
                    • glyphicon glyphicon-download-alt
                    • glyphicon glyphicon-download
                    • glyphicon glyphicon-upload
                    • glyphicon glyphicon-inbox
                    • glyphicon glyphicon-play-circle
                    • glyphicon glyphicon-repeat
                    • glyphicon glyphicon-refresh
                    • glyphicon glyphicon-list-alt
                    • glyphicon glyphicon-lock
                    • glyphicon glyphicon-flag
                    • glyphicon glyphicon-headphones
                    • glyphicon glyphicon-volume-off
                    • glyphicon glyphicon-volume-down
                    • glyphicon glyphicon-volume-up
                    • glyphicon glyphicon-qrcode
                    • glyphicon glyphicon-barcode
                    • glyphicon glyphicon-tag
                    • glyphicon glyphicon-tags
                    • glyphicon glyphicon-book
                    • glyphicon glyphicon-bookmark
                    • glyphicon glyphicon-print
                    • glyphicon glyphicon-camera
                    • glyphicon glyphicon-font
                    • glyphicon glyphicon-bold
                    • glyphicon glyphicon-italic
                    • glyphicon glyphicon-text-height
                    • glyphicon glyphicon-text-width
                    • glyphicon glyphicon-align-left
                    • glyphicon glyphicon-align-center
                    • glyphicon glyphicon-align-right
                    • glyphicon glyphicon-align-justify
                    • glyphicon glyphicon-list
                    • glyphicon glyphicon-indent-left
                    • glyphicon glyphicon-indent-right
                    • glyphicon glyphicon-facetime-video
                    • glyphicon glyphicon-picture
                    • glyphicon glyphicon-map-marker
                    • glyphicon glyphicon-adjust
                    • glyphicon glyphicon-tint
                    • glyphicon glyphicon-edit
                    • glyphicon glyphicon-share
                    • glyphicon glyphicon-check
                    • glyphicon glyphicon-move
                    • glyphicon glyphicon-step-backward
                    • glyphicon glyphicon-fast-backward
                    • glyphicon glyphicon-backward
                    • glyphicon glyphicon-play
                    • glyphicon glyphicon-pause
                    • glyphicon glyphicon-stop
                    • glyphicon glyphicon-forward
                    • glyphicon glyphicon-fast-forward
                    • glyphicon glyphicon-step-forward
                    • glyphicon glyphicon-eject
                    • glyphicon glyphicon-chevron-left
                    • glyphicon glyphicon-chevron-right
                    • glyphicon glyphicon-plus-sign
                    • glyphicon glyphicon-minus-sign
                    • glyphicon glyphicon-remove-sign
                    • glyphicon glyphicon-ok-sign
                    • glyphicon glyphicon-question-sign
                    • glyphicon glyphicon-info-sign
                    • glyphicon glyphicon-screenshot
                    • glyphicon glyphicon-remove-circle
                    • glyphicon glyphicon-ok-circle
                    • glyphicon glyphicon-ban-circle
                    • glyphicon glyphicon-arrow-left
                    • glyphicon glyphicon-arrow-right
                    • glyphicon glyphicon-arrow-up
                    • glyphicon glyphicon-arrow-down
                    • glyphicon glyphicon-share-alt
                    • glyphicon glyphicon-resize-full
                    • glyphicon glyphicon-resize-small
                    • glyphicon glyphicon-exclamation-sign
                    • glyphicon glyphicon-gift
                    • glyphicon glyphicon-leaf
                    • glyphicon glyphicon-fire
                    • glyphicon glyphicon-eye-open
                    • glyphicon glyphicon-eye-close
                    • glyphicon glyphicon-warning-sign
                    • glyphicon glyphicon-plane
                    • glyphicon glyphicon-calendar
                    • glyphicon glyphicon-random
                    • glyphicon glyphicon-comment
                    • glyphicon glyphicon-magnet
                    • glyphicon glyphicon-chevron-up
                    • glyphicon glyphicon-chevron-down
                    • glyphicon glyphicon-retweet
                    • glyphicon glyphicon-shopping-cart
                    • glyphicon glyphicon-folder-close
                    • glyphicon glyphicon-folder-open
                    • glyphicon glyphicon-resize-vertical
                    • glyphicon glyphicon-resize-horizontal
                    • glyphicon glyphicon-hdd
                    • glyphicon glyphicon-bullhorn
                    • glyphicon glyphicon-bell
                    • glyphicon glyphicon-certificate
                    • glyphicon glyphicon-thumbs-up
                    • glyphicon glyphicon-thumbs-down
                    • glyphicon glyphicon-hand-right
                    • glyphicon glyphicon-hand-left
                    • glyphicon glyphicon-hand-up
                    • glyphicon glyphicon-hand-down
                    • glyphicon glyphicon-circle-arrow-right
                    • glyphicon glyphicon-circle-arrow-left
                    • glyphicon glyphicon-circle-arrow-up
                    • glyphicon glyphicon-circle-arrow-down
                    • glyphicon glyphicon-globe
                    • glyphicon glyphicon-wrench
                    • glyphicon glyphicon-tasks
                    • glyphicon glyphicon-filter
                    • glyphicon glyphicon-briefcase
                    • glyphicon glyphicon-fullscreen
                    • glyphicon glyphicon-dashboard
                    • glyphicon glyphicon-paperclip
                    • glyphicon glyphicon-heart-empty
                    • glyphicon glyphicon-link
                    • glyphicon glyphicon-phone
                    • glyphicon glyphicon-pushpin
                    • glyphicon glyphicon-usd
                    • glyphicon glyphicon-gbp
                    • glyphicon glyphicon-sort
                    • glyphicon glyphicon-sort-by-alphabet
                    • glyphicon glyphicon-sort-by-alphabet-alt
                    • glyphicon glyphicon-sort-by-order
                    • glyphicon glyphicon-sort-by-order-alt
                    • glyphicon glyphicon-sort-by-attributes
                    • glyphicon glyphicon-sort-by-attributes-alt
                    • glyphicon glyphicon-unchecked
                    • glyphicon glyphicon-expand
                    • glyphicon glyphicon-collapse-down
                    • glyphicon glyphicon-collapse-up
                    • glyphicon glyphicon-log-in
                    • glyphicon glyphicon-flash
                    • glyphicon glyphicon-log-out
                    • glyphicon glyphicon-new-window
                    • glyphicon glyphicon-record
                    • glyphicon glyphicon-save
                    • glyphicon glyphicon-open
                    • glyphicon glyphicon-saved
                    • glyphicon glyphicon-import
                    • glyphicon glyphicon-export
                    • glyphicon glyphicon-send
                    • glyphicon glyphicon-floppy-disk
                    • glyphicon glyphicon-floppy-saved
                    • glyphicon glyphicon-floppy-remove
                    • glyphicon glyphicon-floppy-save
                    • glyphicon glyphicon-floppy-open
                    • glyphicon glyphicon-credit-card
                    • glyphicon glyphicon-transfer
                    • glyphicon glyphicon-cutlery
                    • glyphicon glyphicon-header
                    • glyphicon glyphicon-compressed
                    • glyphicon glyphicon-earphone
                    • glyphicon glyphicon-phone-alt
                    • glyphicon glyphicon-tower
                    • glyphicon glyphicon-stats
                    • glyphicon glyphicon-sd-video
                    • glyphicon glyphicon-hd-video
                    • glyphicon glyphicon-subtitles
                    • glyphicon glyphicon-sound-stereo
                    • glyphicon glyphicon-sound-dolby
                    • glyphicon glyphicon-sound-5-1
                    • glyphicon glyphicon-sound-6-1
                    • glyphicon glyphicon-sound-7-1
                    • glyphicon glyphicon-copyright-mark
                    • glyphicon glyphicon-registration-mark
                    • glyphicon glyphicon-cloud-download
                    • glyphicon glyphicon-cloud-upload
                    • glyphicon glyphicon-tree-conifer
                    • glyphicon glyphicon-tree-deciduous
                    • glyphicon glyphicon-cd
                    • glyphicon glyphicon-save-file
                    • glyphicon glyphicon-open-file
                    • glyphicon glyphicon-level-up
                    • glyphicon glyphicon-copy
                    • glyphicon glyphicon-paste
                    • glyphicon glyphicon-alert
                    • glyphicon glyphicon-equalizer
                    • glyphicon glyphicon-king
                    • glyphicon glyphicon-queen
                    • glyphicon glyphicon-pawn
                    • glyphicon glyphicon-bishop
                    • glyphicon glyphicon-knight
                    • glyphicon glyphicon-baby-formula
                    • glyphicon glyphicon-tent
                    • glyphicon glyphicon-blackboard
                    • glyphicon glyphicon-bed
                    • glyphicon glyphicon-apple
                    • glyphicon glyphicon-erase
                    • glyphicon glyphicon-hourglass
                    • glyphicon glyphicon-lamp
                    • glyphicon glyphicon-duplicate
                    • glyphicon glyphicon-piggy-bank
                    • glyphicon glyphicon-scissors
                    • glyphicon glyphicon-bitcoin
                    • glyphicon glyphicon-btc
                    • glyphicon glyphicon-xbt
                    • glyphicon glyphicon-yen
                    • glyphicon glyphicon-jpy
                    • glyphicon glyphicon-ruble
                    • glyphicon glyphicon-rub
                    • glyphicon glyphicon-scale
                    • glyphicon glyphicon-ice-lolly
                    • glyphicon glyphicon-ice-lolly-tasted
                    • glyphicon glyphicon-education
                    • glyphicon glyphicon-option-horizontal
                    • glyphicon glyphicon-option-vertical
                    • glyphicon glyphicon-menu-hamburger
                    • glyphicon glyphicon-modal-window
                    • glyphicon glyphicon-oil
                    • glyphicon glyphicon-grain
                    • glyphicon glyphicon-sunglasses
                    • glyphicon glyphicon-text-size
                    • glyphicon glyphicon-text-color
                    • glyphicon glyphicon-text-background
                    • glyphicon glyphicon-object-align-top
                    • glyphicon glyphicon-object-align-bottom
                    • glyphicon glyphicon-object-align-horizontal
                    • glyphicon glyphicon-object-align-left
                    • glyphicon glyphicon-object-align-vertical
                    • glyphicon glyphicon-object-align-right
                    • glyphicon glyphicon-triangle-right
                    • glyphicon glyphicon-triangle-left
                    • glyphicon glyphicon-triangle-bottom
                    • glyphicon glyphicon-triangle-top
                    • glyphicon glyphicon-console
                    • glyphicon glyphicon-superscript
                    • glyphicon glyphicon-subscript
                    • glyphicon glyphicon-menu-left
                    • glyphicon glyphicon-menu-right
                    • glyphicon glyphicon-menu-down
                    • glyphicon glyphicon-menu-up
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/dialog.html ================================================
                    模态窗口

                    创建自定义的RuoYi模态窗口可通过添加.inmodal类来实现。

                    大小设置

                    模态窗口提供两种大小尺寸,可以通过为模态窗口的.modal-dialog添加类来实现

                    动画窗口

                    您可以通过为模态窗口的.modal-content添加类来实现动画效果

                    设置选项

                    可以通过数据绑定或者Javascript来实现模态窗口的相关功能,如果使用数据绑定,可以为元素添加data-,如data-backdrop=""

                    名称 类型 默认值 说明
                    backdrop boolean 或 string 'static' true 遮罩层,或使用'static'指定遮罩层与关闭模态窗口不关联
                    keyboard boolean true 按Esc键时退出模态窗口
                    show boolean true 初始化完成后显示模态窗口
                    remote path false

                    推荐使用数据绑定方式,或使用 jQuery.load

                    远程URL示例:

                    <a data-toggle="modal" href="remote.html" data-target="#modal">Click me</a>
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/form.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/layer.html ================================================
                    信息框

                    通过调用$.modal.alert()实现。

                    提示框

                    通过调用$.modal.msg()实现。

                    询问框

                    通过调用$.modal.confirm()实现。

                    消息提示并刷新父窗体

                    通过调用$.modal.msgReload()实现。

                    普通弹出层

                    通过调用$.modal.open()实现。

                    选卡页方式

                    通过调用$.modal.openTab()实现。

                    其他内容

                    通过调用layer实现。

                    遮罩层

                    通过调用blockUI实现。

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/table/check.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/table/frame1.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/table/frame2.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/table/parent.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/table/radio.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/modal/table.html ================================================
                    弹层框

                    弹出复选框表格及单选框表格(点击提交后得到数据)。

                    弹层框

                    弹出层,点击提交后得到数据并回显到父窗体。

                    弹层框

                    多层弹出,点击提交后得到数据并回显到父窗体。

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/operate/add.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/operate/detail.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/operate/edit.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/operate/other.html ================================================
                     

                     
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/operate/table.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/report/echarts.html ================================================

                    ECharts开源来自百度商业前端数据可视化团队,基于html5 Canvas,是一个纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。创新的拖拽重计算、数据视图、值域漫游等特性大大增强了用户体验,赋予了用户对数据进行挖掘、整合的能力。 了解更多

                    ECharts官网:https://echarts.apache.org/

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/report/metrics.html ================================================
                    Q1 销量

                    上升

                    更新时间:12天以前
                    Q2 销量

                    上升

                    更新时间:12天以前
                    Q3 销量

                    下降

                    更新时间:12天以前
                    Q4 销量

                    下降

                    更新时间:12天以前
                    本日访问量

                    198 009

                    本周访问量

                    65 000

                    本月访问量

                    680 900

                    平均停留时间

                    00:06:40

                    使用率

                    65%

                    4:32更新
                    使用率

                    50%

                    4:32更新
                    使用率

                    14%

                    4:32更新
                    使用率

                    20%

                    4:32更新
                    百分比

                    42/20

                    百分比

                    100/54

                    百分比

                    685/211

                    百分比

                    240/32

                    收入

                    886,200

                    98%
                    总收入
                    本月收入

                    1 738,200

                    98%
                    总收入
                    本日收入

                    -200,100

                    12%
                    总收入
                    搜索有收入

                    54,200

                    24%
                    总收入
                    预警
                    示例 01
                    示例 02
                    示例 03
                    项目
                    示例 01
                    示例 02
                    示例 03
                    消息
                    示例 01
                    示例 02
                    示例 03
                    通知
                    示例 01
                    示例 02
                    示例 03
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/report/peity.html ================================================

                    Peity图表

                    是一个内嵌数据图形可视化的图表库

                    了解 Peity

                    饼状图 自定义颜色
                    图表 代码
                    1/5 <span class="pie">1/5</span>
                    226/360 <span class="pie">226/360</span>
                    0.52/1.561 <span class="pie">0.52/1.561</span>
                    1,4 <span class="pie">1,4</span>
                    226,134 <span class="pie">226,134</span>
                    0.52,1.041 <span class="pie">0.52,1.041</span>
                    线性图
                    图表 代码
                    5,3,9,6,5,9,7,3,5,2,5,3,9,6,5,9,7,3,5,2 <span class="line">5,3,9,6,5,9,7,3,5,2</span>
                    5,3,9,6,5,9,7,3,5,2 <span class="line">5,3,9,6,5,9,7,3,5,2</span>
                    5,3,2,-1,-3,-2,2,3,5,2 <span class="line">5,3,2,-1,-3,-2,2,3,5,2</span>
                    0,-3,-6,-4,-5,-4,-7,-3,-5,-2 <span class="line">0,-3,-6,-4,-5,-4,-7,-3,-5,-2</span>
                    5,3,9,6,5,9,7,3,5,2 <span class="bar">5,3,9,6,5,9,7,3,5,2</span>
                    5,3,2,-1,-3,-2,2,3,5,2 <span class="bar">5,3,2,-1,-3,-2,2,3,5,2</span>
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/report/sparkline.html ================================================

                    Sparkline

                    这是另一个可视化图表库

                    了解 Sparkline

                    Sparkline图表 自定义颜色
                    图表 类型
                    内联线性图
                    柱状图
                    饼状图
                    长线性图
                    三态图
                    散点图
                    自定义饼状图尺寸
                    自定义柱状图尺寸
                    自定义线性图尺寸
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/asynTree.html ================================================ ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/button.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/child.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/cookie.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/curd.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/customView.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/data.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/detail.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/dynamicColumns.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/editable.html ================================================ ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/event.html ================================================

                    自定义触发事件(点击某行/双击某行/单击某格/双击某格/服务器发送数据前触发/数据被加载时触发)

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/export.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/exportSelected.html ================================================
                    勾选数据导出指定列,否则为全部 导出
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/fixedColumns.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/footer.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/groupHeader.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/headerStyle.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/image.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/multi.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/other.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/pageGo.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/params.html ================================================

                    通过queryParams方法设置

                    • 用户姓名:

                    通过form自动填充

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/print.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/refresh.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/remember.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/reorderColumns.html ================================================

                    按住表格列拖拽

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/reorderRows.html ================================================

                    按住表格行拖拽

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/resizable.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/search.html ================================================

                    普通条件查询

                    • 商户编号:
                    • 终端编号:
                    • 处理状态:
                    •  搜索  重置

                    时间条件查询

                    多级联动下拉查询

                    下拉多选条件查询

                    • 商户编号:
                    • 终端编号:
                    •  搜索  重置

                    复杂条件查询

                    • -
                    •  搜索  重置
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/subdata.html ================================================

                    客户信息

                    商品数据

                     
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/textSearch.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/demo/table/virtualScroll.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/error/404.html ================================================ RuoYi - 404

                    404

                    找不到网页!

                    对不起,您正在寻找的页面不存在。尝试检查URL的错误,然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容。 主页
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/error/500.html ================================================ RuoYi - 500

                    500

                    内部服务器错误!

                    服务器遇到意外事件,不允许完成请求。我们抱歉。您可以返回主页面。 主页
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/error/service.html ================================================ RuoYi - 500

                    操作异常!

                    [[${errorMessage}]]
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/error/unauth.html ================================================ RuoYi - 403

                    403

                    您没有访问权限!

                    对不起,您没有访问权限,请不要进行非法操作!您可以返回主页面 返回主页
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/include.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/index-topnav.html ================================================ 若依系统首页
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/index.html ================================================ 若依系统首页
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/lock.html ================================================ 锁定屏幕
                    [[ ${user.loginName} ]] / [[${#strings.defaultString(user.userName, '-')}]]
                    User Image
                    系统锁屏,请输入密码登录!
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/login.html ================================================ 登录若依系统

                    登录:

                    你若不离不弃,我必生死相依

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/main.html ================================================ 若依介绍

                    Hello,Guest

                    移动设备访问请扫描以下二维码:


                    若依后台管理框架

                    一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了若依管理系统。,她可以用于所有的Web应用程序,如网站管理后台网站会员中心CMSCRMOA等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。

                    当前版本:v[[${version}]]

                    ¥免费开源


                    访问码云 访问主页

                    技术选型:

                    1. 核心框架:Spring Boot。
                    2. 安全框架:Apache Shiro。
                    3. 模板引擎:Thymeleaf。
                    4. 持久层框架:MyBatis。
                    5. 定时任务:Quartz。
                    6. 数据库连接池:Druid。
                    7. 工具类:Fastjson。
                    8. 更多……
                    联系信息

                    官网:http://www.ruoyi.vip

                    QQ群:满1389287 满1679294 满1529866 满1772718 满1366522 满1382251 满1145125 满86752435 满134072510 满210336300 满339522636 满130035985 满143151071 满158781320 满201531282 满101526938 满264355400 满298522656 满139845794 满185760789 满175104288 满174942938 满287843737 满232896766 满180208928 满140284548 177203794

                    微信:/ *若依

                    支付宝:/ *若依

                    更新日志
                    v4.8.22025.12.13
                    1. 使用yauaa代替bitwalker
                    2. 用户归属部门新增清除按钮
                    3. 用户头像更换后移除旧头像文件
                    4. 支持Excel导出对象的多个子列表
                    5. 升级bootstrap版本到3.4.1
                    6. 升级oshi到最新版本6.9.1
                    7. 升级druid到最新版本1.2.27
                    8. 升级tomcat到最新版本9.0.112
                    9. 升级commons.io到最新版本2.21.0
                    10. 修复用户归属部门无法修改为空问题
                    11. 修复菜单栏收起后下级菜单无法滚动问题
                    12. 修复异步树表查询条件无法带入分页问题
                    13. 修复comboReadDict属性下多个sheet出现的报错
                    14. 优化密码策略提示优先顺序
                    15. 优化日志记录参数拼装提升效率
                    16. 优化导入文件检查标题行不能为空
                    17. 优化定时任务包名白名单匹配方式
                    18. 优化Excel统计行数值的单元格样式显示
                    19. 优化数据权限控制逻辑,放开permission限制
                    20. 其他细节优化
                    v4.8.12025.05.20
                    1. 新增CSRF防护功能
                    2. Excel导入导出支持多图片
                    3. 菜单管理支持批量保存排序
                    4. 用户分配角色页禁用不允许分配
                    5. 优化Tab页签跟随主题样式效果
                    6. 优化浅色主题下菜单右边框同步主题色
                    7. 优化空指针异常时日志无法记录错误信息问题
                    8. 新增表格参数(自定义radio/checkbox的name值)
                    9. 升级oshi到最新版本6.8.1
                    10. 升级tomcat到最新版本9.0.105
                    11. 升级commons.io到最新版本2.19.0
                    12. 升级bootstrap-table到最新版本1.24.1
                    13. 升级bootstrap-fileinput到最新版本5.5.4
                    14. 用户更新方法移除login_name更新字段
                    15. 修复定时任务参数值带括号时异常问题
                    16. 进入新增页面前方法校验数据权限
                    17. 进入授权角色&重置密码页校验数据权限
                    18. 优化Excel匹配数值型.0结尾
                    19. 优化HttpUtils加入请求类型传参
                    20. 优化管理员登录不设置权限permissions属性
                    21. 优化successCallback方法对于非特定表格类型无响应问题
                    22. 优化导出Excel日期格式双击离开后与设定的格式不一致问题
                    23. 其他细节优化
                    v4.8.02024.12.26
                    1. 支持自定义显示Excel属性列
                    2. 表格默认转义HTML字符串
                    3. 新增列宽拖动长内容自适应显示示例
                    4. Excel注解支持wrapText是否允许内容换行
                    5. 代码生成新增配置是否允许文件覆盖到本地
                    6. 升级oshi到最新版本6.6.5
                    7. 升级tomcat到最新版本9.0.96
                    8. 升级logback到最新版本1.2.13
                    9. 升级commons.io到最新版本2.16.1
                    10. 升级spring-framework到最新版本5.3.39
                    11. 升级jquery.validate到最新版本v1.21.0
                    12. 优化导入带标题文件关闭清理
                    13. 代码生成创建表屏蔽违规的字符
                    14. 修复主子表数据显示问题
                    15. 修复记住我请求头过大的问题
                    16. 修复角色禁用权限不失效问题
                    17. 修复类匿名注解访问失效问题
                    18. 修复导出子列表对象只能在最后的问题
                    19. 修复多选下拉框open导致页签空白问题
                    20. 优化身份证脱敏正则
                    21. 优化查询时间范围日期格式
                    22. 优化异步树表格折叠同步子状态
                    23. 优化时间控件清除按钮样式问题
                    24. 优化表格图片预览动态路径显示问题
                    25. 优化select2下拉框必填背景色无法清空问题
                    26. 其他细节优化
                    v4.7.92024.06.06
                    1. 通知公告新增详细显示
                    2. 新增数据脱敏过滤注解
                    3. 新增表格示例(虚拟滚动)
                    4. 新增表格示例(全文检索)
                    5. 新增表格示例(保存状态)
                    6. 代码生成支持表单布局选项
                    7. 限制用户操作数据权限范围
                    8. 用户密码新增非法字符验证
                    9. 默认加载layer扩展皮肤
                    10. 未修改初始密码弹框提醒
                    11. 定时任务白名单配置范围缩小
                    12. 操作日志列表重置回第一页
                    13. 定时任务日志默认按时间排序
                    14. Excel注解ColumnType类型新增文本
                    15. Excel注解新增属性comboReadDict
                    16. 新增Anonymous匿名访问不鉴权注解
                    17. 升级oshi到最新版本6.6.1
                    18. 升级druid到最新版本1.2.23
                    19. 升级commons.io到最新版本2.13.0
                    20. 升级spring-framework到安全版本
                    21. 升级bootstrap-table到最新版本1.22.6
                    22. 修复重置日期时出现的异常问题
                    23. 修复页签关闭后存在的跳转问题
                    24. 修复tooltip单击复制文本不生效的问题
                    25. 更新缓存管理键名排序方式
                    26. 更新HttpUtils中的User-Agent
                    27. 优化自定义XSS注解匹配方式
                    28. 优化登录注册页面验证码验证
                    29. 优化数据权限自定义匹配方式
                    30. 优化高频率定时任务不执行问题
                    31. 优化树表格align属性在标题生效
                    32. 优化代码生成主子表关联查询方式
                    33. 优化导入Excel时设置dictType属性重复查缓存问题
                    34. 其他细节优化
                    v4.7.82023.11.23
                    1. 用户列表新增抽屉效果详细信息
                    2. 操作日志列表新增IP地址查询
                    3. 定时任务新增页去除状态选项
                    4. 系统管理角色列表显示数据权限
                    5. 通用排序属性orderBy参数限制长度
                    6. 新增isScrollToTop页签切换滚动到顶部
                    7. Excel自定义数据处理器增加单元格/工作簿对象
                    8. 新增表格参数(数据值为空时显示的内容undefinedText)
                    9. 升级oshi到最新版本6.4.7
                    10. 升级shiro到最新版本1.13.0
                    11. 升级druid到最新版本1.2.20
                    12. 升级pagehelper到最新版1.4.7
                    13. 升级spring-boot到最新版本2.5.15
                    14. 升级jquery到最新版v3.7.1
                    15. 升级layer到最新版本v3.7.0
                    16. 升级layui到最新版本v2.8.18
                    17. 升级x-editable到最新版本1.5.3
                    18. 修复自定义字典样式不生效的问题
                    19. 修复弹窗按钮启用禁用方法无效问题
                    20. 修复横向菜单关闭最后一个页签状态问题
                    21. 修复Excel导入数据临时文件无法删除问题
                    22. 修复表格行内编辑启用翻页记住选择无效问题
                    23. 修复Excels导入时无法获取到dictType字典值问题
                    24. 优化重置密码鼠标按下显示密码
                    25. 优化参数键值文本框改为文本域
                    26. 优化表格重置默认返回到第一页
                    27. 优化菜单管理类型为按钮状态可选
                    28. 优化数字金额大写转换精度丢失问题
                    29. 优化树表查询无数据时清除分页信息
                    30. 优化通用detail详细信息弹窗不显示按钮
                    31. 其他细节优化
                    v4.7.72023.04.14
                    1. 操作日志新增消耗时间属性
                    2. 日志管理使用索引提升查询性能
                    3. 日志注解支持排除指定的请求参数
                    4. 新增监控页面图标显示
                    5. 新增支持登录IP黑名单限制
                    6. 更新fontawesome图标示例
                    7. 屏蔽定时任务bean违规的字符
                    8. 支持自定义隐藏属性列过滤子对象
                    9. 连接池Druid支持新的配置connectTimeout和socketTimeout
                    10. 升级jquery到最新版v3.6.3
                    11. 升级layui到最新版本2.7.6
                    12. 升级jasny-bootstrap到最新版4.0.0
                    13. 升级oshi到最新版本6.4.1
                    14. 升级druid到最新版本1.2.16
                    15. 修复异步表格树子项排序问题
                    16. 修复冻结列不支持IE浏览器的问题
                    17. 修复主子表使用suggest插件无法新增问题
                    18. 修复菜单栏快速点击导致展开折叠样式问题
                    19. 修复用户多角色数据权限可能出现权限抬升的情况
                    20. 修复异步加载表格树重置列表父节点展开异常问题
                    21. 修复页签属性refresh为undefined时页面被刷新问题
                    22. 移除apache/commons-fileupload依赖
                    23. 优化前端属性提醒说明
                    24. 优化用户导入更新时需获取用户编号问题
                    25. 优化主子表根据序号删除方法加入表格ID参数
                    26. 优化导出Excel时设置dictType属性重复查缓存问题
                    27. 优化在线用户服务缓存改为从Bean容器获取不使用自动装配
                    28. 优化表格示例行拖拽后列表底部总记录条数变成了undefined问题
                    29. 其他细节优化
                    v4.7.62022.12.16
                    1. 定时任务违规的字符
                    2. 忽略不必要的属性数据返回
                    3. 导入更新用户数据前校验数据权限
                    4. 修改参数键名时移除前缓存配置
                    5. 修改用户登录账号进行重复验证
                    6. 兼容Excel下拉框内容过多无法显示
                    7. 升级oshi到最新版本6.4.0
                    8. 升级kaptcha到最新版2.3.3
                    9. 升级druid到最新版本1.2.15
                    10. 升级shiro到最新版本1.10.1
                    11. 升级pagehelper到最新版1.4.6
                    12. 升级bootstrap-fileinput到最新版本5.5.2
                    13. 修复sheet超出最大行数异常问题
                    14. 修复关闭父页签后提交无法跳转的问题
                    15. 修复操作日志类型多选导出不生效问题
                    16. 修复导出包含空子列表数据异常的问题
                    17. 优化树形表格层级显示
                    18. 优化SQL关键字检查防止注入
                    19. 优化用户管理重置时取消部门选择
                    20. 优化代码生成同步后字典值NULL问题
                    21. 优化导出对象的子列表为空会出现[]问题
                    22. 优化select2搜索下拉后校验必填样式问题
                    23. 其他细节优化
                    v4.7.52022.09.05
                    1. Excel支持导出对象的子列表方法
                    2. 数据逻辑删除不进行唯一验证
                    3. 优化多角色数据权限匹配规则
                    4. 新增主子表提交校验示例
                    5. 支持自定义隐藏Excel属性列
                    6. Excel注解支持backgroundColor属性设置背景颜色
                    7. 菜单配置刷新时Tab页签切换时刷新
                    8. 增加对AjaxResult消息结果类型的判断
                    9. 新增示例(进度条)
                    10. 新增内容编码/解码方便插件集成使用
                    11. 升级jquery到最新版3.6.1
                    12. 升级layui到最新版本2.7.5
                    13. 升级shiro到最新版本1.9.1
                    14. 升级druid到最新版本1.2.11
                    15. 升级pagehelper到最新版1.4.3
                    16. 升级oshi到最新版本6.2.2
                    17. 修复树表onLoadSuccess不生效的问题
                    18. 修复用户分配角色大于默认页数丢失问题
                    19. 定时任务支持执行父类方法
                    20. 自动设置切换多个树表格实例配置
                    21. 页签创建标题优先data-title属性
                    22. 优化任务过期不执行调度
                    23. 优化横向菜单下激活菜单样式
                    24. 优化按钮打开窗口后按回车反复弹出
                    25. 优化excel/scale属性导出单元格数值类型
                    26. 优化druid开启wall过滤器出现的异常问题
                    27. 优化多个相同角色数据导致权限SQL重复问题
                    28. 其他细节优化
                    v4.7.42022.06.01
                    1. 用户头像上传图片格式限制
                    2. Excel注解支持color属性设置字体颜色
                    3. 设置分页参数默认值
                    4. 主子表操作列新增单个删除
                    5. 定时任务检查Bean包名是否为白名单配置
                    6. 升级spring-boot到最新版本2.5.14
                    7. 升级shiro到最新版本1.9.0
                    8. 升级oshi到最新版本6.1.6
                    9. 升级fastjson到最新版1.2.83 安全修复版本
                    10. 文件上传兼容Weblogic环境
                    11. 新增清理分页的线程变量方法
                    12. 新增获取不带后缀文件名称方法
                    13. 用户缓存信息添加部门ancestors祖级列表
                    14. 自定义ShiroFilterFactoryBean防止中文请求被拦截
                    15. 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
                    16. 优化IP地址获取到多个的问题
                    17. 优化表格冻结列阴影效果显示
                    18. 优化菜单侧边栏滚动条尺寸及颜色
                    19. 优化显示顺序orderNum类型为整型
                    20. 优化接口使用泛型使其看到响应属性字段
                    21. 优化导出数据LocalDateTime类型无数据问题
                    22. 修复导入Excel时字典字段类型为Long转义为空问题
                    23. 优化导出excel单元格验证,包含变更为开头.防止正常内容被替换
                    24. 修复URL类型回退键被禁止问题
                    25. 修复表格客户端分页序号显示错误问题
                    26. 修复代码生成拖拽多次出现的排序不正确问题
                    27. 修复表格打印组件不识别多层对象属性值问题
                    28. 修复操作日志查询类型条件为0时会查到所有数据
                    29. 修复Excel注解prompt/combo同时使用不生效问题
                    30. 修复初始化多表格处理回调函数时获取的表格配置不一致问题
                    31. 其他细节优化
                    v4.7.32022.03.01
                    1. 表格树支持分页/异步加载
                    2. 代码生成预览支持复制内容
                    3. 定时任务默认保存到内存中执行
                    4. 代码生成同步保留必填/类型选项
                    5. 页面若未匹配到字典标签则返回原字典值
                    6. 用户访问控制时校验数据权限,防止越权
                    7. 导出Excel时屏蔽公式,防止CSV注入风险
                    8. 升级spring-boot到最新版本2.5.10
                    9. 升级spring-boot-mybatis到最新版2.2.2
                    10. 升级pagehelper到最新版1.4.1
                    11. 升级oshi到最新版本6.1.2
                    12. 升级bootstrap-table到最新版本1.19.1
                    13. 服务监控新增运行参数信息显示
                    14. 定时任务目标字符串验证包名白名单
                    15. 文件上传接口新增原/新文件名返回参数
                    16. 定时任务屏蔽违规的字符
                    17. 分页数据新增分页参数合理化参数
                    18. 表格父子视图添加点击事件打开示例
                    19. 优化上传文件名称命名规则
                    20. 优化加载字典缓存数据
                    21. 优化任务队列满时任务拒绝策略
                    22. 优化IE11上传预览不显示的问题
                    23. 优化Excel格式化不同类型的日期对象
                    24. 优化国际化配置多余的zh请求问题
                    25. 优化新版Chrome浏览器回退出现的遮罩层
                    26. 修复EMAIL类型回退键被禁止问题
                    27. 修复Xss注解字段值为空时的异常问题
                    28. 其他细节优化
                    v4.7.22021.12.23
                    1. 自定义xss校验注解实现
                    2. 进入修改页面方法添加权限标识
                    3. 代码生成创建按钮添加超级管理员权限
                    4. 代码生成创建表检查关键字,防止注入风险
                    5. 修复定时任务多参数逗号分隔的问题
                    6. 修复表格插件一起使用出现的声明报错问题
                    7. 修复代码生成主子表模板删除方法缺少事务
                    8. 升级oshi到最新版本v5.8.6
                    9. 升级velocity到最新版本2.3
                    10. 升级fastjson到最新版1.2.79
                    11. 升级log4j2到最新版2.17.0 防止漏洞风险
                    12. 升级thymeleaf到最新版3.0.14 阻止远程代码执行漏洞
                    13. 优化修改/授权角色实时生效
                    14. 修整tomcat配置参数已过期问题
                    15. 前端添加单独的二代身份证校验
                    16. 优化新增部门时验证用户所属部门
                    17. 优化查询用户的角色组&岗位组代码
                    18. 请求分页方法设置成通用方便灵活调用
                    19. 优化日期类型错误提示与图标重叠问题
                    20. 其他细节优化
                    v4.7.12021.11.10
                    1. 新增是否开启页签功能
                    2. 代码生成的模块增加创建表功能
                    3. Excel导入支持@Excels注解
                    4. Excel注解支持导入导出标题信息
                    5. Excel注解支持自定义数据处理器
                    6. 日志注解新增是否保存响应参数
                    7. 防重提交注解支持配置间隔时间/提示消息
                    8. 网页部分操作禁止使用后退键(Backspace)
                    9. 实例演示中增加多层窗口获取值
                    10. 弹出层openOptions增加动画属性
                    11. 升级spring-boot到最新版本2.5.6
                    12. 升级spring-boot-mybatis到最新版2.2.0
                    13. 升级pagehelper到最新版1.4.0
                    14. 升级oshi到最新版本v5.8.2
                    15. 升级druid到最新版1.2.8
                    16. 升级fastjson到最新版1.2.78
                    17. 升级thymeleaf-extras-shiro到最新版本v2.1.0
                    18. 升级bootstrap-fileinput到最新版本v5.2.4
                    19. 修改阿里云maven仓库地址为新版地址
                    20. 定时任务屏蔽违规字符
                    21. 增加sendGet无参请求方法
                    22. 代码生成去掉多余的排序字段
                    23. 优化启动脚本参数优化
                    24. 优化页签关闭右侧清除iframe元素
                    25. 优化多表格切换表单查询参数
                    26. 优化表格实例切换event不能为空
                    27. 优化mybatis全局默认的执行器
                    28. 优化导入Excel数据关闭时清理file
                    29. 优化Excel导入图片可能出现的异常
                    30. 优化记录登录信息,防止不必要的修改
                    31. 优化aop语法,使用spring自动注入注解
                    32. 修复无法被反转义问题
                    33. 修复拖拽行数据错位问题
                    34. 修复新窗口打开页面关闭弹窗报错
                    35. 修复富文本回退键被禁止&控制台报错问题
                    36. 修复自定义弹出层全屏参数无效问题
                    37. 修复树表代码生成短字段无法识别问题
                    38. 修复apple/webkit浏览器时间无法格式化
                    39. 修复后端主子表代码模板方法名生成错误问题
                    40. 修复swagger没有指定dataTypeClass导致启动出现warn日志
                    41. 其他细节优化
                    v4.7.02021.09.01
                    1. 优化弹出层显示在顶层窗口
                    2. 定时任务支持在线生成cron表达式
                    3. Excel注解支持Image图片导入
                    4. 支持配置是否开启记住我功能
                    5. 修改时检查用户数据权限范围
                    6. 表单重置开始/结束时间控件
                    7. 新增多图上传示例
                    8. 启用父部门状态排除顶级节点
                    9. 富文本默认dialogsInBody属性
                    10. 去除默认分页合理化参数
                    11. 顶部菜单跳转添加绝对路径
                    12. 升级oshi到最新版本v5.8.0
                    13. 升级shiro到最新版本v1.8.0
                    14. 升级commons.io到最新版本v2.11.0
                    15. 升级jquery到最新版v3.6.0
                    16. 升级icheck到最新版v1.0.3
                    17. 升级layer到最新版本v3.5.1
                    18. 升级layui到最新版本v2.6.8
                    19. 升级laydate到最新版本v5.3.1
                    20. 升级select2到最新版v4.0.13
                    21. 升级cropper到最新版本v1.5.12
                    22. 升级summernote到最新版本v0.8.18
                    23. 升级duallistbox到最新版本v3.0.9
                    24. 升级jquery.validate到最新版本v1.19.3
                    25. 升级bootstrap-suggest到最新版本v0.1.29
                    26. 升级bootstrap-select到最新版本v1.13.18
                    27. 升级bootstrap-fileinput到最新版本v5.2.3
                    28. 查询表格指定列值增加是否去重属性
                    29. 删除sourceMappingURL源映射
                    30. 去除多余的favicon.ico引入
                    31. 优化代码生成模板
                    32. 优化XSS跨站脚本过滤
                    33. 补充定时任务表字段注释
                    34. 定时任务屏蔽ldap远程调用
                    35. 定时任务屏蔽http(s)远程调用
                    36. 定时任务对检查异常进行事务回滚
                    37. 调度日志详细页添加关闭按钮
                    38. 优化异常打印输出信息
                    39. 优化移动端进入首页样式
                    40. 优化用户操作不能删除自己
                    41. 默认开始/结束时间绑定控件选择类型
                    42. 其他细节优化
                    v4.6.22021.07.01
                    1. 优化参数&字典缓存操作
                    2. 新增表格参数(导出方式&导出文件类型)
                    3. 新增表格示例(自定义视图分页)
                    4. 新增示例(表格列拖拽)
                    5. 集成yuicompressor实现(CSS/JS压缩)
                    6. 新增表格参数(是否支持打印页面showPrint)
                    7. 支持bat脚本执行应用
                    8. 修复存在的SQL注入漏洞问题
                    9. 定时任务屏蔽rmi远程调用
                    10. 导出Excel文件支持数据流下载方式
                    11. 实例演示弹层组件增加相册层示例
                    12. 删除操作日志记录信息
                    13. 增加表格重置分页的参数
                    14. 限制超级管理员不允许操作
                    15. 树级结构更新子节点使用replaceFirst
                    16. 支持动态生成密匙,防止默认密钥泄露
                    17. 升级pagehelper到最新版1.3.1
                    18. 升级oshi到最新版本v5.7.4
                    19. 升级swagger到最新版本v3.0.0
                    20. 升级commons.io到最新版本v2.10.0
                    21. 升级commons.fileupload到最新版本v1.4
                    22. 升级bootstrap-table到最新版本v1.18.3
                    23. 升级druid到最新版本v1.2.6
                    24. 升级fastjson到最新版1.2.76
                    25. 升级layui到最新版本v2.6.6
                    26. 升级layer到最新版本v3.5.0
                    27. 升级laydate到最新版本v5.3.0
                    28. 优化表格树移动端&边框显示
                    29. 新增表格刷新options配置方法
                    30. 优化图片工具类读取文件,防止异常
                    31. 修复表格图片预览移动端宽高无效问题
                    32. 主子表通用操作封装处理增加文本域类型
                    33. 日志注解兼容获取json类型的参数
                    34. 修复表单向导插件有滚动条时底部工具栏无法固定问题
                    35. 修复导出角色数据范围翻译缺少仅本人
                    36. 修正Velocity模板初始字符集
                    37. 升级mybatis到最新版3.5.6 阻止远程代码执行漏洞
                    38. 优化代码生成导出模板名称
                    39. 修改个人中心密码长度提醒
                    40. 实例演示中弹出表格增加以回调形式回显到父窗体
                    41. 修复登录页面弹窗文字不显示的问题
                    42. 其他细节优化
                    v4.6.12021.04.12
                    1. 新增IE浏览器版本过低提示页面
                    2. 新增详细信息tab页签方式
                    3. 新增解锁屏幕打开上次页签
                    4. 数据监控默认账户密码防止越权访问
                    5. 新增表格示例(导出选择列)
                    6. 个人信息添加手机&邮箱重复验证
                    7. 个人中心刷新后样式问题
                    8. 操作日志返回参数添加非空验证
                    9. velocity剔除commons-collections版本,防止3.2.1版本的反序列化漏洞
                    10. 子表模板默认日期格式化
                    11. 代码生成预览语言根据后缀名高亮显示
                    12. 代码生成主子表相同字段导致数据问题
                    13. 升级SpringBoot到最新版本2.2.13
                    14. 升级shiro到最新版1.7.1 阻止身份认证绕过漏洞
                    15. 升级bootstrapTable到最新版本v1.18.2
                    16. 升级bootstrapTable相关组件到最新版本v1.18.2
                    17. 升级fastjson到最新版1.2.75
                    18. 升级druid到最新版本v1.2.4
                    19. 升级oshi到最新版本v5.6.0
                    20. 修改ip字段长度防止ipv6地址长度不够
                    21. 搜索建议示例选择后隐藏列表
                    22. 主子表示例增加初始化数据
                    23. 优化Excel导入增加空行判断
                    24. 修复横向菜单无法打开页签问题
                    25. 修复导入数据为负浮点数时,导入结果会丢失精度问题
                    26. 优化更多操作按钮左侧移入内容闪现消失情况
                    27. 修复主子表提交中列隐藏后出现列偏移问题
                    28. 单据打印网页时通过hidden-print隐藏元素
                    29. 表格销毁清除记住选择数据
                    30. 增加表格动态列示例
                    31. 代码生成选择主子表关联元素必填
                    32. tree根据Id和Name选中指定节点增加空判断
                    33. 其他细节优化
                    v4.6.02021.01.01
                    1. 新增缓存监控管理
                    2. 新增锁定屏幕功能
                    3. 菜单新增是否刷新页面
                    4. 删除用户和角色解绑关联
                    5. 新增密码强度字符范围提示
                    6. 防止匿名访问进行过滤
                    7. 升级SpringBoot到最新版本2.2.12
                    8. 升级poi到最新版本4.1.2
                    9. 升级bitwalker到最新版本1.21
                    10. 升级bootstrap-fileinput到最新版本5.1.3
                    11. 升级bootstrapTable到最新版本v1.18.0
                    12. 升级bootstrapTable相关组件到最新版本v1.18.0
                    13. 升级oshi到最新版本v5.3.6
                    14. 新增示例(标签 & 提示)
                    15. 添加单据打印示例
                    16. 修改表格初始参数sortName默认值为undefined
                    17. 新增表格参数(自定义打印页面模板printPageBuilder)
                    18. 新增表格参数(是否显示行间隔色striped)
                    19. 新增表格参数(渲染完成后执行的事件onPostBody)
                    20. Excel注解支持Image图片导出
                    21. Excel支持注解align对齐方式
                    22. Excel支持导入Boolean型数据
                    23. 主子表操作添加通用addColumn方法
                    24. 代码生成日期控件区分范围
                    25. 代码生成数据库文本类型生成表单文本域
                    26. 修复生成主子表外键名错误
                    27. 选项卡新增是否刷新属性
                    28. 修复树表格表头跟表格宽度不同步的问题
                    29. 表格树加载完成触发tooltip方法
                    30. 使用widthUnit定义树表格选项单位
                    31. 修复主子表editColumn序列问题
                    32. 修复添加全屏在无参数时没有替换url参数问题
                    33. 弹出层openOptions移动端自适应
                    34. 防止错误页返回主页出现嵌套问题
                    35. 设置回显数据字典验证防止空值
                    36. 其他细节优化
                    v4.5.12020.11.18
                    1. 阻止任意文件下载漏洞
                    2. 升级shiro到最新版1.7.0 阻止权限绕过漏洞
                    3. 升级druid到最新版本v1.2.2
                    4. 新增表格行触发事件(onCheck、onUncheck、onCheckAll、onUncheckAll)
                    5. 修复多页签关闭非当前选项出现空白问题
                    6. 代码生成预览支持高亮显示
                    7. mapperLocations配置支持分隔符
                    8. 权限信息调整
                    9. 个人中心头像和上传头像增加默认图片
                    10. 全局配置类保持和其他应用命名相同
                    v4.5.02020.10.20
                    1. 新增菜单导航显示风格(default为左侧导航菜单,topnav为顶部导航菜单)
                    2. 菜单&数据权限新增(展开/折叠 全选/全不选 父子联动)
                    3. 账号密码支持自定义更新周期
                    4. 初始密码支持自定义修改策略
                    5. 新增校验用户修改新密码不能与旧密码相同
                    6. 添加检查密码范围支持的特殊字符包括:~!@#$%^&*()-=_+
                    7. 注册账号设置默认用户名称及密码最后更新时间
                    8. 去除用户手机邮箱部门必填验证
                    9. 新增日期格式化方法
                    10. 代码生成添加bit类型
                    11. 树结构加载添加callBack回调方法
                    12. 修复用户管理页面滚动返回顶部条失效
                    13. 修复代码生成模板文件上传组件缺少ctx的问题
                    14. 限制系统内置参数不允许删除
                    15. 新增表格列宽拖动插件
                    16. 新增Ajax局部刷新demo
                    17. 新增是否开启页脚功能
                    18. 新增表格参数(通过自定义函数设置标题样式headerStyle)
                    19. 新增表格参数(通过自定义函数设置页脚样式footerStyle)
                    20. 修复窗体大小改变后浮动提示框失效问题
                    21. 生成代码补充必填样式
                    22. 生成页面时不忽略remark属性
                    23. 字典数据列表页添加关闭按钮
                    24. Excel注解支持自动统计数据总和
                    25. 升级springboot到2.1.17 提升安全性
                    26. 升级pagehelper到最新版1.3.0
                    27. 升级druid到最新版本v1.2.1
                    28. 升级fastjson到最新版1.2.74
                    29. 升级bootstrap-fileinput到最新版本5.1.2
                    30. 升级oshi到最新版本v5.2.5
                    31. 表单向导插件更换为jquery-smartwizard
                    32. 修改主子表提交示例代码防止渲染失效
                    33. 添加导入数据弹出窗体自定义宽高
                    34. 用户信息参数返回忽略掉密码字段
                    35. 优化关闭窗体添加index参数
                    36. 回显数据字典(字符串数组)增加空值判断
                    37. 修改前端密码长度校验和错误提示不符问题
                    38. AjaxResult重写put方法,以方便链式调用
                    39. 增强验证码校验的语义,更易懂
                    40. 导入excel整形值校验优化
                    41. Excel导出类型NUMERIC支持精度浮点类型
                    42. 导出Excel调整targetAttr获取值方法,防止get方法不规范
                    43. 输入框组验证错误后置图标提示颜色
                    44. 上传媒体类型添加视频格式
                    45. 数据权限判断参数类型
                    46. 修正数据库字符串类型nvarchar
                    47. 优化递归子节点
                    48. 修复多表格搜索formId无效
                    49. 其他细节优化
                    v4.4.02020.08.24
                    1. 升级bootstrapTable到最新版本1.17.1
                    2. 升级shiro到最新版1.6.0 阻止权限绕过漏洞
                    3. 升级fastjson到最新版1.2.73
                    4. 代码生成支持同步数据库
                    5. 代码生成支持富文本控件
                    6. 用户密码支持自定义配置规则
                    7. 新增表格自动刷新插件
                    8. 新增表格打印配置插件
                    9. 更换图片裁剪工具为cropper
                    10. Excel支持sort导出排序
                    11. 代码生成支持自定义路径
                    12. 代码生成支持选择上级菜单
                    13. 代码生成支持上传控件
                    14. 新增表格参数(自定义加载文本的字体大小loadingFontSize)
                    15. Excel注解支持设置BigDecimal精度&舍入规则
                    16. 操作日志记录排除敏感属性字段
                    17. 修复不同浏览器附件下载中文名乱码的问题
                    18. 用户分配角色不允许选择超级管理员角色
                    19. 更换表格冻结列插件
                    20. 添加右侧冻结列示例
                    21. 升级表格行编辑&移动端适应插件
                    22. 修复更新表格插件后无法设置实例配置问题
                    23. 修复更新表格插件后导致的主子表错误
                    24. 修复页面存在多表格,回调函数res数据不正确问题
                    25. 强退&过期清理登录账号缓存会话
                    26. 表格树标题内容支持html语义化标签
                    27. 修复配置应用的访问路径首页页签重复问题
                    28. 优化openTab打开时滚动到当前页签
                    29. 表格请求方式method支持自定义配置
                    30. 菜单页签联动优化
                    31. 用户邮箱长度限制修改为50
                    32. 主子表示例添加日期格式案例
                    33. 修改表格行内编辑示例旧值参数
                    34. 操作日志查询方式调整
                    35. 唯一限制条件只返回单条数据
                    36. 修改Excel设置STRING单元格类型
                    37. 添加获取当前的环境配置方法
                    38. 截取返回参数长度,防止超出异常
                    39. 定时任务cron表达式验证
                    40. 拆分表格插件,按需引入
                    41. 多行文本框补齐必填错误提示背景
                    42. 其他细节优化
                    v4.3.12020.07.05
                    1. 国家信息安全漏洞(请务必保持cipherKey密钥唯一性)
                    2. 升级shiro到最新版1.5.3 阻止权限绕过漏洞
                    3. 修改验证码在使用后清除,防止多次使用
                    4. 检查字符支持小数点&降级改成异常提醒
                    5. openOptions函数中加入自定义maxmin属性
                    6. 支持openOptions方法最大化
                    7. 支持openOptions方法多个按钮回调
                    8. 新增isLinkage支持页签与菜单联动
                    9. 修改代码生成导入表结构出现异常页面不提醒问题
                    10. 优化用户头像发生错误,则显示一个默认头像
                    11. Excel导出支持字典类型
                    v4.3.02020.06.22
                    1. 代码生成模板支持主子表
                    2. 代码生成显示类型支持复选框
                    3. 前端表单样式修改成圆角
                    4. 新增回显数据字典(字符串数组)
                    5. 修复浏览器手动缩放比例后菜单无法自适应问题
                    6. 限制用户不允许选择系统管理员角色
                    7. 用户信息添加输入框组图标&鼠标按下显示密码
                    8. 升级fastjson到最新版1.2.70 修复高危安全漏洞
                    9. 升级Bootstrap版本到v3.3.7
                    10. 修复selectColumns方法获取子对象数据无效问题
                    11. 修改数据源类型优先级,先根据方法,再根据类
                    12. 修改上级部门(选择项排除本身和下级)
                    13. 首页菜单显示调整
                    14. 添加是否开启swagger配置
                    15. 新增示例(主子表提交)
                    16. 新增示例(多级联动下拉示例)
                    17. 新增示例(表格属性data数据加载)
                    18. 新增表格列参数(是否列选项可见ignore)
                    19. 新增表格参数(是否启用显示卡片视图cardView)
                    20. 新增表格参数(是否显示全屏按钮showFullscreen)
                    21. 新增表格参数(是否启用分页条无限循环的功能paginationLoop)
                    22. 新增表格参数(是否显示表头showHeader)
                    23. 表格添加显示/隐藏所有列方法 showAllColumns/hideAllColumns
                    24. 修复部分情况节点不展开问题
                    25. 修复关闭标签页后刷新还是上次地址问题
                    26. 修复选择菜单后刷新页面,菜单箭头显示不对问题
                    27. 修复jquery表单序列化时复选框未选中不会序列化到对象中问题
                    28. Excel支持readConverterExp读取字符串组内容
                    29. 更换IP地址查询接口
                    30. 默认关闭获取ip地址
                    31. 操作处理ajaxSuccess判断修正
                    32. HttpUtils.sendPost()方法,参数无需拼接参数到url
                    33. 通用http发送方法增加参数 contentType 编码类型
                    34. HTML过滤器不替换&实体
                    35. 代码生成浮点型改用BigDecimal
                    36. 修复表单构建单选和多选框渲染问题
                    37. 代码生成模板调整,字段为String并且必填则加空串条件
                    38. 字典数据查询列表根据dictSort升序排序
                    39. 修复树表对imageView和tooltip方法无效问题
                    40. 修复Long类型比较相等问题调整
                    41. 示例demo页面清除html链接,防止点击后跳转出现404
                    42. 在线用户强退方法合并
                    43. 添加校验部门包含未停用的子部门
                    44. 取消回车自动提交表单
                    45. 'A','I','BUTTON' 标签忽略clickToSelect事件,防止点击操作按钮时选中
                    46. 邮箱显示截取部分字符串,防止低分辨率错位
                    47. 代码生成列属性根据sort排序
                    48. 修复更多操作部分浏览器不兼容情况
                    49. 图片预览事件属性修正
                    50. 修复冻结列排序样式无效问题
                    51. 修复context-path的情况下个人中心刷新导致样式问题
                    52. 全屏editFull打开适配表树
                    53. 其他细节优化
                    v4.2.02020.03.23
                    1. 用户管理添加分配角色页面
                    2. 定时任务添加调度日志按钮
                    3. 新增是否开启用户注册功能
                    4. 新增页面滚动显示返回顶部按钮
                    5. 用户&角色&任务添加更多操作按钮
                    6. iframe框架页会话过期弹出超时提示
                    7. 移动端登录不显示左侧菜单
                    8. 侧边栏添加一套深蓝色主题
                    9. 首页logo固定,不随菜单滚动
                    10. 支持mode配置history(表示去掉地址栏的#)
                    11. 任务分组字典翻译(调度日志详细)
                    12. 字典管理添加缓存读取
                    13. 字典数据列表标签显示样式
                    14. 参数管理支持缓存操作
                    15. 日期控件清空结束时间设置开始默认值为2099-12-31
                    16. 表格树添加获取数据后响应回调处理
                    17. 批量替换表前缀调整
                    18. 支持表格导入模板的弹窗表单加入其它输入控件
                    19. 表单重置刷新表格树
                    20. 新增支持导出数据字段排序
                    21. 新增表格参数(是否单选checkbox)
                    22. druid未授权不允许访问
                    23. 表格树父节点兼容0,'0','',null
                    24. 表单必填的项添加星号
                    25. 修复select2不显示校验错误信息
                    26. 添加自定义HTML过滤器
                    27. 修复多数据源下开关关闭出现异常问题
                    28. 修复翻页记住选择项数据问题
                    29. 用户邮箱长度限制20
                    30. 修改错误页面返回主页出现嵌套问题
                    31. 表格浮动提示单双引号转义
                    32. 支持配置四级菜单
                    33. 升级shiro到最新版1.4.2 阻止rememberMe漏洞攻击
                    34. 升级summernote到最新版本v0.8.12
                    35. 导入Excel根据dateFormat属性格式处理
                    36. 修复War部署无法正常shutdown,ehcache内存泄漏
                    37. 修复代码生成短字段无法识别问题
                    38. 修复serviceImpl模版,修改方法判断日期错误
                    39. 代码生成模板增加导出功能日志记录
                    40. 代码生成唯一编号调整为tableId
                    41. 代码生成查询时忽略大小写
                    42. 代码生成支持翻页记住选中
                    43. 代码生成表注释未填写也允许导入
                    44. Global全局配置类修改为注解,防止多环境配置下读取问题
                    45. 修复多表格情况下,firstLoad只对第一个表格生效
                    46. 处理Maven打包出现警告问题
                    47. 默认主题样式,防止网速慢情况下出现空白
                    48. 修复文件上传多级目录识别问题
                    49. 锚链接解码url,防止中文导致页面不能加载问题
                    50. 修复右键Tab页刷新事件重复请求问题
                    51. 角色禁用&菜单隐藏不查询权限
                    52. 其他细节优化
                    v4.1.02019.10.22
                    1. 支持多表格实例操作
                    2. 浮动提示方法tooltip支持弹窗
                    3. 代码生成&字典数据支持模糊条件查询
                    4. 增加页签全屏方法
                    5. 增加清除表单验证错误信息方法
                    6. 支持iframe局部刷新页面
                    7. 支持在线切换主题
                    8. 修改图片预览设置的高宽参数颠倒问题
                    9. 操作日志新增解锁账户功能
                    10. 管理员用户&角色不允许操作
                    11. 去掉jsoup包调用自定义转义工具
                    12. 添加时间轴示例
                    13. 修复翻页记住选择时获取指定列值的问题
                    14. 代码生成sql脚本添加导出按钮
                    15. 添加表格父子视图示例
                    16. 添加表格行内编辑示例
                    17. 升级fastjson到最新版1.2.60 阻止漏洞攻击
                    18. 升级echarts到最新版4.2.1
                    19. 操作日志新增返回参数
                    20. 支持mybatis通配符扫描任意多个包
                    21. 权限验证多种情况处理
                    22. 修复树形类型的代码生成的部分必要属性无法显示
                    23. 修复非表格插件情况下重置出现异常
                    24. 修复富文本编辑器有序列表冲突
                    25. 代码生成表前缀配置支持多个
                    26. 修复自动去除表前缀配置无效问题
                    27. 菜单列表按钮数据可见不显示(权限标识控制)
                    28. 修复设置会话超时时间无效问题
                    29. 新增本地资源通用下载方法
                    30. 操作日志记录新增请求方式
                    31. 代码生成单选按钮属性重名修复
                    32. 优化select2下拉框宽度不会随浏览器改变
                    33. 修复代码生成树表异常
                    34. 其他细节优化
                    v4.0.02019.08.08
                    1. 代码生成支持预览、编辑,保存方案
                    2. 新增防止表单重复提交注解
                    3. 新增后端校验(和前端保持一致)
                    4. 新增同一个用户最大会话数控制
                    5. Excel导出子对象支持多个字段
                    6. 定时任务支持静态调用和多参数
                    7. 定时任务增加分组条件查询
                    8. 字典类型增加任务分组数据
                    9. 新增表格是否首次加载数据
                    10. 新增parentTab选项卡可在同一页签打开
                    11. 多数据源支持类注解(允许继承父类的注解)
                    12. 部门及以下数据权限(调整为以下及所有子节点)
                    13. 新增角色数据权限配(仅本人数据权限)
                    14. 修改菜单权限显示问题
                    15. 上传文件修改路径及返回名称
                    16. 添加报表插件及示例
                    17. 添加首页统计模板
                    18. 添加表格拖拽示例
                    19. 添加卡片列表示例
                    20. 添加富文本编辑器示例
                    21. 添加表格动态增删改查示例
                    22. 添加用户页面岗位选择框提示
                    23. 点击菜单操作添加背景高亮显示
                    24. 表格树新增showSearch是否显示检索信息
                    25. 解决表格列设置sortName无效问题
                    26. 表格图片预览支持自定义设置宽高
                    27. 添加表格列浮动提示(单击文本复制)
                    28. PC端收起菜单后支持浮动显示
                    29. 详细操作样式调整
                    30. 修改用户更新描述空串不更新问题
                    31. 导入修改为模板渲染
                    32. 修改菜单及部门排序规则
                    33. 角色导出数据范围表达式翻译
                    34. 添加summernote富文本字体大小
                    35. 优化表格底部下边框防重叠&汇总像素问题
                    36. 树表格支持属性多层级访问
                    37. 修复IE浏览器用户管理界面右侧留白问题
                    38. 重置按钮刷新表格
                    39. 重置密码更新用户缓存
                    40. 优化验证码属性参数
                    41. 支持数据监控配置用户名和密码
                    42. 文件上传修改按钮背景及加载动画
                    43. 支持配置一级菜单href跳转
                    44. 侧边栏添加一套浅色主题
                    45. 树表格添加回调函数(校验异常状态)
                    46. 用户个人中心适配手机端显示
                    47. Excel支持设置导出类型&更换样式
                    48. 检查属性改变修改为克隆方式(防止热部署强转异常)
                    49. 其他细节优化
                    v3.4.02019.06.03
                    1. 新增实例演示菜单及demo
                    2. 新增页签右键操作
                    3. 菜单管理新增打开方式
                    4. 新增点击某行触发的事件
                    5. 新增双击某行触发的事件
                    6. 新增单击某格触发的事件
                    7. 新增双击某格触发的事件
                    8. 新增是否启用显示细节视图
                    9. 支持上传任意格式文件
                    10. 修复角色权限注解失效问题
                    11. 左侧的菜单栏宽度调整
                    12. 新增响应完成后自定义回调函数
                    13. 支持前端及其他模块直接获取用户信息
                    14. 升级swagger到最新版2.9.2
                    15. 升级jquery.slimscroll到最新版1.3.8
                    16. 升级select2到最新版4.0.7
                    17. 新增角色配置本部门数据权限
                    18. 新增角色配置本部门及以下数据权限
                    19. 优化底部操作防止跳到页面顶端
                    20. 修改冻结列选框无效及样式问题
                    21. 修复部门四层级修改祖级无效问题
                    22. 更换开关切换按钮样式
                    23. 新增select2-bootstrap美化下拉框
                    24. 添加表格内图片预览方法
                    25. 修复权限校验失败跳转页面路径错误
                    26. 国际化资源文件调整
                    27. 通知公告布局调整
                    28. 删除页签操作功能
                    29. 表格树新增查询指定列值
                    30. 更改系统接口扫描方式及完善测试案例
                    31. 表格列浮动提示及字典回显默认去背景
                    32. 修复启用翻页记住前面的选择check没选中问题
                    33. 去除监控页面底部的广告
                    34. 日期控件功问题修复及data功能增强
                    35. 新增角色权限可见性(前端直接调用)
                    36. 新增获取当前登录用户方法(前端及子模块调用)
                    37. 修复热部署重启导致菜单丢失问题
                    38. 优化业务校验失败普通请求跳转页面
                    39. 操作日志新增状态条件查询
                    40. 操作类型支持多选条件查询
                    41. 通知公告防止滚动触底回弹优化
                    42. 其他细节优化
                    v3.3.02019.04.01
                    1. 新增线程池统一管理
                    2. 新增支持左右冻结列
                    3. 新增表格字符超长浮动提示
                    4. 升级datepicker拓展并汉化
                    5. 升级druid到最新版本v1.1.14
                    6. 修复个人头像为图片服务器跨域问题
                    7. 修改上传文件按日期存储
                    8. 新增表格客户端分页选项
                    9. 新增表格的高度参数
                    10. 新增表格销毁方法
                    11. 新增表格下拉按钮切换方法
                    12. 新增表格分页跳转到指定页码
                    13. 新增表格启用点击选中行参数
                    14. 修复表格数据重新加载未触发部分按钮禁用
                    15. 使用jsonview展示操作日志参数
                    16. 新增方法(addTab、editTab)
                    17. 修改用户管理界面为Tab打开方式
                    18. 表单验证代码优化
                    19. 修复@Excel注解 prompt 属性使用报错
                    20. 修复combo属性Excel兼容性问题
                    21. 新增@Excel导入导出支持父类字段
                    22. 修复关闭最后选项卡无法激活滚动问题
                    23. 增加日期控件显示类型及回显格式扩展选项
                    24. 修复定时任务执行失败后入库状态为成功状态
                    25. 支持定时任务并发开关控制
                    26. 优化权限校验失败普通请求跳转页面
                    27. 捕获线程池执行任务抛出的异常
                    28. 修复IE浏览器导出功能报错
                    29. 新增角色管理分配用户功能
                    30. 新增表格翻页记住前面的选择
                    31. 调整用户个人中心页面
                    32. 修复界面存在的一些安全问题
                    33. 其他细节优化
                    v3.2.02019.01.18
                    1. 部门修改时不允许选择最后节点
                    2. 修复部门菜单排序字段无效
                    3. 修复光驱磁盘导致服务监控异常
                    4. 登录界面去除check插件
                    5. 验证码文本字符间距修正
                    6. 升级SpringBoot到最新版本2.1.1
                    7. 升级MYSQL驱动
                    8. 修正登录必填项位置偏移
                    9. Session会话检查优化
                    10. Excel注解支持多级获取
                    11. 新增序列号生成方法
                    12. 修复WAR部署tomcat退出线程异常
                    13. 全屏操作增加默认确认/关闭
                    14. 修复个人信息可能导致漏洞
                    15. 字典数据根据下拉选择新增类型
                    16. 升级Summernote到最新版本v0.8.11
                    17. 新增用户数据导入
                    18. 首页主题样式更换
                    19. layer扩展主题更换
                    20. 用户管理移动端默认隐藏左侧布局
                    21. 详细信息弹出层显示在顶层
                    22. 表格支持切换状态(用户/角色/定时任务)
                    23. Druid数据源支持配置继承
                    24. 修正部分iPhone手机端表格适配问题
                    25. 新增防止重复提交表单方法
                    26. 新增表格数据统计汇总方法
                    27. 支持富文本上传图片文件
                    v3.1.02018.12.03
                    1. 新增内网不获取IP地址
                    2. 新增cron表达式有效校验
                    3. 定时任务新增详细信息
                    4. 定时任务默认策略修改(不触发立即执行)
                    5. 定时任务显示下一个执行周期
                    6. 支持前端任意日期格式处理
                    7. 上传头像删除多余提交按钮
                    8. 表格增加行间隔色配置项
                    9. 表格增加转义HTML字符串配置项
                    10. 表格增加显示/隐藏指定列
                    11. 代码生成优化
                    12. 操作日志参数格式化显示
                    13. 页签新增新增全屏显示
                    14. 新增一键打包部署
                    15. Excel注解新增多个参数
                    16. 新增提交静默更新表格方法
                    17. 新增服务监控菜单
                    v3.0.02018.10.08
                    1. 升级poi到最新版3.17
                    2. 导出修改临时目录绝对路径
                    3. 升级laydate到最新版5.0.9
                    4. 升级SpringBoot到最新版本2.0.5
                    5. 优化开始/结束时间校验限制
                    6. 重置密码参数表中获取默认值
                    7. 修复头像修改显示问题
                    8. 新增数据权限过滤注解
                    9. 新增表格检索折叠按钮
                    10. 新增清空(登录、操作、调度)日志
                    11. 固定按钮位置(提交/关闭)
                    12. 部门/菜单支持(展开/折叠)
                    13. 部分细节调整优化
                    14. 项目采用分模块
                    v2.4.02018.09.03
                    1. 支持部门多级查询
                    2. 修复菜单状态查询无效
                    3. 支持IP地址开关
                    4. 支持XSS开关
                    5. 记录日志异步处理
                    6. 字典回显样式更改为下拉框
                    7. 菜单类型必填校验
                    8. 修复在线用户排序报错
                    9. 增加重置按钮
                    10. 支持注解导入数据
                    11. 支持弹层外区域关闭
                    12. 备注更换为文本区域
                    13. 新增角色逻辑删除
                    14. 新增部门逻辑删除
                    15. 支持部门数据权限
                    16. 管理员默认拥有所有授权
                    17. 字典数据采用分页
                    18. 部分细节调整优化
                    v2.3.02018.08.06
                    1. 支持表格不分页开关控制
                    2. 修改字典类型同步修改字典数据
                    3. 代码生成新增修改后缀处理
                    4. 代码生成新增实体toString
                    5. 代码生成非字符串去除!=''
                    6. 导出数据前加载遮罩层
                    7. 部门删除校验条件修改
                    8. 搜索查询下载优化
                    9. 手机打开弹出层自适应
                    10. 角色岗位禁用显示置灰
                    11. 角色禁用不显示菜单
                    12. 新增导出权限
                    13. 角色权限唯一校验
                    14. 岗位名称编码唯一校验
                    15. TreeTable优化
                    16. 支持多数据源
                    17. 其他细节优化
                    v2.2.02018.07.23
                    1. 修复批量生成代码异常问题
                    2. 修复定时器保存失败问题
                    3. 修复热部署转换问题
                    4. 支持查询菜单管理,部门管理
                    5. 大多数功能支持时间查询
                    6. 自定义导出注解自动匹配column
                    7. 新增任务执行策略
                    8. 操作详细动态显示类型
                    9. 支持动态回显字典数据
                    10. 后台代码优化调整
                    11. 其他细节优化
                    v2.1.02018.07.10
                    1. 新增登录超时提醒
                    2. 修复定时器热部署转换问题
                    3. 修复登录验证码校验无效问题
                    4. 定时任务新增立即执行一次
                    5. 存在字典数据不允许删除字典
                    6. 字典数据支持按名称查询
                    7. 代码生成增加日志注解&表格优化
                    8. 修复用户逻辑删除后能登录问题
                    9. 表格支持多字段动态排序
                    10. 支持三级菜单显示
                    11. 新增ry.sh启动程序脚本
                    12. 其他细节优化
                    v2.0.02018.07.02
                    1. 升级SpringBoot到最新版本2.0.3
                    2. 新增公告管理
                    3. 表单校验示提体验优化
                    4. 前端通用方法封装调整
                    5. 前端去除js文件,合并到html
                    6. 操作加载遮罩层
                    7. 支持全屏模式操作
                    8. 支持注解导出数据
                    9. 系统支持多查询&下载
                    10. 系统样式调整
                    v1.1.62018.06.04
                    1. 新增用户列表部门列
                    2. 新增登录地点
                    3. 新增swagger
                    4. 修复排序数字校验
                    5. 优化头像上传文件类型限定为图片
                    6. 新增XSS过滤
                    7. 新增热部署提高开发效率
                    8. 修复treegrid居中无效
                    9. 角色多条件查询
                    v1.1.52018.05.28
                    1. 优化登录失败刷新验证码
                    2. 新增用户登录地址时间
                    3. 修复ajax超时退出问题
                    4. 新增html调用数据字典(若依首创)
                    5. 调整系统部分样式
                    6. 新增用户逻辑删除
                    7. 新增管理员不允许删除修改
                    8. 升级bootstrapTable到最新版本1.12.1
                    9. 升级layer到最新版本3.1.1
                    v1.1.42018.05.20
                    1. 新增参数管理
                    2. 修复头像上传bug
                    3. 手机邮箱唯一校验
                    4. 支持手机邮箱登录
                    5. 代码生成优化
                    6. 支持模糊查询
                    7. 支持切换主题皮肤
                    8. 修改权限即时生效
                    9. 修复页签Tab关闭问题
                    v1.1.32018.05.14
                    1. 新增验证码(数组计算、字符验证)
                    2. 新增cookie记住我
                    3. 新增头像上传
                    4. 用户名密码长度限制
                    5. 通用字段提取
                    6. 支持自定义条件查询
                    7. 部门名称必填、时间格式调整
                    8. 其他细节优化
                    v1.1.22018.05.07
                    1. 新增个人信息修改
                    2. 菜单存在子菜单不允许删除
                    3. 菜单分配角色不允许删除
                    4. 角色分配人员不允许删除
                    5. 岗位使用后不允许删除
                    6. 保证用户的数据完整性加入事物
                    7. 新增环境使用手册、数据建模
                    8. Thymeleaf升级到3.0
                    9. 支持非ROOT部署
                    v1.1.12018.04.23
                    1. 新增表单构建器
                    2. 代码生成优化
                    3. 支持新增主部门
                    4. 支持选择上级部门、上级菜单
                    5. 新增字典管理单条删除
                    6. 优化一些其他细节
                    v1.1.02018.04.20
                    1. 支持密码盐
                    2. 支持新增主目录
                    3. 支持批量生成代码
                    4. 支持表格导出(csv、txt、doc、excel)
                    5. 自动适应宽高模式窗体
                    6. 重复校验(角色名、菜单名、部门名)
                    7. 优化一些其他细节
                    v1.0.92018.04.14
                    1. 新增代码生成(生成包括 java、html、js、xml、sql)
                    2. 新增按钮权限控制隐藏(若依首创)
                    v1.0.82018.04.08
                    1. 新增定时任务(新增、修改、删除、查询、启动/暂停)
                    2. 新增调度日志(查询、删除)
                    v1.0.72018.04.04
                    1. 新增岗位管理(新增、修改、删除、查询)
                    2. 优化用户管理,菜单管理部分细节
                    v1.0.62018.03.15
                    1. 新增字典管理(新增、删除、修改、查询、数据选择)
                    2. 新增用户密码重置
                    3. 优化一些其他细节
                    v1.0.52018.03.12
                    1. 新增菜单管理(新增、删除、修改、查询、图标选择)
                    2. 部门管理优化(添加责任人、联系电话、邮箱、修改者)
                    v1.0.42018.03.11
                    1. 新增角色管理(新增、删除、修改、查询、菜单选择)
                    v1.0.32018.03.08
                    1. 新增用户管理(新增、删除、修改、查询、部门选择)
                    v1.0.22018.03.04
                    1. 新增部门管理 (新增、删除、修改、查询)
                    v1.0.12018.03.03
                    1. 新增在线用户 (批量强退、单条强退、查询)
                    2. 新增登录日志 (批量删除、查询)
                    3. 新增操作日志 (批量删除、查询、详细)
                    4. 新增数据监控 (监控DB池连接和SQL的执行)

                    v1.0.02018.03.01

                    1. 若依管理系统正式发布。
                    捐赠
                    请作者喝杯咖啡(点击图片放大)

                    请使用手机支付宝或者微信扫码支付

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/main_v1.html ================================================ 统计
                    收入

                    40 886,200

                    98%
                    总收入
                    全年
                    订单

                    275,800

                    20%
                    新订单
                    今天
                    访客

                    106,120

                    44%
                    新访客
                    最近一个月
                    活跃用户

                    80,600

                    38%
                    12月
                    订单
                    • 2,346

                      订单总数
                      48%
                    • 4,422

                      最近一个月订单
                      60%
                    • 9,180

                      最近一个月销售额
                      22%
                    用户项目列表
                    状态 日期 用户
                    进行中... 11:20 青衣5858 24%
                    已取消 10:40 徐子崴 66%
                    进行中... 01:30 姜岚昕 54%
                    进行中... 02:20 武汉大兵哥 12%
                    进行中... 09:40 荆莹儿 22%
                    已完成 04:10 栾某某 66%
                    进行中... 12:08 范范范二妮 23%
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/monitor/cache/cache.html ================================================
                    缓存列表
                    缓存名称 操作
                    [[${stat.index + 1}]] [[${cacheName}]]
                    键名列表
                    缓存键名 操作
                    [[${stat.index + 1}]] [[${cacheKey}]]
                    缓存内容
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/monitor/logininfor/logininfor.html ================================================
                    • -
                    •  搜索  重置
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/monitor/online/online.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/monitor/operlog/detail.html ================================================
                    基本信息
                    操作模块 - 业务类型 -
                    操作时间 - 执行状态 正常 异常
                    操作人员
                    操作人员 -
                    所属部门 -
                    操作地址 -  
                    请求信息
                    请求地址 POST -
                    操作方法 -
                    消耗时间 0 毫秒
                    请求参数
                    
                                
                    返回参数
                    
                                
                    异常信息
                    -
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/monitor/operlog/operlog.html ================================================
                    • -
                    •  搜索  重置
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/monitor/server/server.html ================================================
                    CPU
                    属性
                    核心数 0个
                    用户使用率 0%
                    系统使用率 0%
                    当前空闲率 0%
                    内存
                    属性 内存 JVM
                    总内存 0GB 0MB
                    已用内存 0GB 0MB
                    剩余内存 0GB 0MB
                    使用率 [[${server.mem.usage}]]% [[${server.jvm.usage}]]%
                    服务器信息
                    服务器名称 RuoYi 操作系统 Linux
                    服务器IP 127.0.0.1 系统架构 amd64
                    Java虚拟机信息
                    Java名称 Java Java版本 1.8.0
                    启动时间 2018-12-31 00:00:00 运行时长 0天0时0分0秒
                    安装路径
                    项目路径
                    运行参数
                    磁盘状态
                    盘符路径 文件系统 盘符类型 总大小 可用大小 已用大小 已用百分比
                    C:\ NTFS local 0GB 0GB 0GB [[${sysFile.usage}]]%
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/register.html ================================================ 注册若依系统

                    注册:

                    你若不离不弃,我必生死相依

                    使用条款
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/skin.html ================================================ 主题选择
                    • 绿

                    • 蓝灰

                    • 绿灰

                    • 紫灰

                    • 红灰

                    • 黄灰

                    • 蓝浅

                    • 绿浅

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/config/add.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/config/config.html ================================================
                    • 参数名称:
                    • 参数键名:
                    • 系统内置:
                    • -
                    •  搜索  重置
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/config/edit.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dept/add.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dept/dept.html ================================================ ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dept/edit.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dept/tree.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dict/data/add.html ================================================
                    table表格字典列显示样式属性
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dict/data/data.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dict/data/edit.html ================================================
                    table表格字典列显示样式属性
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dict/type/add.html ================================================
                    数据存储中的Key值,如:sys_user_sex
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dict/type/edit.html ================================================
                    数据存储中的Key值,如:sys_user_sex
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dict/type/tree.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/dict/type/type.html ================================================
                    • 字典名称:
                    • 字典类型:
                    • 字典状态:
                    • -
                    •  搜索  重置
                    字典数据
                    加载中...
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/menu/add.html ================================================
                    控制器中定义的权限标识,如:@RequiresPermissions("")
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/menu/edit.html ================================================
                    控制器中定义的权限标识,如:@RequiresPermissions("")
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/menu/icon.html ================================================ Font Awesome Ico list
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/menu/menu.html ================================================ ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/menu/tree.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/notice/add.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/notice/edit.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/notice/notice.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/notice/view.html ================================================
                    通知 公告 消息

                    公告标题

                    发布人 发布时间 状态
                    暂无内容
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/post/add.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/post/edit.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/post/post.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/role/add.html ================================================
                    控制器中定义的权限字符,如:@RequiresRoles("")
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/role/authUser.html ================================================ ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/role/dataScope.html ================================================
                    特殊情况下,设置为“自定数据权限”
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/role/edit.html ================================================
                    控制器中定义的权限字符,如:@RequiresRoles("")
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/role/role.html ================================================
                    • 角色名称:
                    • 权限字符:
                    • 角色状态:
                    • -
                    •  搜索  重置
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/role/selectUser.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/role/view.html ================================================

                    基本信息

                    正常 停用

                    全部数据权限 自定义数据权限 本部门数据权限 本部门及以下数据权限 仅本人数据权限

                    数据权限

                    全部数据权限 — 可查看系统内所有数据 自定义数据权限 — 仅可查看下方勾选部门的数据 本部门数据权限 — 仅可查看本部门数据 本部门及以下数据权限 — 可查看本部门及其下级部门数据 仅本人数据权限 — 仅可查看本人数据

                    加载中...

                    菜单权限

                    0 个菜单权限   查看已分配菜单

                    关联用户

                    0 个关联用户   查看已分配用户
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/add.html ================================================

                    基本信息

                    其他信息

                     
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/authRole.html ================================================

                    基本信息

                    分配角色

                     
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/deptTree.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/edit.html ================================================

                    基本信息

                    其他信息

                     
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/profile/avatar.html ================================================ ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/profile/profile.html ================================================
                    个人资料
                    • 登录名称:

                      [[${user.loginName}]]

                    • 手机号码:

                      [[${user.phonenumber}]]

                    • 所属部门:

                      [[${user.dept?.deptName}]] / [[${#strings.defaultString(postGroup,'无岗位')}]]

                    • 邮箱地址:

                      [[${#strings.abbreviate(user.email, 16)}]]

                    • 创建时间:

                      [[${#dates.format(user.createTime, 'yyyy-MM-dd')}]]

                    基本资料
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/profile/resetPwd.html ================================================
                    密码只能为0-9数字 密码只能为a-z和A-Z字母 密码必须包含(字母,数字) 密码必须包含(字母,数字,特殊字符!@#$%^&*()-=_+)
                    请再次输入您的密码
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/resetPwd.html ================================================
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/user.html ================================================
                    组织机构
                    • 登录名称:
                    • 手机号码:
                    • 用户状态:
                    • -
                    •  搜索  重置
                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/system/user/view.html ================================================

                    基本信息

                    [[${#strings.defaultString(postGroup, '无岗位')}]]

                    [[${#strings.defaultString(roleGroup, '无角色')}]]

                    其他信息

                    ================================================ FILE: ruoyi-admin/src/main/resources/templates/tool/build/build.html ================================================
                    拖拽左侧的表单元素到右侧区域,即可生成相应的HTML代码,表单代码,轻松搞定!

                    这里是纯文字信息

                    拖拽左侧表单元素到此区域
                    请选择显示的列数:
                    ================================================ FILE: ruoyi-common/pom.xml ================================================ ruoyi com.ruoyi 4.8.2 4.0.0 ruoyi-common common通用工具 org.springframework spring-context-support org.springframework spring-web org.apache.shiro shiro-core jakarta org.apache.shiro shiro-ehcache com.github.pagehelper pagehelper-spring-boot-starter org.springframework.boot spring-boot-starter-validation org.apache.commons commons-lang3 com.fasterxml.jackson.core jackson-databind com.alibaba fastjson commons-io commons-io org.apache.poi poi-ooxml nl.basjes.parse.useragent yauaa jakarta.servlet jakarta.servlet-api ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java ================================================ package com.ruoyi.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 匿名访问不鉴权注解 * * @author ruoyi */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Anonymous { } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataScope.java ================================================ package com.ruoyi.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 数据权限过滤注解 * * @author ruoyi */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataScope { /** * 部门表的别名 */ public String deptAlias() default ""; /** * 用户表的别名 */ public String userAlias() default ""; /** * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@RequiresPermissions获取,多个权限用逗号分隔开来 */ public String permission() default ""; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/annotation/DataSource.java ================================================ package com.ruoyi.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.ruoyi.common.enums.DataSourceType; /** * 自定义多数据源切换注解 * * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 * * @author ruoyi */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource { /** * 切换数据源名称 */ public DataSourceType value() default DataSourceType.MASTER; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java ================================================ package com.ruoyi.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.math.BigDecimal; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; import com.ruoyi.common.utils.poi.ExcelHandlerAdapter; /** * 自定义导出Excel数据注解 * * @author ruoyi */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Excel { /** * 导出时在excel中排序 */ public int sort() default Integer.MAX_VALUE; /** * 导出到Excel中的名字. */ public String name() default ""; /** * 日期格式, 如: yyyy-MM-dd */ public String dateFormat() default ""; /** * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) */ public String dictType() default ""; /** * 读取内容转表达式 (如: 0=男,1=女,2=未知) */ public String readConverterExp() default ""; /** * 分隔符,读取字符串组内容 */ public String separator() default ","; /** * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) */ public int scale() default -1; /** * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN */ @SuppressWarnings("deprecation") public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; /** * 导出时在excel中每个列的高度 */ public double height() default 14; /** * 导出时在excel中每个列的宽度 */ public double width() default 16; /** * 文字后缀,如% 90 变成90% */ public String suffix() default ""; /** * 当值为空时,字段的默认值 */ public String defaultValue() default ""; /** * 提示信息 */ public String prompt() default ""; /** * 是否允许内容换行 */ public boolean wrapText() default false; /** * 设置只能选择不能输入的列内容. */ public String[] combo() default {}; /** * 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解. */ public boolean comboReadDict() default false; /** * 是否需要纵向合并单元格,应对需求:含有list集合单元格) */ public boolean needMerge() default false; /** * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. */ public boolean isExport() default true; /** * 另一个类中的属性名称,支持多级获取,以小数点隔开 */ public String targetAttr() default ""; /** * 是否自动统计数据,在最后追加一行统计数据总和 */ public boolean isStatistics() default false; /** * 导出类型(0数字 1字符串 2图片) */ public ColumnType cellType() default ColumnType.STRING; /** * 导出列头背景颜色 */ public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; /** * 导出列头字体颜色 */ public IndexedColors headerColor() default IndexedColors.WHITE; /** * 导出单元格背景颜色 */ public IndexedColors backgroundColor() default IndexedColors.WHITE; /** * 导出单元格字体颜色 */ public IndexedColors color() default IndexedColors.BLACK; /** * 导出字段对齐方式 */ public HorizontalAlignment align() default HorizontalAlignment.CENTER; /** * 自定义数据处理器 */ public Class handler() default ExcelHandlerAdapter.class; /** * 自定义数据处理器参数 */ public String[] args() default {}; /** * 字段类型(0:导出导入;1:仅导出;2:仅导入) */ Type type() default Type.ALL; public enum Type { ALL(0), EXPORT(1), IMPORT(2); private final int value; Type(int value) { this.value = value; } public int value() { return this.value; } } public enum ColumnType { NUMERIC(0), STRING(1), IMAGE(2), TEXT(3); private final int value; ColumnType(int value) { this.value = value; } public int value() { return this.value; } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excels.java ================================================ package com.ruoyi.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Excel注解集 * * @author ruoyi */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Excels { Excel[] value(); } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java ================================================ package com.ruoyi.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.OperatorType; /** * 自定义操作日志记录注解 * * @author ruoyi */ @Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** * 模块 */ public String title() default ""; /** * 功能 */ public BusinessType businessType() default BusinessType.OTHER; /** * 操作人类别 */ public OperatorType operatorType() default OperatorType.MANAGE; /** * 是否保存请求的参数 */ public boolean isSaveRequestData() default true; /** * 是否保存响应的参数 */ public boolean isSaveResponseData() default true; /** * 排除指定的请求参数 */ public String[] excludeParamNames() default {}; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java ================================================ package com.ruoyi.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义注解防止表单重复提交 * * @author ruoyi * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatSubmit { /** * 间隔时间(ms),小于此时间视为重复提交 */ public int interval() default 5000; /** * 提示消息 */ public String message() default "不允许重复提交,请稍后再试"; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/annotation/Sensitive.java ================================================ package com.ruoyi.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.ruoyi.common.config.serializer.SensitiveJsonSerializer; import com.ruoyi.common.enums.DesensitizedType; /** * 数据脱敏注解 * * @author ruoyi */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JacksonAnnotationsInside @JsonSerialize(using = SensitiveJsonSerializer.class) public @interface Sensitive { DesensitizedType desensitizedType(); } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java ================================================ package com.ruoyi.common.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 全局配置类 * * @author ruoyi */ @Component @ConfigurationProperties(prefix = "ruoyi") public class RuoYiConfig { /** 项目名称 */ private static String name; /** 版本 */ private static String version; /** 版权年份 */ private static String copyrightYear; /** 实例演示开关 */ private static boolean demoEnabled; /** 上传路径 */ private static String profile; /** 获取地址开关 */ private static boolean addressEnabled; public static String getName() { return name; } public void setName(String name) { RuoYiConfig.name = name; } public static String getVersion() { return version; } public void setVersion(String version) { RuoYiConfig.version = version; } public static String getCopyrightYear() { return copyrightYear; } public void setCopyrightYear(String copyrightYear) { RuoYiConfig.copyrightYear = copyrightYear; } public static boolean isDemoEnabled() { return demoEnabled; } public void setDemoEnabled(boolean demoEnabled) { RuoYiConfig.demoEnabled = demoEnabled; } public static String getProfile() { return profile; } public void setProfile(String profile) { RuoYiConfig.profile = profile; } public static boolean isAddressEnabled() { return addressEnabled; } public void setAddressEnabled(boolean addressEnabled) { RuoYiConfig.addressEnabled = addressEnabled; } /** * 获取导入上传路径 */ public static String getImportPath() { return getProfile() + "/import"; } /** * 获取头像上传路径 */ public static String getAvatarPath() { return getProfile() + "/avatar"; } /** * 获取下载路径 */ public static String getDownloadPath() { return getProfile() + "/download/"; } /** * 获取上传路径 */ public static String getUploadPath() { return getProfile() + "/upload"; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/config/ServerConfig.java ================================================ package com.ruoyi.common.config; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; import com.ruoyi.common.utils.ServletUtils; /** * 服务相关配置 * * @author ruoyi * */ @Component public class ServerConfig { /** * 获取完整的请求路径,包括:域名,端口,上下文访问路径 * * @return 服务地址 */ public String getUrl() { HttpServletRequest request = ServletUtils.getRequest(); return getDomain(request); } public static String getDomain(HttpServletRequest request) { StringBuffer url = request.getRequestURL(); String contextPath = request.getServletContext().getContextPath(); return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/config/datasource/DynamicDataSourceContextHolder.java ================================================ package com.ruoyi.common.config.datasource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 数据源切换处理 * * @author ruoyi */ public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 */ private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); /** * 设置数据源的变量 */ public static void setDataSourceType(String dsType) { log.info("切换到{}数据源", dsType); CONTEXT_HOLDER.set(dsType); } /** * 获得数据源的变量 */ public static String getDataSourceType() { return CONTEXT_HOLDER.get(); } /** * 清空数据源变量 */ public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/config/serializer/SensitiveJsonSerializer.java ================================================ package com.ruoyi.common.config.serializer; import java.io.IOException; import java.util.Objects; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.ruoyi.common.annotation.Sensitive; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.enums.DesensitizedType; import com.ruoyi.common.utils.ShiroUtils; /** * 数据脱敏序列化过滤 * * @author ruoyi */ public class SensitiveJsonSerializer extends JsonSerializer implements ContextualSerializer { private DesensitizedType desensitizedType; @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (desensitization()) { gen.writeString(desensitizedType.desensitizer().apply(value)); } else { gen.writeString(value); } } @Override public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { Sensitive annotation = property.getAnnotation(Sensitive.class); if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) { this.desensitizedType = annotation.desensitizedType(); return this; } return prov.findValueSerializer(property.getType(), property); } /** * 是否需要脱敏处理 */ private boolean desensitization() { SysUser securityUser = ShiroUtils.getSysUser(); if (securityUser == null) { return true; } // 管理员不脱敏 return !securityUser.isAdmin(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/config/thread/ThreadPoolConfig.java ================================================ package com.ruoyi.common.config.thread; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import com.ruoyi.common.utils.Threads; /** * 线程池配置 * * @author ruoyi **/ @Configuration public class ThreadPoolConfig { // 核心线程池大小 private int corePoolSize = 50; // 最大可创建的线程数 private int maxPoolSize = 200; // 队列最大长度 private int queueCapacity = 1000; // 线程池维护线程所允许的空闲时间 private int keepAliveSeconds = 300; @Bean(name = "threadPoolTaskExecutor") public ThreadPoolTaskExecutor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setMaxPoolSize(maxPoolSize); executor.setCorePoolSize(corePoolSize); executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(keepAliveSeconds); // 线程池对拒绝任务(无线程可用)的处理策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } /** * 执行周期性或定时任务 */ @Bean(name = "scheduledExecutorService") protected ScheduledExecutorService scheduledExecutorService() { return new ScheduledThreadPoolExecutor(corePoolSize, BasicThreadFactory.builder().namingPattern("schedule-pool-%d").daemon(true).build(), new ThreadPoolExecutor.CallerRunsPolicy()) { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); Threads.printException(r, t); } }; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java ================================================ package com.ruoyi.common.constant; import java.util.Locale; /** * 通用常量信息 * * @author ruoyi */ public class Constants { /** * UTF-8 字符集 */ public static final String UTF8 = "UTF-8"; /** * GBK 字符集 */ public static final String GBK = "GBK"; /** * 系统语言 */ public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; /** * http请求 */ public static final String HTTP = "http://"; /** * https请求 */ public static final String HTTPS = "https://"; /** * 通用成功标识 */ public static final String SUCCESS = "0"; /** * 通用失败标识 */ public static final String FAIL = "1"; /** * 登录成功 */ public static final String LOGIN_SUCCESS = "Success"; /** * 注销 */ public static final String LOGOUT = "Logout"; /** * 注册 */ public static final String REGISTER = "Register"; /** * 登录失败 */ public static final String LOGIN_FAIL = "Error"; /** * 系统用户授权缓存 */ public static final String SYS_AUTH_CACHE = "sys-authCache"; /** * 参数管理 cache name */ public static final String SYS_CONFIG_CACHE = "sys-config"; /** * 参数管理 cache key */ public static final String SYS_CONFIG_KEY = "sys_config:"; /** * 字典管理 cache name */ public static final String SYS_DICT_CACHE = "sys-dict"; /** * 字典管理 cache key */ public static final String SYS_DICT_KEY = "sys_dict:"; /** * 资源映射路径 前缀 */ public static final String RESOURCE_PREFIX = "/profile"; /** * RMI 远程方法调用 */ public static final String LOOKUP_RMI = "rmi:"; /** * LDAP 远程方法调用 */ public static final String LOOKUP_LDAP = "ldap:"; /** * LDAPS 远程方法调用 */ public static final String LOOKUP_LDAPS = "ldaps:"; /** * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) */ public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" }; /** * 定时任务违规的字符 */ public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" }; /** * 部门相关常量 */ public static class Dept { /** * 全部数据权限 */ public static final String DATA_SCOPE_ALL = "1"; /** * 自定数据权限 */ public static final String DATA_SCOPE_CUSTOM = "2"; /** * 部门数据权限 */ public static final String DATA_SCOPE_DEPT = "3"; /** * 部门及以下数据权限 */ public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; /** * 仅本人数据权限 */ public static final String DATA_SCOPE_SELF = "5"; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/constant/GenConstants.java ================================================ package com.ruoyi.common.constant; /** * 代码生成通用常量 * * @author ruoyi */ public class GenConstants { /** 单表(增删改查) */ public static final String TPL_CRUD = "crud"; /** 树表(增删改查) */ public static final String TPL_TREE = "tree"; /** 主子表(增删改查) */ public static final String TPL_SUB = "sub"; /** 树编码字段 */ public static final String TREE_CODE = "treeCode"; /** 树父编码字段 */ public static final String TREE_PARENT_CODE = "treeParentCode"; /** 树名称字段 */ public static final String TREE_NAME = "treeName"; /** 上级菜单ID字段 */ public static final String PARENT_MENU_ID = "parentMenuId"; /** 上级菜单名称字段 */ public static final String PARENT_MENU_NAME = "parentMenuName"; /** 数据库字符串类型 */ public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; /** 数据库文本类型 */ public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" }; /** 数据库时间类型 */ public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; /** 数据库数字类型 */ public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", "bit", "bigint", "float", "double", "decimal" }; /** 页面不需要编辑字段 */ public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; /** 页面不需要显示的列表字段 */ public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by", "update_time" }; /** 页面不需要查询字段 */ public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by", "update_time", "remark" }; /** Entity基类字段 */ public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; /** Tree基类字段 */ public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors" }; /** 文本框 */ public static final String HTML_INPUT = "input"; /** 文本域 */ public static final String HTML_TEXTAREA = "textarea"; /** 下拉框 */ public static final String HTML_SELECT = "select"; /** 单选框 */ public static final String HTML_RADIO = "radio"; /** 复选框 */ public static final String HTML_CHECKBOX = "checkbox"; /** 日期控件 */ public static final String HTML_DATETIME = "datetime"; /** 上传控件 */ public static final String HTML_UPLOAD = "upload"; /** 富文本控件 */ public static final String HTML_SUMMERNOTE = "summernote"; /** 字符串类型 */ public static final String TYPE_STRING = "String"; /** 整型 */ public static final String TYPE_INTEGER = "Integer"; /** 长整型 */ public static final String TYPE_LONG = "Long"; /** 浮点型 */ public static final String TYPE_DOUBLE = "Double"; /** 高精度计算类型 */ public static final String TYPE_BIGDECIMAL = "BigDecimal"; /** 时间类型 */ public static final String TYPE_DATE = "Date"; /** 模糊查询 */ public static final String QUERY_LIKE = "LIKE"; /** 相等查询 */ public static final String QUERY_EQ = "EQ"; /** 需要 */ public static final String REQUIRE = "1"; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/constant/PermissionConstants.java ================================================ package com.ruoyi.common.constant; /** * 权限通用常量 * * @author ruoyi */ public class PermissionConstants { /** 新增权限 */ public static final String ADD_PERMISSION = "add"; /** 修改权限 */ public static final String EDIT_PERMISSION = "edit"; /** 删除权限 */ public static final String REMOVE_PERMISSION = "remove"; /** 导出权限 */ public static final String EXPORT_PERMISSION = "export"; /** 显示权限 */ public static final String VIEW_PERMISSION = "view"; /** 查询权限 */ public static final String LIST_PERMISSION = "list"; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java ================================================ package com.ruoyi.common.constant; /** * 任务调度通用常量 * * @author ruoyi */ public class ScheduleConstants { public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; /** 执行目标key */ public static final String TASK_PROPERTIES = "TASK_PROPERTIES"; /** 默认 */ public static final String MISFIRE_DEFAULT = "0"; /** 立即触发执行 */ public static final String MISFIRE_IGNORE_MISFIRES = "1"; /** 触发一次执行 */ public static final String MISFIRE_FIRE_AND_PROCEED = "2"; /** 不触发立即执行 */ public static final String MISFIRE_DO_NOTHING = "3"; public enum Status { /** * 正常 */ NORMAL("0"), /** * 暂停 */ PAUSE("1"); private String value; private Status(String value) { this.value = value; } public String getValue() { return value; } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/constant/ShiroConstants.java ================================================ package com.ruoyi.common.constant; /** * Shiro通用常量 * * @author ruoyi */ public class ShiroConstants { /** * 当前登录的用户 */ public static final String CURRENT_USER = "currentUser"; /** * 用户名字段 */ public static final String CURRENT_USERNAME = "username"; /** * 锁定屏幕字段 */ public static final String LOCK_SCREEN = "lockscreen"; /** * 消息key */ public static final String MESSAGE = "message"; /** * 错误key */ public static final String ERROR = "errorMsg"; /** * csrf session content */ public static final String CSRF_TOKEN = "csrf_token"; /** * csrf request header */ public static final String X_CSRF_TOKEN = "X-CSRF-Token"; /** * 当前在线会话 */ public static final String ONLINE_SESSION = "online_session"; /** * 验证码key */ public static final String CURRENT_CAPTCHA = "captcha"; /** * 验证码开关 */ public static final String CURRENT_ENABLED = "captchaEnabled"; /** * 验证码类型 */ public static final String CURRENT_TYPE = "captchaType"; /** * 验证码 */ public static final String CURRENT_VALIDATECODE = "validateCode"; /** * 验证码错误 */ public static final String CAPTCHA_ERROR = "captchaError"; /** * 登录记录缓存 */ public static final String LOGIN_RECORD_CACHE = "loginRecordCache"; /** * 系统活跃用户缓存 */ public static final String SYS_USERCACHE = "sys-userCache"; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java ================================================ package com.ruoyi.common.constant; /** * 用户常量信息 * * @author ruoyi */ public class UserConstants { /** * 平台内系统用户的唯一标志 */ public static final String SYS_USER = "SYS_USER"; /** 正常状态 */ public static final String NORMAL = "0"; /** 异常状态 */ public static final String EXCEPTION = "1"; /** 用户封禁状态 */ public static final String USER_DISABLE = "1"; /** 角色正常状态 */ public static final String ROLE_NORMAL = "0"; /** 角色封禁状态 */ public static final String ROLE_DISABLE = "1"; /** 部门正常状态 */ public static final String DEPT_NORMAL = "0"; /** 部门停用状态 */ public static final String DEPT_DISABLE = "1"; /** 字典正常状态 */ public static final String DICT_NORMAL = "0"; /** 是否为系统默认(是) */ public static final String YES = "Y"; /** 是否唯一的返回标识 */ public final static boolean UNIQUE = true; public final static boolean NOT_UNIQUE = false; /** * 用户名长度限制 */ public static final int USERNAME_MIN_LENGTH = 2; public static final int USERNAME_MAX_LENGTH = 20; /** * 密码长度限制 */ public static final int PASSWORD_MIN_LENGTH = 5; public static final int PASSWORD_MAX_LENGTH = 20; /** * 用户类型 */ public static final String SYSTEM_USER_TYPE = "00"; public static final String REGISTER_USER_TYPE = "01"; /** * 手机号码格式限制 */ public static final String MOBILE_PHONE_NUMBER_PATTERN = "^0{0,1}(13[0-9]|15[0-9]|14[0-9]|18[0-9])[0-9]{8}$"; /** * 邮箱格式限制 */ public static final String EMAIL_PATTERN = "^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?"; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/context/PermissionContextHolder.java ================================================ package com.ruoyi.common.core.context; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import com.ruoyi.common.core.text.Convert; /** * 权限信息 * * @author ruoyi */ public class PermissionContextHolder { private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; public static void setContext(String permission) { RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, RequestAttributes.SCOPE_REQUEST); } public static String getContext() { return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST)); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java ================================================ package com.ruoyi.common.core.controller; import java.beans.PropertyEditorSupport; import java.util.Date; import java.util.List; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult.Type; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.page.PageDomain; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableSupport; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.PageUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.sql.SqlUtil; /** * web层通用数据处理 * * @author ruoyi */ public class BaseController { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 将前台传递过来的日期格式的字符串,自动转化为Date类型 */ @InitBinder public void initBinder(WebDataBinder binder) { // Date 类型转换 binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { @Override public void setAsText(String text) { setValue(DateUtils.parseDate(text)); } }); } /** * 设置请求分页数据 */ protected void startPage() { PageUtils.startPage(); } /** * 设置请求排序数据 */ protected void startOrderBy() { PageDomain pageDomain = TableSupport.buildPageRequest(); if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) { String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); PageHelper.orderBy(orderBy); } } /** * 清理分页的线程变量 */ protected void clearPage() { PageUtils.clearPage(); } /** * 获取request */ public HttpServletRequest getRequest() { return ServletUtils.getRequest(); } /** * 获取response */ public HttpServletResponse getResponse() { return ServletUtils.getResponse(); } /** * 获取session */ public HttpSession getSession() { return getRequest().getSession(); } /** * 响应请求分页数据 */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected TableDataInfo getDataTable(List list) { TableDataInfo rspData = new TableDataInfo(); rspData.setCode(0); rspData.setRows(list); rspData.setTotal(new PageInfo(list).getTotal()); return rspData; } /** * 响应返回结果 * * @param rows 影响行数 * @return 操作结果 */ protected AjaxResult toAjax(int rows) { return rows > 0 ? success() : error(); } /** * 响应返回结果 * * @param result 结果 * @return 操作结果 */ protected AjaxResult toAjax(boolean result) { return result ? success() : error(); } /** * 返回成功 */ public AjaxResult success() { return AjaxResult.success(); } /** * 返回失败消息 */ public AjaxResult error() { return AjaxResult.error(); } /** * 返回成功消息 */ public AjaxResult success(String message) { return AjaxResult.success(message); } /** * 返回成功数据 */ public static AjaxResult success(Object data) { return AjaxResult.success("操作成功", data); } /** * 返回失败消息 */ public AjaxResult error(String message) { return AjaxResult.error(message); } /** * 返回错误码消息 */ public AjaxResult error(Type type, String message) { return new AjaxResult(type, message); } /** * 页面跳转 */ public String redirect(String url) { return StringUtils.format("redirect:{}", url); } /** * 获取用户缓存信息 */ public SysUser getSysUser() { return ShiroUtils.getSysUser(); } /** * 设置用户缓存信息 */ public void setSysUser(SysUser user) { ShiroUtils.setSysUser(user); } /** * 获取登录用户id */ public Long getUserId() { return getSysUser().getUserId(); } /** * 获取登录用户名 */ public String getLoginName() { return getSysUser().getLoginName(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java ================================================ package com.ruoyi.common.core.domain; import java.util.HashMap; import java.util.Objects; import com.ruoyi.common.utils.StringUtils; /** * 操作消息提醒 * * @author ruoyi */ public class AjaxResult extends HashMap { private static final long serialVersionUID = 1L; /** 状态码 */ public static final String CODE_TAG = "code"; /** 返回内容 */ public static final String MSG_TAG = "msg"; /** 数据对象 */ public static final String DATA_TAG = "data"; /** * 状态类型 */ public enum Type { /** 成功 */ SUCCESS(0), /** 警告 */ WARN(301), /** 错误 */ ERROR(500); private final int value; Type(int value) { this.value = value; } public int value() { return this.value; } } /** * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 */ public AjaxResult() { } /** * 初始化一个新创建的 AjaxResult 对象 * * @param type 状态类型 * @param msg 返回内容 */ public AjaxResult(Type type, String msg) { super.put(CODE_TAG, type.value); super.put(MSG_TAG, msg); } /** * 初始化一个新创建的 AjaxResult 对象 * * @param type 状态类型 * @param msg 返回内容 * @param data 数据对象 */ public AjaxResult(Type type, String msg, Object data) { super.put(CODE_TAG, type.value); super.put(MSG_TAG, msg); if (StringUtils.isNotNull(data)) { super.put(DATA_TAG, data); } } /** * 返回成功消息 * * @return 成功消息 */ public static AjaxResult success() { return AjaxResult.success("操作成功"); } /** * 返回成功数据 * * @return 成功消息 */ public static AjaxResult success(Object data) { return AjaxResult.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回内容 * @return 成功消息 */ public static AjaxResult success(String msg) { return AjaxResult.success(msg, null); } /** * 返回成功消息 * * @param msg 返回内容 * @param data 数据对象 * @return 成功消息 */ public static AjaxResult success(String msg, Object data) { return new AjaxResult(Type.SUCCESS, msg, data); } /** * 返回警告消息 * * @param msg 返回内容 * @return 警告消息 */ public static AjaxResult warn(String msg) { return AjaxResult.warn(msg, null); } /** * 返回警告消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static AjaxResult warn(String msg, Object data) { return new AjaxResult(Type.WARN, msg, data); } /** * 返回错误消息 * * @return */ public static AjaxResult error() { return AjaxResult.error("操作失败"); } /** * 返回错误消息 * * @param msg 返回内容 * @return 警告消息 */ public static AjaxResult error(String msg) { return AjaxResult.error(msg, null); } /** * 返回错误消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static AjaxResult error(String msg, Object data) { return new AjaxResult(Type.ERROR, msg, data); } /** * 是否为成功消息 * * @return 结果 */ public boolean isSuccess() { return Objects.equals(Type.SUCCESS.value, this.get(CODE_TAG)); } /** * 是否为警告消息 * * @return 结果 */ public boolean isWarn() { return Objects.equals(Type.WARN.value, this.get(CODE_TAG)); } /** * 是否为错误消息 * * @return 结果 */ public boolean isError() { return Objects.equals(Type.ERROR.value, this.get(CODE_TAG)); } /** * 方便链式调用 * * @param key 键 * @param value 值 * @return 数据对象 */ @Override public AjaxResult put(String key, Object value) { super.put(key, value); return this; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java ================================================ package com.ruoyi.common.core.domain; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; /** * Entity基类 * * @author ruoyi */ public class BaseEntity implements Serializable { private static final long serialVersionUID = 1L; /** 搜索值 */ @JsonIgnore private String searchValue; /** 创建者 */ private String createBy; /** 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime; /** 更新者 */ private String updateBy; /** 更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date updateTime; /** 备注 */ private String remark; /** 请求参数 */ @JsonInclude(JsonInclude.Include.NON_EMPTY) private Map params; public String getSearchValue() { return searchValue; } public void setSearchValue(String searchValue) { this.searchValue = searchValue; } public String getCreateBy() { return createBy; } public void setCreateBy(String createBy) { this.createBy = createBy; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public String getUpdateBy() { return updateBy; } public void setUpdateBy(String updateBy) { this.updateBy = updateBy; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public Map getParams() { if (params == null) { params = new HashMap<>(); } return params; } public void setParams(Map params) { this.params = params; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/CxSelect.java ================================================ package com.ruoyi.common.core.domain; import java.io.Serializable; import java.util.List; /** * CxSelect树结构实体类 * * @author ruoyi */ public class CxSelect implements Serializable { private static final long serialVersionUID = 1L; /** * 数据值字段名称 */ private String v; /** * 数据标题字段名称 */ private String n; /** * 子集数据字段名称 */ private List s; public CxSelect() { } public CxSelect(String v, String n) { this.v = v; this.n = n; } public List getS() { return s; } public void setN(String n) { this.n = n; } public String getN() { return n; } public void setS(List s) { this.s = s; } public String getV() { return v; } public void setV(String v) { this.v = v; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java ================================================ package com.ruoyi.common.core.domain; import java.io.Serializable; /** * 响应信息主体 * * @author ruoyi */ public class R implements Serializable { private static final long serialVersionUID = 1L; /** 成功 */ public static final int SUCCESS = 0; /** 失败 */ public static final int FAIL = 500; private int code; private String msg; private T data; public static R ok() { return restResult(null, SUCCESS, "操作成功"); } public static R ok(T data) { return restResult(data, SUCCESS, "操作成功"); } public static R ok(T data, String msg) { return restResult(data, SUCCESS, msg); } public static R fail() { return restResult(null, FAIL, "操作失败"); } public static R fail(String msg) { return restResult(null, FAIL, msg); } public static R fail(T data) { return restResult(data, FAIL, "操作失败"); } public static R fail(T data, String msg) { return restResult(data, FAIL, msg); } public static R fail(int code, String msg) { return restResult(null, code, msg); } private static R restResult(T data, int code, String msg) { R apiResult = new R<>(); apiResult.setCode(code); apiResult.setData(data); apiResult.setMsg(msg); return apiResult; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public static Boolean isError(R ret) { return !isSuccess(ret); } public static Boolean isSuccess(R ret) { return R.SUCCESS == ret.getCode(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java ================================================ package com.ruoyi.common.core.domain; /** * Tree基类 * * @author ruoyi */ public class TreeEntity extends BaseEntity { private static final long serialVersionUID = 1L; /** 父菜单名称 */ private String parentName; /** 父菜单ID */ private Long parentId; /** 显示顺序 */ private Integer orderNum; /** 祖级列表 */ private String ancestors; public String getParentName() { return parentName; } public void setParentName(String parentName) { this.parentName = parentName; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public Integer getOrderNum() { return orderNum; } public void setOrderNum(Integer orderNum) { this.orderNum = orderNum; } public String getAncestors() { return ancestors; } public void setAncestors(String ancestors) { this.ancestors = ancestors; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/Ztree.java ================================================ package com.ruoyi.common.core.domain; import java.io.Serializable; /** * Ztree树结构实体类 * * @author ruoyi */ public class Ztree implements Serializable { private static final long serialVersionUID = 1L; /** 节点ID */ private Long id; /** 节点父ID */ private Long pId; /** 节点名称 */ private String name; /** 节点标题 */ private String title; /** 是否勾选 */ private boolean checked = false; /** 是否展开 */ private boolean open = false; /** 是否能勾选 */ private boolean nocheck = false; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getpId() { return pId; } public void setpId(Long pId) { this.pId = pId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public boolean isChecked() { return checked; } public void setChecked(boolean checked) { this.checked = checked; } public boolean isOpen() { return open; } public void setOpen(boolean open) { this.open = open; } public boolean isNocheck() { return nocheck; } public void setNocheck(boolean nocheck) { this.nocheck = nocheck; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java ================================================ package com.ruoyi.common.core.domain.entity; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.fasterxml.jackson.annotation.JsonIgnore; import com.ruoyi.common.core.domain.BaseEntity; /** * 部门表 sys_dept * * @author ruoyi */ public class SysDept extends BaseEntity { private static final long serialVersionUID = 1L; /** 部门ID */ private Long deptId; /** 父部门ID */ private Long parentId; /** 祖级列表 */ private String ancestors; /** 部门名称 */ private String deptName; /** 显示顺序 */ private Integer orderNum; /** 负责人 */ private String leader; /** 联系电话 */ private String phone; /** 邮箱 */ private String email; /** 部门状态:0正常,1停用 */ private String status; /** 删除标志(0代表存在 2代表删除) */ private String delFlag; /** 父部门名称 */ private String parentName; /** 排除编号 */ private Long excludeId; public Long getDeptId() { return deptId; } public void setDeptId(Long deptId) { this.deptId = deptId; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public String getAncestors() { return ancestors; } public void setAncestors(String ancestors) { this.ancestors = ancestors; } @NotBlank(message = "部门名称不能为空") @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符") public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } @NotNull(message = "显示顺序不能为空") public Integer getOrderNum() { return orderNum; } public void setOrderNum(Integer orderNum) { this.orderNum = orderNum; } public String getLeader() { return leader; } public void setLeader(String leader) { this.leader = leader; } @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符") public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Email(message = "邮箱格式不正确") @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getDelFlag() { return delFlag; } public void setDelFlag(String delFlag) { this.delFlag = delFlag; } public String getParentName() { return parentName; } public void setParentName(String parentName) { this.parentName = parentName; } @JsonIgnore public Long getExcludeId() { return excludeId; } public void setExcludeId(Long excludeId) { this.excludeId = excludeId; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("deptId", getDeptId()) .append("parentId", getParentId()) .append("ancestors", getAncestors()) .append("deptName", getDeptName()) .append("orderNum", getOrderNum()) .append("leader", getLeader()) .append("phone", getPhone()) .append("email", getEmail()) .append("status", getStatus()) .append("delFlag", getDelFlag()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java ================================================ package com.ruoyi.common.core.domain.entity; import jakarta.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.BaseEntity; /** * 字典数据表 sys_dict_data * * @author ruoyi */ public class SysDictData extends BaseEntity { private static final long serialVersionUID = 1L; /** 字典编码 */ @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) private Long dictCode; /** 字典排序 */ @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) private Long dictSort; /** 字典标签 */ @Excel(name = "字典标签") private String dictLabel; /** 字典键值 */ @Excel(name = "字典键值") private String dictValue; /** 字典类型 */ @Excel(name = "字典类型") private String dictType; /** 样式属性(其他样式扩展) */ @Excel(name = "字典样式") private String cssClass; /** 表格字典样式 */ private String listClass; /** 是否默认(Y是 N否) */ @Excel(name = "是否默认", readConverterExp = "Y=是,N=否") private String isDefault; /** 状态(0正常 1停用) */ @Excel(name = "状态", readConverterExp = "0=正常,1=停用") private String status; public Long getDictCode() { return dictCode; } public void setDictCode(Long dictCode) { this.dictCode = dictCode; } public Long getDictSort() { return dictSort; } public void setDictSort(Long dictSort) { this.dictSort = dictSort; } @NotBlank(message = "字典标签不能为空") @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符") public String getDictLabel() { return dictLabel; } public void setDictLabel(String dictLabel) { this.dictLabel = dictLabel; } @NotBlank(message = "字典键值不能为空") @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符") public String getDictValue() { return dictValue; } public void setDictValue(String dictValue) { this.dictValue = dictValue; } @NotBlank(message = "字典类型不能为空") @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符") public String getDictType() { return dictType; } public void setDictType(String dictType) { this.dictType = dictType; } @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符") public String getCssClass() { return cssClass; } public void setCssClass(String cssClass) { this.cssClass = cssClass; } public String getListClass() { return listClass; } public void setListClass(String listClass) { this.listClass = listClass; } public boolean getDefault() { return UserConstants.YES.equals(this.isDefault); } public String getIsDefault() { return isDefault; } public void setIsDefault(String isDefault) { this.isDefault = isDefault; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("dictCode", getDictCode()) .append("dictSort", getDictSort()) .append("dictLabel", getDictLabel()) .append("dictValue", getDictValue()) .append("dictType", getDictType()) .append("cssClass", getCssClass()) .append("listClass", getListClass()) .append("isDefault", getIsDefault()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java ================================================ package com.ruoyi.common.core.domain.entity; import jakarta.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.domain.BaseEntity; /** * 字典类型表 sys_dict_type * * @author ruoyi */ public class SysDictType extends BaseEntity { private static final long serialVersionUID = 1L; /** 字典主键 */ @Excel(name = "字典主键", cellType = ColumnType.NUMERIC) private Long dictId; /** 字典名称 */ @Excel(name = "字典名称") private String dictName; /** 字典类型 */ @Excel(name = "字典类型") private String dictType; /** 状态(0正常 1停用) */ @Excel(name = "状态", readConverterExp = "0=正常,1=停用") private String status; public Long getDictId() { return dictId; } public void setDictId(Long dictId) { this.dictId = dictId; } @NotBlank(message = "字典名称不能为空") @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") public String getDictName() { return dictName; } public void setDictName(String dictName) { this.dictName = dictName; } @NotBlank(message = "字典类型不能为空") @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") public String getDictType() { return dictType; } public void setDictType(String dictType) { this.dictType = dictType; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("dictId", getDictId()) .append("dictName", getDictName()) .append("dictType", getDictType()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java ================================================ package com.ruoyi.common.core.domain.entity; import java.util.List; import java.util.ArrayList; import jakarta.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.core.domain.BaseEntity; /** * 菜单权限表 sys_menu * * @author ruoyi */ public class SysMenu extends BaseEntity { private static final long serialVersionUID = 1L; /** 菜单ID */ private Long menuId; /** 菜单名称 */ private String menuName; /** 父菜单名称 */ private String parentName; /** 父菜单ID */ private Long parentId; /** 显示顺序 */ private String orderNum; /** 菜单URL */ private String url; /** 打开方式(menuItem页签 menuBlank新窗口) */ private String target; /** 类型(M目录 C菜单 F按钮) */ private String menuType; /** 菜单状态(0显示 1隐藏) */ private String visible; /** 是否刷新(0刷新 1不刷新) */ private String isRefresh; /** 权限字符串 */ private String perms; /** 菜单图标 */ private String icon; /** 子菜单 */ private List children = new ArrayList(); public Long getMenuId() { return menuId; } public void setMenuId(Long menuId) { this.menuId = menuId; } @NotBlank(message = "菜单名称不能为空") @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") public String getMenuName() { return menuName; } public void setMenuName(String menuName) { this.menuName = menuName; } public String getParentName() { return parentName; } public void setParentName(String parentName) { this.parentName = parentName; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } @NotBlank(message = "显示顺序不能为空") public String getOrderNum() { return orderNum; } public void setOrderNum(String orderNum) { this.orderNum = orderNum; } @Size(min = 0, max = 200, message = "请求地址不能超过200个字符") public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getTarget() { return target; } public void setTarget(String target) { this.target = target; } @NotBlank(message = "菜单类型不能为空") public String getMenuType() { return menuType; } public void setMenuType(String menuType) { this.menuType = menuType; } public String getVisible() { return visible; } public void setVisible(String visible) { this.visible = visible; } public String getIsRefresh() { return isRefresh; } public void setIsRefresh(String isRefresh) { this.isRefresh = isRefresh; } @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") public String getPerms() { return perms; } public void setPerms(String perms) { this.perms = perms; } public String getIcon() { return icon; } public void setIcon(String icon) { this.icon = icon; } public List getChildren() { return children; } public void setChildren(List children) { this.children = children; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("menuId", getMenuId()) .append("menuName", getMenuName()) .append("parentId", getParentId()) .append("orderNum", getOrderNum()) .append("url", getUrl()) .append("target", getTarget()) .append("menuType", getMenuType()) .append("visible", getVisible()) .append("perms", getPerms()) .append("icon", getIcon()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java ================================================ package com.ruoyi.common.core.domain.entity; import java.util.Set; import jakarta.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.domain.BaseEntity; /** * 角色表 sys_role * * @author ruoyi */ public class SysRole extends BaseEntity { private static final long serialVersionUID = 1L; /** 角色ID */ @Excel(name = "角色序号", cellType = ColumnType.NUMERIC) private Long roleId; /** 角色名称 */ @Excel(name = "角色名称") private String roleName; /** 角色权限 */ @Excel(name = "角色权限") private String roleKey; /** 角色排序 */ @Excel(name = "角色排序", cellType = ColumnType.NUMERIC) private String roleSort; /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") private String dataScope; /** 角色状态(0正常 1停用) */ @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") private String status; /** 删除标志(0代表存在 2代表删除) */ private String delFlag; /** 用户是否存在此角色标识 默认不存在 */ private boolean flag = false; /** 菜单组 */ private Long[] menuIds; /** 部门组(数据权限) */ private Long[] deptIds; /** 角色菜单权限 */ private Set permissions; public SysRole() { } public SysRole(Long roleId) { this.roleId = roleId; } public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } public boolean isAdmin() { return isAdmin(this.roleId); } public static boolean isAdmin(Long roleId) { return roleId != null && 1L == roleId; } public String getDataScope() { return dataScope; } public void setDataScope(String dataScope) { this.dataScope = dataScope; } @NotBlank(message = "角色名称不能为空") @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } @NotBlank(message = "权限字符不能为空") @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") public String getRoleKey() { return roleKey; } public void setRoleKey(String roleKey) { this.roleKey = roleKey; } @NotBlank(message = "显示顺序不能为空") public String getRoleSort() { return roleSort; } public void setRoleSort(String roleSort) { this.roleSort = roleSort; } public String getStatus() { return status; } public String getDelFlag() { return delFlag; } public void setDelFlag(String delFlag) { this.delFlag = delFlag; } public void setStatus(String status) { this.status = status; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public Long[] getMenuIds() { return menuIds; } public void setMenuIds(Long[] menuIds) { this.menuIds = menuIds; } public Long[] getDeptIds() { return deptIds; } public void setDeptIds(Long[] deptIds) { this.deptIds = deptIds; } public Set getPermissions() { return permissions; } public void setPermissions(Set permissions) { this.permissions = permissions; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("roleId", getRoleId()) .append("roleName", getRoleName()) .append("roleKey", getRoleKey()) .append("roleSort", getRoleSort()) .append("dataScope", getDataScope()) .append("status", getStatus()) .append("delFlag", getDelFlag()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java ================================================ package com.ruoyi.common.core.domain.entity; import java.util.Date; import java.util.List; import jakarta.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.fasterxml.jackson.annotation.JsonIgnore; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.Type; import com.ruoyi.common.annotation.Excels; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.xss.Xss; /** * 用户对象 sys_user * * @author ruoyi */ public class SysUser extends BaseEntity { private static final long serialVersionUID = 1L; /** 用户ID */ @Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号") private Long userId; /** 部门ID */ @Excel(name = "部门编号", type = Type.IMPORT) private Long deptId; /** 部门父ID */ private Long parentId; /** 角色ID */ private Long roleId; /** 登录名称 */ @Excel(name = "登录名称") private String loginName; /** 用户名称 */ @Excel(name = "用户名称") private String userName; /** 用户类型 */ private String userType; /** 用户邮箱 */ @Excel(name = "用户邮箱") private String email; /** 手机号码 */ @Excel(name = "手机号码", cellType = ColumnType.TEXT) private String phonenumber; /** 用户性别 */ @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") private String sex; /** 用户头像 */ private String avatar; /** 密码 */ private String password; /** 盐加密 */ private String salt; /** 账号状态(0正常 1停用) */ @Excel(name = "账号状态", readConverterExp = "0=正常,1=停用") private String status; /** 删除标志(0代表存在 2代表删除) */ private String delFlag; /** 最后登录IP */ @Excel(name = "最后登录IP", type = Type.EXPORT) private String loginIp; /** 最后登录时间 */ @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) private Date loginDate; /** 密码最后更新时间 */ private Date pwdUpdateDate; /** 部门对象 */ @Excels({ @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) }) private SysDept dept; private List roles; /** 角色组 */ private Long[] roleIds; /** 岗位组 */ private Long[] postIds; public SysUser() { } public SysUser(Long userId) { this.userId = userId; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public boolean isAdmin() { return ShiroUtils.isAdmin(this.userId); } public Long getDeptId() { return deptId; } public void setDeptId(Long deptId) { this.deptId = deptId; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } @Xss(message = "登录账号不能包含脚本字符") @NotBlank(message = "登录账号不能为空") @Size(min = 0, max = 30, message = "登录账号长度不能超过30个字符") public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } @Xss(message = "用户昵称不能包含脚本字符") @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserType() { return userType; } public void setUserType(String userType) { this.userType = userType; } @Email(message = "邮箱格式不正确") @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") public String getPhonenumber() { return phonenumber; } public void setPhonenumber(String phonenumber) { this.phonenumber = phonenumber; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAvatar() { return avatar; } public void setAvatar(String avatar) { this.avatar = avatar; } @JsonIgnore public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @JsonIgnore public String getSalt() { return salt; } public void setSalt(String salt) { this.salt = salt; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getDelFlag() { return delFlag; } public void setDelFlag(String delFlag) { this.delFlag = delFlag; } public String getLoginIp() { return loginIp; } public void setLoginIp(String loginIp) { this.loginIp = loginIp; } public Date getLoginDate() { return loginDate; } public void setLoginDate(Date loginDate) { this.loginDate = loginDate; } public Date getPwdUpdateDate() { return pwdUpdateDate; } public void setPwdUpdateDate(Date pwdUpdateDate) { this.pwdUpdateDate = pwdUpdateDate; } public SysDept getDept() { if (dept == null) { dept = new SysDept(); } return dept; } public void setDept(SysDept dept) { this.dept = dept; } public List getRoles() { return roles; } public void setRoles(List roles) { this.roles = roles; } public Long[] getRoleIds() { return roleIds; } public void setRoleIds(Long[] roleIds) { this.roleIds = roleIds; } public Long[] getPostIds() { return postIds; } public void setPostIds(Long[] postIds) { this.postIds = postIds; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("userId", getUserId()) .append("deptId", getDeptId()) .append("loginName", getLoginName()) .append("userName", getUserName()) .append("userType", getUserType()) .append("email", getEmail()) .append("phonenumber", getPhonenumber()) .append("sex", getSex()) .append("avatar", getAvatar()) .append("password", getPassword()) .append("salt", getSalt()) .append("status", getStatus()) .append("delFlag", getDelFlag()) .append("loginIp", getLoginIp()) .append("loginDate", getLoginDate()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .append("dept", getDept()) .append("roles", getRoles()) .toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java ================================================ package com.ruoyi.common.core.page; import com.ruoyi.common.utils.StringUtils; /** * 分页数据 * * @author ruoyi */ public class PageDomain { /** 当前记录起始索引 */ private Integer pageNum; /** 每页显示记录数 */ private Integer pageSize; /** 排序列 */ private String orderByColumn; /** 排序的方向desc或者asc */ private String isAsc = "asc"; /** 分页参数合理化 */ private Boolean reasonable = true; public String getOrderBy() { if (StringUtils.isEmpty(orderByColumn)) { return ""; } return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; } public Integer getPageNum() { return pageNum; } public void setPageNum(Integer pageNum) { this.pageNum = pageNum; } public Integer getPageSize() { return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } public String getOrderByColumn() { return orderByColumn; } public void setOrderByColumn(String orderByColumn) { this.orderByColumn = orderByColumn; } public String getIsAsc() { return isAsc; } public void setIsAsc(String isAsc) { this.isAsc = isAsc; } public Boolean getReasonable() { if (StringUtils.isNull(reasonable)) { return Boolean.TRUE; } return reasonable; } public void setReasonable(Boolean reasonable) { this.reasonable = reasonable; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java ================================================ package com.ruoyi.common.core.page; import java.io.Serializable; import java.util.List; /** * 表格分页数据对象 * * @author ruoyi */ public class TableDataInfo implements Serializable { private static final long serialVersionUID = 1L; /** 总记录数 */ private long total; /** 列表数据 */ private List rows; /** 消息状态码 */ private int code; /** 消息内容 */ private String msg; /** * 表格数据对象 */ public TableDataInfo() { } /** * 分页 * * @param list 列表数据 * @param total 总记录数 */ public TableDataInfo(List list, long total) { this.rows = list; this.total = total; } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public List getRows() { return rows; } public void setRows(List rows) { this.rows = rows; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java ================================================ package com.ruoyi.common.core.page; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.ServletUtils; /** * 表格数据处理 * * @author ruoyi */ public class TableSupport { /** * 当前记录起始索引 */ public static final String PAGE_NUM = "pageNum"; /** * 每页显示记录数 */ public static final String PAGE_SIZE = "pageSize"; /** * 排序列 */ public static final String ORDER_BY_COLUMN = "orderByColumn"; /** * 排序的方向 "desc" 或者 "asc". */ public static final String IS_ASC = "isAsc"; /** * 分页参数合理化 */ public static final String REASONABLE = "reasonable"; /** * 封装分页对象 */ public static PageDomain getPageDomain() { PageDomain pageDomain = new PageDomain(); pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10)); pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); return pageDomain; } public static PageDomain buildPageRequest() { return getPageDomain(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/session/OnlineSession.java ================================================ package com.ruoyi.common.core.session; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.shiro.session.mgt.SimpleSession; import com.ruoyi.common.enums.OnlineStatus; /** * 在线用户会话属性 * * @author ruoyi */ public class OnlineSession extends SimpleSession { private static final long serialVersionUID = 1L; /** 用户ID */ private Long userId; /** 用户名称 */ private String loginName; /** 部门名称 */ private String deptName; /** 用户头像 */ private String avatar; /** 登录IP地址 */ private String host; /** 浏览器类型 */ private String browser; /** 操作系统 */ private String os; /** 在线状态 */ private OnlineStatus status = OnlineStatus.on_line; /** 属性是否改变 优化session数据同步 */ private transient boolean attributeChanged = false; @Override public String getHost() { return host; } @Override public void setHost(String host) { this.host = host; } public String getBrowser() { return browser; } public void setBrowser(String browser) { this.browser = browser; } public String getOs() { return os; } public void setOs(String os) { this.os = os; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public OnlineStatus getStatus() { return status; } public void setStatus(OnlineStatus status) { this.status = status; } public void markAttributeChanged() { this.attributeChanged = true; } public void resetAttributeChanged() { this.attributeChanged = false; } public boolean isAttributeChanged() { return attributeChanged; } public String getAvatar() { return avatar; } public void setAvatar(String avatar) { this.avatar = avatar; } @Override public void setAttribute(Object key, Object value) { super.setAttribute(key, value); } @Override public Object removeAttribute(Object key) { return super.removeAttribute(key); } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("userId", getUserId()) .append("loginName", getLoginName()) .append("deptName", getDeptName()) .append("avatar", getAvatar()) .append("host", getHost()) .append("browser", getBrowser()) .append("os", getOs()) .append("status", getStatus()) .append("attributeChanged", isAttributeChanged()) .toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java ================================================ package com.ruoyi.common.core.text; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import com.ruoyi.common.utils.StringUtils; /** * 字符集工具类 * * @author ruoyi */ public class CharsetKit { /** ISO-8859-1 */ public static final String ISO_8859_1 = "ISO-8859-1"; /** UTF-8 */ public static final String UTF_8 = "UTF-8"; /** GBK */ public static final String GBK = "GBK"; /** ISO-8859-1 */ public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); /** UTF-8 */ public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); /** GBK */ public static final Charset CHARSET_GBK = Charset.forName(GBK); /** * 转换为Charset对象 * * @param charset 字符集,为空则返回默认字符集 * @return Charset */ public static Charset charset(String charset) { return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); } /** * 转换字符串的字符集编码 * * @param source 字符串 * @param srcCharset 源字符集,默认ISO-8859-1 * @param destCharset 目标字符集,默认UTF-8 * @return 转换后的字符集 */ public static String convert(String source, String srcCharset, String destCharset) { return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); } /** * 转换字符串的字符集编码 * * @param source 字符串 * @param srcCharset 源字符集,默认ISO-8859-1 * @param destCharset 目标字符集,默认UTF-8 * @return 转换后的字符集 */ public static String convert(String source, Charset srcCharset, Charset destCharset) { if (null == srcCharset) { srcCharset = StandardCharsets.ISO_8859_1; } if (null == destCharset) { destCharset = StandardCharsets.UTF_8; } if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) { return source; } return new String(source.getBytes(srcCharset), destCharset); } /** * @return 系统字符集编码 */ public static String systemCharset() { return Charset.defaultCharset().name(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java ================================================ package com.ruoyi.common.core.text; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.text.NumberFormat; import java.util.Set; import com.ruoyi.common.utils.StringUtils; import org.apache.commons.lang3.ArrayUtils; /** * 类型转换器 * * @author ruoyi */ public class Convert { /** * 转换为字符串
                    * 如果给定的值为null,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static String toStr(Object value, String defaultValue) { if (null == value) { return defaultValue; } if (value instanceof String) { return (String) value; } return value.toString(); } /** * 转换为字符串
                    * 如果给定的值为null,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static String toStr(Object value) { return toStr(value, null); } /** * 转换为字符
                    * 如果给定的值为null,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Character toChar(Object value, Character defaultValue) { if (null == value) { return defaultValue; } if (value instanceof Character) { return (Character) value; } final String valueStr = toStr(value, null); return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); } /** * 转换为字符
                    * 如果给定的值为null,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Character toChar(Object value) { return toChar(value, null); } /** * 转换为byte
                    * 如果给定的值为null,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Byte toByte(Object value, Byte defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Byte) { return (Byte) value; } if (value instanceof Number) { return ((Number) value).byteValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Byte.parseByte(valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为byte
                    * 如果给定的值为null,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Byte toByte(Object value) { return toByte(value, null); } /** * 转换为Short
                    * 如果给定的值为null,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Short toShort(Object value, Short defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Short) { return (Short) value; } if (value instanceof Number) { return ((Number) value).shortValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Short.parseShort(valueStr.trim()); } catch (Exception e) { return defaultValue; } } /** * 转换为Short
                    * 如果给定的值为null,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Short toShort(Object value) { return toShort(value, null); } /** * 转换为Number
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Number toNumber(Object value, Number defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Number) { return (Number) value; } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return NumberFormat.getInstance().parse(valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为Number
                    * 如果给定的值为空,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Number toNumber(Object value) { return toNumber(value, null); } /** * 转换为int
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Integer toInt(Object value, Integer defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Integer) { return (Integer) value; } if (value instanceof Number) { return ((Number) value).intValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Integer.parseInt(valueStr.trim()); } catch (Exception e) { return defaultValue; } } /** * 转换为int
                    * 如果给定的值为null,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Integer toInt(Object value) { return toInt(value, null); } /** * 转换为Integer数组
                    * * @param str 被转换的值 * @return 结果 */ public static Integer[] toIntArray(String str) { return toIntArray(",", str); } /** * 转换为Long数组
                    * * @param str 被转换的值 * @return 结果 */ public static Long[] toLongArray(String str) { return toLongArray(",", str); } /** * 转换为Integer数组
                    * * @param split 分隔符 * @param split 被转换的值 * @return 结果 */ public static Integer[] toIntArray(String split, String str) { if (StringUtils.isEmpty(str)) { return new Integer[] {}; } String[] arr = str.split(split); final Integer[] ints = new Integer[arr.length]; for (int i = 0; i < arr.length; i++) { final Integer v = toInt(arr[i], 0); ints[i] = v; } return ints; } /** * 转换为Long数组
                    * * @param split 分隔符 * @param str 被转换的值 * @return 结果 */ public static Long[] toLongArray(String split, String str) { if (StringUtils.isEmpty(str)) { return new Long[] {}; } String[] arr = str.split(split); final Long[] longs = new Long[arr.length]; for (int i = 0; i < arr.length; i++) { final Long v = toLong(arr[i], null); longs[i] = v; } return longs; } /** * 转换为String数组
                    * * @param str 被转换的值 * @return 结果 */ public static String[] toStrArray(String str) { if (StringUtils.isEmpty(str)) { return new String[] {}; } return toStrArray(",", str); } /** * 转换为String数组
                    * * @param split 分隔符 * @param split 被转换的值 * @return 结果 */ public static String[] toStrArray(String split, String str) { return str.split(split); } /** * 转换为long
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Long toLong(Object value, Long defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Long) { return (Long) value; } if (value instanceof Number) { return ((Number) value).longValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { // 支持科学计数法 return new BigDecimal(valueStr.trim()).longValue(); } catch (Exception e) { return defaultValue; } } /** * 转换为long
                    * 如果给定的值为null,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Long toLong(Object value) { return toLong(value, null); } /** * 转换为double
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Double toDouble(Object value, Double defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Double) { return (Double) value; } if (value instanceof Number) { return ((Number) value).doubleValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { // 支持科学计数法 return new BigDecimal(valueStr.trim()).doubleValue(); } catch (Exception e) { return defaultValue; } } /** * 转换为double
                    * 如果给定的值为空,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Double toDouble(Object value) { return toDouble(value, null); } /** * 转换为Float
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Float toFloat(Object value, Float defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Float) { return (Float) value; } if (value instanceof Number) { return ((Number) value).floatValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Float.parseFloat(valueStr.trim()); } catch (Exception e) { return defaultValue; } } /** * 转换为Float
                    * 如果给定的值为空,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Float toFloat(Object value) { return toFloat(value, null); } /** * 转换为boolean
                    * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Boolean toBool(Object value, Boolean defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Boolean) { return (Boolean) value; } String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } valueStr = valueStr.trim().toLowerCase(); switch (valueStr) { case "true": case "yes": case "ok": case "1": return true; case "false": case "no": case "0": return false; default: return defaultValue; } } /** * 转换为boolean
                    * 如果给定的值为空,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Boolean toBool(Object value) { return toBool(value, null); } /** * 转换为Enum对象
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * * @param clazz Enum的Class * @param value 值 * @param defaultValue 默认值 * @return Enum */ public static > E toEnum(Class clazz, Object value, E defaultValue) { if (value == null) { return defaultValue; } if (clazz.isAssignableFrom(value.getClass())) { @SuppressWarnings("unchecked") E myE = (E) value; return myE; } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Enum.valueOf(clazz, valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为Enum对象
                    * 如果给定的值为空,或者转换失败,返回默认值null
                    * * @param clazz Enum的Class * @param value 值 * @return Enum */ public static > E toEnum(Class clazz, Object value) { return toEnum(clazz, value, null); } /** * 转换为BigInteger
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static BigInteger toBigInteger(Object value, BigInteger defaultValue) { if (value == null) { return defaultValue; } if (value instanceof BigInteger) { return (BigInteger) value; } if (value instanceof Long) { return BigInteger.valueOf((Long) value); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return new BigInteger(valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为BigInteger
                    * 如果给定的值为空,或者转换失败,返回默认值null
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static BigInteger toBigInteger(Object value) { return toBigInteger(value, null); } /** * 转换为BigDecimal
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) { if (value == null) { return defaultValue; } if (value instanceof BigDecimal) { return (BigDecimal) value; } if (value instanceof Long) { return new BigDecimal((Long) value); } if (value instanceof Double) { return BigDecimal.valueOf((Double) value); } if (value instanceof Integer) { return new BigDecimal((Integer) value); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return new BigDecimal(valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为BigDecimal
                    * 如果给定的值为空,或者转换失败,返回默认值
                    * 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static BigDecimal toBigDecimal(Object value) { return toBigDecimal(value, null); } /** * 将对象转为字符串
                    * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 * * @param obj 对象 * @return 字符串 */ public static String utf8Str(Object obj) { return str(obj, CharsetKit.CHARSET_UTF_8); } /** * 将对象转为字符串
                    * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 * * @param obj 对象 * @param charsetName 字符集 * @return 字符串 */ public static String str(Object obj, String charsetName) { return str(obj, Charset.forName(charsetName)); } /** * 将对象转为字符串
                    * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 * * @param obj 对象 * @param charset 字符集 * @return 字符串 */ public static String str(Object obj, Charset charset) { if (null == obj) { return null; } if (obj instanceof String) { return (String) obj; } else if (obj instanceof byte[]) { return str((byte[]) obj, charset); } else if (obj instanceof Byte[]) { byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj); return str(bytes, charset); } else if (obj instanceof ByteBuffer) { return str((ByteBuffer) obj, charset); } return obj.toString(); } /** * 将byte数组转为字符串 * * @param bytes byte数组 * @param charset 字符集 * @return 字符串 */ public static String str(byte[] bytes, String charset) { return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); } /** * 解码字节码 * * @param data 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 解码后的字符串 */ public static String str(byte[] data, Charset charset) { if (data == null) { return null; } if (null == charset) { return new String(data); } return new String(data, charset); } /** * 将编码的byteBuffer数据转换为字符串 * * @param data 数据 * @param charset 字符集,如果为空使用当前系统字符集 * @return 字符串 */ public static String str(ByteBuffer data, String charset) { if (data == null) { return null; } return str(data, Charset.forName(charset)); } /** * 将编码的byteBuffer数据转换为字符串 * * @param data 数据 * @param charset 字符集,如果为空使用当前系统字符集 * @return 字符串 */ public static String str(ByteBuffer data, Charset charset) { if (null == charset) { charset = Charset.defaultCharset(); } return charset.decode(data).toString(); } // ----------------------------------------------------------------------- 全角半角转换 /** * 半角转全角 * * @param input String. * @return 全角字符串. */ public static String toSBC(String input) { return toSBC(input, null); } /** * 半角转全角 * * @param input String * @param notConvertSet 不替换的字符集合 * @return 全角字符串. */ public static String toSBC(String input, Set notConvertSet) { char[] c = input.toCharArray(); for (int i = 0; i < c.length; i++) { if (null != notConvertSet && notConvertSet.contains(c[i])) { // 跳过不替换的字符 continue; } if (c[i] == ' ') { c[i] = '\u3000'; } else if (c[i] < '\177') { c[i] = (char) (c[i] + 65248); } } return new String(c); } /** * 全角转半角 * * @param input String. * @return 半角字符串 */ public static String toDBC(String input) { return toDBC(input, null); } /** * 替换全角为半角 * * @param text 文本 * @param notConvertSet 不替换的字符集合 * @return 替换后的字符 */ public static String toDBC(String text, Set notConvertSet) { char[] c = text.toCharArray(); for (int i = 0; i < c.length; i++) { if (null != notConvertSet && notConvertSet.contains(c[i])) { // 跳过不替换的字符 continue; } if (c[i] == '\u3000') { c[i] = ' '; } else if (c[i] > '\uFF00' && c[i] < '\uFF5F') { c[i] = (char) (c[i] - 65248); } } String returnString = new String(c); return returnString; } /** * 数字金额大写转换 先写个完整的然后将如零拾替换成零 * * @param n 数字 * @return 中文大写数字 */ public static String digitUppercase(double n) { String[] fraction = { "角", "分" }; String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; String head = n < 0 ? "负" : ""; n = Math.abs(n); String s = ""; for (int i = 0; i < fraction.length; i++) { // 优化double计算精度丢失问题 BigDecimal nNum = new BigDecimal(n); BigDecimal decimal = new BigDecimal(10); BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN); double d = scale.doubleValue(); s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); } if (s.length() < 1) { s = "整"; } int integerPart = (int) Math.floor(n); for (int i = 0; i < unit[0].length && integerPart > 0; i++) { String p = ""; for (int j = 0; j < unit[1].length && n > 0; j++) { p = digit[integerPart % 10] + unit[1][j] + p; integerPart = integerPart / 10; } s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; } return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java ================================================ package com.ruoyi.common.core.text; import com.ruoyi.common.utils.StringUtils; /** * 字符串格式化 * * @author ruoyi */ public class StrFormatter { public static final String EMPTY_JSON = "{}"; public static final char C_BACKSLASH = '\\'; public static final char C_DELIM_START = '{'; public static final char C_DELIM_END = '}'; /** * 格式化字符串
                    * 此方法只是简单将占位符 {} 按照顺序替换为参数
                    * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
                    * 例:
                    * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
                    * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
                    * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
                    * * @param strPattern 字符串模板 * @param argArray 参数列表 * @return 结果 */ public static String format(final String strPattern, final Object... argArray) { if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) { return strPattern; } final int strPatternLength = strPattern.length(); // 初始化定义好的长度以获得更好的性能 StringBuilder sbuf = new StringBuilder(strPatternLength + 50); int handledPosition = 0; int delimIndex;// 占位符所在位置 for (int argIndex = 0; argIndex < argArray.length; argIndex++) { delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); if (delimIndex == -1) { if (handledPosition == 0) { return strPattern; } else { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 sbuf.append(strPattern, handledPosition, strPatternLength); return sbuf.toString(); } } else { if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) { if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) { // 转义符之前还有一个转义符,占位符依旧有效 sbuf.append(strPattern, handledPosition, delimIndex - 1); sbuf.append(Convert.utf8Str(argArray[argIndex])); handledPosition = delimIndex + 2; } else { // 占位符被转义 argIndex--; sbuf.append(strPattern, handledPosition, delimIndex - 1); sbuf.append(C_DELIM_START); handledPosition = delimIndex + 1; } } else { // 正常占位符 sbuf.append(strPattern, handledPosition, delimIndex); sbuf.append(Convert.utf8Str(argArray[argIndex])); handledPosition = delimIndex + 2; } } } // 加入最后一个占位符后所有的字符 sbuf.append(strPattern, handledPosition, strPattern.length()); return sbuf.toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java ================================================ package com.ruoyi.common.enums; /** * 操作状态 * * @author ruoyi */ public enum BusinessStatus { /** * 成功 */ SUCCESS, /** * 失败 */ FAIL, } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java ================================================ package com.ruoyi.common.enums; /** * 业务操作类型 * * @author ruoyi */ public enum BusinessType { /** * 其它 */ OTHER, /** * 新增 */ INSERT, /** * 修改 */ UPDATE, /** * 删除 */ DELETE, /** * 授权 */ GRANT, /** * 导出 */ EXPORT, /** * 导入 */ IMPORT, /** * 强退 */ FORCE, /** * 生成代码 */ GENCODE, /** * 清空 */ CLEAN, } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java ================================================ package com.ruoyi.common.enums; /** * 数据源 * * @author ruoyi */ public enum DataSourceType { /** * 主库 */ MASTER, /** * 从库 */ SLAVE } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/enums/DesensitizedType.java ================================================ package com.ruoyi.common.enums; import java.util.function.Function; import com.ruoyi.common.utils.DesensitizedUtil; /** * 脱敏类型 * * @author ruoyi */ public enum DesensitizedType { /** * 姓名,第2位星号替换 */ USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")), /** * 密码,全部字符都用*代替 */ PASSWORD(DesensitizedUtil::password), /** * 身份证,中间10位星号替换 */ ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{3}[Xx]|\\d{4})", "$1** **** ****$2")), /** * 手机号,中间4位星号替换 */ PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")), /** * 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换 */ EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")), /** * 银行卡号,保留最后4位,其他星号替换 */ BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")), /** * 车牌号码,包含普通车辆、新能源车辆 */ CAR_LICENSE(DesensitizedUtil::carLicense); private final Function desensitizer; DesensitizedType(Function desensitizer) { this.desensitizer = desensitizer; } public Function desensitizer() { return desensitizer; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/enums/OnlineStatus.java ================================================ package com.ruoyi.common.enums; /** * 用户会话 * * @author ruoyi */ public enum OnlineStatus { /** 用户状态 */ on_line("在线"), off_line("离线"); private final String info; private OnlineStatus(String info) { this.info = info; } public String getInfo() { return info; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java ================================================ package com.ruoyi.common.enums; /** * 操作人类别 * * @author ruoyi */ public enum OperatorType { /** * 其它 */ OTHER, /** * 后台用户 */ MANAGE, /** * 手机端用户 */ MOBILE } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/enums/UserStatus.java ================================================ package com.ruoyi.common.enums; /** * 用户状态 * * @author ruoyi */ public enum UserStatus { OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除"); private final String code; private final String info; UserStatus(String code, String info) { this.code = code; this.info = info; } public String getCode() { return code; } public String getInfo() { return info; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java ================================================ package com.ruoyi.common.exception; /** * 演示模式异常 * * @author ruoyi */ public class DemoModeException extends RuntimeException { private static final long serialVersionUID = 1L; public DemoModeException() { } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/GlobalException.java ================================================ package com.ruoyi.common.exception; /** * 全局异常 * * @author ruoyi */ public class GlobalException extends RuntimeException { private static final long serialVersionUID = 1L; /** * 错误提示 */ private String message; /** * 错误明细,内部调试错误 * * 和 {@link CommonResult#getDetailMessage()} 一致的设计 */ private String detailMessage; /** * 空构造方法,避免反序列化问题 */ public GlobalException() { } public GlobalException(String message) { this.message = message; } public String getDetailMessage() { return detailMessage; } public GlobalException setDetailMessage(String detailMessage) { this.detailMessage = detailMessage; return this; } @Override public String getMessage() { return message; } public GlobalException setMessage(String message) { this.message = message; return this; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java ================================================ package com.ruoyi.common.exception; /** * 业务异常 * * @author ruoyi */ public final class ServiceException extends RuntimeException { private static final long serialVersionUID = 1L; /** * 错误提示 */ private String message; /** * 错误明细,内部调试错误 * * 和 {@link CommonResult#getDetailMessage()} 一致的设计 */ private String detailMessage; /** * 空构造方法,避免反序列化问题 */ public ServiceException() { } public ServiceException(String message) { this.message = message; } public String getDetailMessage() { return detailMessage; } public ServiceException setDetailMessage(String detailMessage) { this.detailMessage = detailMessage; return this; } @Override public String getMessage() { return message; } public ServiceException setMessage(String message) { this.message = message; return this; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/UtilException.java ================================================ package com.ruoyi.common.exception; /** * 工具类异常 * * @author ruoyi */ public class UtilException extends RuntimeException { private static final long serialVersionUID = 8247610319171014183L; public UtilException(Throwable e) { super(e.getMessage(), e); } public UtilException(String message) { super(message); } public UtilException(String message, Throwable throwable) { super(message, throwable); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java ================================================ package com.ruoyi.common.exception.base; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.StringUtils; /** * 基础异常 * * @author ruoyi */ public class BaseException extends RuntimeException { private static final long serialVersionUID = 1L; /** * 所属模块 */ private String module; /** * 错误码 */ private String code; /** * 错误码对应的参数 */ private Object[] args; /** * 错误消息 */ private String defaultMessage; public BaseException(String module, String code, Object[] args, String defaultMessage) { this.module = module; this.code = code; this.args = args; this.defaultMessage = defaultMessage; } public BaseException(String module, String code, Object[] args) { this(module, code, args, null); } public BaseException(String module, String defaultMessage) { this(module, null, null, defaultMessage); } public BaseException(String code, Object[] args) { this(null, code, args, null); } public BaseException(String defaultMessage) { this(null, null, null, defaultMessage); } @Override public String getMessage() { String message = null; if (!StringUtils.isEmpty(code)) { message = MessageUtils.message(code, args); } if (message == null) { message = defaultMessage; } return message; } public String getModule() { return module; } public String getCode() { return code; } public Object[] getArgs() { return args; } public String getDefaultMessage() { return defaultMessage; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileException.java ================================================ package com.ruoyi.common.exception.file; import com.ruoyi.common.exception.base.BaseException; /** * 文件信息异常类 * * @author ruoyi */ public class FileException extends BaseException { private static final long serialVersionUID = 1L; public FileException(String code, Object[] args) { super("file", code, args, null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java ================================================ package com.ruoyi.common.exception.file; /** * 文件名称超长限制异常类 * * @author ruoyi */ public class FileNameLengthLimitExceededException extends FileException { private static final long serialVersionUID = 1L; public FileNameLengthLimitExceededException(int defaultFileNameLength) { super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java ================================================ package com.ruoyi.common.exception.file; /** * 文件名大小限制异常类 * * @author ruoyi */ public class FileSizeLimitExceededException extends FileException { private static final long serialVersionUID = 1L; public FileSizeLimitExceededException(long defaultMaxSize) { super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/file/FileUploadException.java ================================================ package com.ruoyi.common.exception.file; import java.io.PrintStream; import java.io.PrintWriter; /** * 文件上传异常类 * * @author ruoyi */ public class FileUploadException extends Exception { private static final long serialVersionUID = 1L; private final Throwable cause; public FileUploadException() { this(null, null); } public FileUploadException(final String msg) { this(msg, null); } public FileUploadException(String msg, Throwable cause) { super(msg); this.cause = cause; } @Override public void printStackTrace(PrintStream stream) { super.printStackTrace(stream); if (cause != null) { stream.println("Caused by:"); cause.printStackTrace(stream); } } @Override public void printStackTrace(PrintWriter writer) { super.printStackTrace(writer); if (cause != null) { writer.println("Caused by:"); cause.printStackTrace(writer); } } @Override public Throwable getCause() { return cause; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java ================================================ package com.ruoyi.common.exception.file; import java.util.Arrays; /** * 文件上传无效扩展名异常类 * * @author ruoyi */ public class InvalidExtensionException extends FileUploadException { private static final long serialVersionUID = 1L; private String[] allowedExtension; private String extension; private String filename; public InvalidExtensionException(String[] allowedExtension, String extension, String filename) { super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); this.allowedExtension = allowedExtension; this.extension = extension; this.filename = filename; } public String[] getAllowedExtension() { return allowedExtension; } public String getExtension() { return extension; } public String getFilename() { return filename; } public static class InvalidImageExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) { super(allowedExtension, extension, filename); } } public static class InvalidFlashExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) { super(allowedExtension, extension, filename); } } public static class InvalidMediaExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) { super(allowedExtension, extension, filename); } } public static class InvalidVideoExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) { super(allowedExtension, extension, filename); } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java ================================================ package com.ruoyi.common.exception.job; /** * 计划策略异常 * * @author ruoyi */ public class TaskException extends Exception { private static final long serialVersionUID = 1L; private Code code; public TaskException(String msg, Code code) { this(msg, code, null); } public TaskException(String msg, Code code, Exception nestedEx) { super(msg, nestedEx); this.code = code; } public Code getCode() { return code; } public enum Code { TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/BlackListException.java ================================================ package com.ruoyi.common.exception.user; /** * 黑名单IP异常类 * * @author ruoyi */ public class BlackListException extends UserException { private static final long serialVersionUID = 1L; public BlackListException() { super("login.blocked", null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java ================================================ package com.ruoyi.common.exception.user; /** * 验证码错误异常类 * * @author ruoyi */ public class CaptchaException extends UserException { private static final long serialVersionUID = 1L; public CaptchaException() { super("user.jcaptcha.error", null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/RoleBlockedException.java ================================================ package com.ruoyi.common.exception.user; /** * 角色锁定异常类 * * @author ruoyi */ public class RoleBlockedException extends UserException { private static final long serialVersionUID = 1L; public RoleBlockedException() { super("role.blocked", null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserBlockedException.java ================================================ package com.ruoyi.common.exception.user; /** * 用户锁定异常类 * * @author ruoyi */ public class UserBlockedException extends UserException { private static final long serialVersionUID = 1L; public UserBlockedException() { super("user.blocked", null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserDeleteException.java ================================================ package com.ruoyi.common.exception.user; /** * 用户账号已被删除 * * @author ruoyi */ public class UserDeleteException extends UserException { private static final long serialVersionUID = 1L; public UserDeleteException() { super("user.password.delete", null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserException.java ================================================ package com.ruoyi.common.exception.user; import com.ruoyi.common.exception.base.BaseException; /** * 用户信息异常类 * * @author ruoyi */ public class UserException extends BaseException { private static final long serialVersionUID = 1L; public UserException(String code, Object[] args) { super("user", code, args, null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserNotExistsException.java ================================================ package com.ruoyi.common.exception.user; /** * 用户不存在异常类 * * @author ruoyi */ public class UserNotExistsException extends UserException { private static final long serialVersionUID = 1L; public UserNotExistsException() { super("user.not.exists", null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java ================================================ package com.ruoyi.common.exception.user; /** * 用户密码不正确或不符合规范异常类 * * @author ruoyi */ public class UserPasswordNotMatchException extends UserException { private static final long serialVersionUID = 1L; public UserPasswordNotMatchException() { super("user.password.not.match", null); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitCountException.java ================================================ package com.ruoyi.common.exception.user; /** * 用户错误记数异常类 * * @author ruoyi */ public class UserPasswordRetryLimitCountException extends UserException { private static final long serialVersionUID = 1L; public UserPasswordRetryLimitCountException(int retryLimitCount) { super("user.password.retry.limit.count", new Object[] { retryLimitCount }); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordRetryLimitExceedException.java ================================================ package com.ruoyi.common.exception.user; /** * 用户错误最大次数异常类 * * @author ruoyi */ public class UserPasswordRetryLimitExceedException extends UserException { private static final long serialVersionUID = 1L; public UserPasswordRetryLimitExceedException(int retryLimitCount) { super("user.password.retry.limit.exceed", new Object[] { retryLimitCount }); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/json/JSON.java ================================================ package com.ruoyi.common.json; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; /** * JSON解析处理 * * @author ruoyi */ public class JSON { public static final String DEFAULT_FAIL = "\"Parse failed\""; private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); public static void marshal(File file, Object value) throws Exception { try { objectWriter.writeValue(file, value); } catch (JsonGenerationException e) { throw new Exception(e); } catch (JsonMappingException e) { throw new Exception(e); } catch (IOException e) { throw new Exception(e); } } public static void marshal(OutputStream os, Object value) throws Exception { try { objectWriter.writeValue(os, value); } catch (JsonGenerationException e) { throw new Exception(e); } catch (JsonMappingException e) { throw new Exception(e); } catch (IOException e) { throw new Exception(e); } } public static String marshal(Object value) throws Exception { try { return objectWriter.writeValueAsString(value); } catch (JsonGenerationException e) { throw new Exception(e); } catch (JsonMappingException e) { throw new Exception(e); } catch (IOException e) { throw new Exception(e); } } public static byte[] marshalBytes(Object value) throws Exception { try { return objectWriter.writeValueAsBytes(value); } catch (JsonGenerationException e) { throw new Exception(e); } catch (JsonMappingException e) { throw new Exception(e); } catch (IOException e) { throw new Exception(e); } } public static T unmarshal(File file, Class valueType) throws Exception { try { return objectMapper.readValue(file, valueType); } catch (JsonParseException e) { throw new Exception(e); } catch (JsonMappingException e) { throw new Exception(e); } catch (IOException e) { throw new Exception(e); } } public static T unmarshal(InputStream is, Class valueType) throws Exception { try { return objectMapper.readValue(is, valueType); } catch (JsonParseException e) { throw new Exception(e); } catch (JsonMappingException e) { throw new Exception(e); } catch (IOException e) { throw new Exception(e); } } public static T unmarshal(String str, Class valueType) throws Exception { try { return objectMapper.readValue(str, valueType); } catch (JsonParseException e) { throw new Exception(e); } catch (JsonMappingException e) { throw new Exception(e); } catch (IOException e) { throw new Exception(e); } } public static T unmarshal(byte[] bytes, Class valueType) throws Exception { try { if (bytes == null) { bytes = new byte[0]; } return objectMapper.readValue(bytes, 0, bytes.length, valueType); } catch (JsonParseException e) { throw new Exception(e); } catch (JsonMappingException e) { throw new Exception(e); } catch (IOException e) { throw new Exception(e); } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/json/JSONObject.java ================================================ package com.ruoyi.common.json; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.utils.StringUtils; /** * 通用消息对象,基于Map实现的可嵌套数据结构。 支持JSON数据结构。 * * @author ruoyi */ public class JSONObject extends LinkedHashMap { private static final long serialVersionUID = 1L; private static final Pattern arrayNamePattern = Pattern.compile("(\\w+)((\\[\\d+\\])+)"); private static final ObjectMapper objectMapper = new ObjectMapper(); /** * 数组结构。 */ public static class JSONArray extends ArrayList { private static final long serialVersionUID = 1L; public JSONArray() { super(); } public JSONArray(int size) { super(size); } @Override public String toString() { try { return JSON.marshal(this); } catch (Exception e) { throw new RuntimeException(e); } } @Override public Object set(int index, Object element) { return super.set(index, transfer(element)); } @Override public boolean add(Object element) { return super.add(transfer(element)); } @Override public void add(int index, Object element) { super.add(index, transfer(element)); } } public JSONObject() { super(); } public JSONObject(final JSONObject other) { super(other); } @Override public String toString() { try { return JSON.marshal(this); } catch (Exception e) { throw new RuntimeException(e); } } /** * 转换为紧凑格式的字符串。 * * @return 返回本对象紧凑格式字符串。 */ public String toCompactString() { try { return objectMapper.writeValueAsString(this); } catch (Exception e) { throw new RuntimeException(e); } } /** * 获取指定字段的整数值。如果字段不存在,或者无法转换为整数,返回null。 * * @param name 字段名,支持多级。 * @return 返回指定的整数值,或者null。 */ public Integer intValue(final String name) { return valueAsInt(value(name)); } /** * 获取指定字段的整数值。如果字段不存在,或者无法转换为整数,返回defaultValue。 * * @param name 字段名,支持多级。 * @param defaultValue 查询失败时,返回的值。 * @return 返回指定的整数值,或者defaultValue。 */ public Integer intValue(final String name, final Integer defaultValue) { return StringUtils.nvl(intValue(name), defaultValue); } /** * 获取指定字段的长整数值。如果字段不存在,或者无法转换为长整数,返回null。 * * @param name 字段名,支持多级。 * @return 返回指定的长整数值,或者null。 */ public Long longValue(final String name) { return valueAsLong(value(name)); } /** * 获取指定字段的长整数值。如果字段不存在,或者无法转换为长整数,返回defaultValue。 * * @param name 字段名,支持多级。 * @param defaultValue 查询失败时,返回的值。 * @return 返回指定的长整数值,或者defaultValue。 */ public Long longValue(final String name, final Long defaultValue) { return StringUtils.nvl(longValue(name), defaultValue); } /** * 获取指定字段的布尔值。如果字段不存在,或者无法转换为布尔型,返回null。 * * @param name 字段名,支持多级。 * @return 返回指定的布尔值,或者null。 */ public Boolean boolValue(final String name) { return valueAsBool(value(name)); } /** * 获取指定字段的布尔值。如果字段不存在,或者无法转换为布尔型,返回defaultValue。 * * @param name 字段名,支持多级。 * @param defaultValue 查询失败时,返回的值。 * @return 返回指定的布尔值,或者defaultValue。 */ public Boolean boolValue(final String name, final Boolean defaultValue) { return StringUtils.nvl(boolValue(name), defaultValue); } /** * 获取指定字段的字符串值。如果字段不存在,返回null。 * * @param name 字段名,支持多级。 * @return 返回指定的字符串值,或者null。 */ public String strValue(final String name) { return valueAsStr(value(name)); } /** * 获取指定字段的字符串值。如果字段不存在,返回defaultValue。 * * @param name 字段名,支持多级。 * @param defaultValue 查询失败时,返回的值。 * @return 返回指定的字符串值,或者defaultValue。 */ public String strValue(final String name, final String defaultValue) { return StringUtils.nvl(strValue(name), defaultValue); } /** * 获取指定字段的值。 * * @param name 字段名,支持多级,支持数组下标。 * @return 返回指定字段的值。 */ public Object value(final String name) { final int indexDot = name.indexOf('.'); if (indexDot >= 0) { return obj(name.substring(0, indexDot)).value(name.substring(indexDot + 1)); } else { final Matcher matcher = arrayNamePattern.matcher(name); if (matcher.find()) { return endArray(matcher.group(1), matcher.group(2), new EndArrayCallback() { @Override public Object callback(JSONArray arr, int index) { return elementAt(arr, index); } }); } else { return get(name); } } } /** * 设置指定字段的值。 * * @param name 字段名,支持多级,支持数组下标。 * @param value 字段值。 * @return 返回本对象。 */ public JSONObject value(final String name, final Object value) { final int indexDot = name.indexOf('.'); if (indexDot >= 0) { obj(name.substring(0, indexDot)).value(name.substring(indexDot + 1), value); } else { final Matcher matcher = arrayNamePattern.matcher(name); if (matcher.find()) { endArray(matcher.group(1), matcher.group(2), new EndArrayCallback() { @Override public Void callback(JSONArray arr, int index) { elementAt(arr, index, value); return null; } }); } else { set(name, value); } } return this; } /** * 获取对象(非标量类型)字段。返回的数据是一个结构体。当不存在指定对象时,则为指定的名字创建一个空的MessageObject对象。 * * @param name 字段名。不支持多级名字,支持数组下标。 * @return 返回指定的对象。如果对象不存在,则为指定的名字创建一个空的MessageObject对象。 */ public JSONObject obj(final String name) { final Matcher matcher = arrayNamePattern.matcher(name); if (matcher.find()) { return endArray(matcher.group(1), matcher.group(2), new EndArrayCallback() { @Override public JSONObject callback(JSONArray arr, int index) { return objAt(arr, index); } }); } else { JSONObject obj = getObj(name); if (obj == null) { obj = new JSONObject(); put(name, obj); } return obj; } } /** * 获取数组字段。将名字对应的对象以数组对象返回,当指定的字段不存在时,创建一个空的数组。 * * @param name 字段名。不支持多级名字,不支持下标。 * @return 返回一个数组(List)。 */ public JSONArray arr(final String name) { JSONArray arr = getArr(name); if (arr == null) { arr = new JSONArray(); put(name, arr); } return arr; } /** * 获取对象(非标量类型)字段。返回的数据是一个结构体。 * * @param name 字段名。 * @return 返回指定的对象字段。 */ public JSONObject getObj(final String name) { return (JSONObject) get(name); } /** * 获取数组类型字段。 * * @param name 字段名。 * @return 返回数组类型字段。 */ public JSONArray getArr(final String name) { return (JSONArray) get(name); } /** * 返回字段整数值。如果不存在,返回null。 * * @param name 字段名。 * @return 返回指定字段整数值。 */ public Integer getInt(final String name) { return valueAsInt(get(name)); } /** * 返回字段整数值。如果不存在,返回defaultValue。 * * @param name 字段名。 * @param defaultValue 字段不存在时,返回的值。 * @return 返回指定字段整数值。 */ public Integer getInt(final String name, Integer defaultValue) { return StringUtils.nvl(getInt(name), defaultValue); } /** * 返回字段长整数值。如果不存在,返回null。 * * @param name 字段名。 * @return 返回指定字段长整数值。 */ public Long getLong(final String name) { return valueAsLong(get(name)); } /** * 返回字段长整数值。如果不存在,返回defaultValue。 * * @param name 字段名。 * @param defaultValue 字段不存在时,返回的值。 * @return 返回指定字段长整数值。 */ public Long getLong(final String name, Long defaultValue) { return StringUtils.nvl(getLong(name), defaultValue); } /** * 返回字段字符串值。如果不存在,返回null。 * * @param name 字段名。 * @return 返回指定字段字符串值。 */ public String getStr(final String name) { return valueAsStr(get(name)); } /** * 返回字段字符串值。如果不存在,返回defaultValue。 * * @param name 字段名。 * @param defaultValue 字段不存在时,返回的值。 * @return 返回指定字段字符串值。 */ public String getStr(final String name, final String defaultValue) { return StringUtils.nvl(getStr(name), defaultValue); } /** * 字段值按照布尔类型返回。如果不存在,返回null。 * * @param name 字段名。 * @return 字段值。 */ public Boolean getBool(final String name) { return valueAsBool(get(name)); } /** * 字段值按照布尔类型返回。如果不存在,返回defaultValue。 * * @param name 字段名。 * @param defaultValue 字段不存在时,返回的值。 * @return 字段值。 */ public Boolean getBool(final String name, final Boolean defaultValue) { return StringUtils.nvl(getBool(name), defaultValue); } /** * 设置字段值 * * @param name 字段名 * @param value 字段值(标量:数字、字符串、布尔型;结构体:MessageObject)。 如果是Map类型同时非MessageObject类型,则自动转换为MessageObject类型再存入 * (此时,再修改Map中的数据,将不会体现到本对象中)。 * @return 返回本对象 */ public JSONObject set(final String name, final Object value) { put(name, value); return this; } /** * 将本对象转换为Java Bean。 * * @param beanClass Java Bean的类对象。 * @return 返回转换后的Java Bean。 */ public T asBean(Class beanClass) { try { return JSON.unmarshal(JSON.marshal(this), beanClass); } catch (Exception e) { throw new RuntimeException(e); } } /** * 重载基类的方法。如果 value 是 Map 类型,但不是 MessageObject 类型,则创建一个包含内容等同于原 Map 的 MessageObject 作为 value(注意:此后再更改 Map 的内容,将不会反映到 * MessageObject 中)。 重载此方法的目的是为了使JSON能够正确地解析为MessageObject对象。不建议直接调用此方法,请使用 set(name, value)方法设置字段值。 */ @Override public Object put(String key, Object value) { return super.put(key, transfer(value)); } public static Integer valueAsInt(Object value) { if (value instanceof Integer) { return (Integer) value; } else if (value instanceof Number) { return ((Number) value).intValue(); } else if (value instanceof String) { return Integer.valueOf((String) value); } else if (value instanceof Boolean) { return ((Boolean) value) ? 1 : 0; } else { return null; } } public static Long valueAsLong(Object value) { if (value instanceof Long) { return (Long) value; } else if (value instanceof Number) { return ((Number) value).longValue(); } else if (value instanceof String) { return Long.valueOf((String) value); } else if (value instanceof Boolean) { return ((Boolean) value) ? 1L : 0L; } else { return null; } } public static String valueAsStr(Object value) { if (value instanceof String) { return (String) value; } else if (value != null) { return value.toString(); } else { return null; } } public static Boolean valueAsBool(Object value) { if (value instanceof Boolean) { return (Boolean) value; } else if (value instanceof Number) { return ((Number) value).doubleValue() != 0.0; } else if (value instanceof String) { return Boolean.valueOf((String) value); } else { return null; } } /** * 将所有层次中凡是Map类型同时又不是MessageObject的类型,转换为MessageObject类型。 * * @param value 值。 * @return 返回转换后的值。 */ @SuppressWarnings("unchecked") private static Object transfer(final Object value) { if (!(value instanceof JSONObject) && value instanceof Map) { return toObj((Map) value); } else if (!(value instanceof JSONArray) && value instanceof Collection) { return toArr((Collection) value); } else { return value; } } private static JSONArray toArr(final Collection list) { final JSONArray arr = new JSONArray(list.size()); for (final Object element : list) { arr.add(element); } return arr; } private static JSONObject toObj(final Map map) { final JSONObject obj = new JSONObject(); for (final Map.Entry ent : map.entrySet()) { obj.put(ent.getKey(), transfer(ent.getValue())); } return obj; } /** * 将指定下标元素作为数组返回,如果不存在,则在该位置创建一个空的数组。 * * @param arr 当前数组。 * @param index 下标。 * @return 返回当前数组指定下标的元素,该元素应该是一个数组。 */ private static JSONArray arrayAt(JSONArray arr, int index) { expand(arr, index); if (arr.get(index) == null) { arr.set(index, new JSONArray()); } return (JSONArray) arr.get(index); } /** * 将指定下标元素作为结构体返回,如果不存在,则在该位置创建一个空的结构体。 * * @param arr 当前数组。 * @param index 下标。 * @return 返回当前数组指定下标元素,该元素是一个结构体。 */ private static JSONObject objAt(final JSONArray arr, int index) { expand(arr, index); if (arr.get(index) == null) { arr.set(index, new JSONObject()); } return (JSONObject) arr.get(index); } /** * 设置数组指定下标位置的值。 * * @param arr 数组。 * @param index 下标。 * @param value 值。 */ private static void elementAt(final JSONArray arr, final int index, final Object value) { expand(arr, index).set(index, value); } /** * 获取数组指定下标元素的值。 * * @param arr 数组。 * @param index 下标。 * @return 值。 */ private static Object elementAt(final JSONArray arr, final int index) { return expand(arr, index).get(index); } /** * 扩展数组到指定下标,以防止访问时下标越界。 * * @param arr 数组 * @param index 下标 * @return 返回传入的数组 */ private static JSONArray expand(final JSONArray arr, final int index) { while (arr.size() <= index) { arr.add(null); } return arr; } /** * 最后数组回调。 * * @author Mike * * @param 回调返回数据类型。 */ private interface EndArrayCallback { /** * 当定位到最后一级数组,将调用本方法。 * * @param arr 最后一级数组对象。 * @param index 最后一级索引。 * @return 返回回调的返回值。 */ T callback(JSONArray arr, int index); } /** * 处理多维数组的工具函数(包括一维数组)。多维数组的名字如:arrary[1][2][3], 则name=array,indexStr=[1][2][3],在callback中,endArr将是 * array[1][2]指定的对象,indexe=3。 * * @param name 不带下标的名字,不支持多级名字。 * @param indexesStr 索引部分的字符串,如:[1][2][3] * @param callback 回调函数。 * @return 返回回调函数的返回值。 */ private T endArray(final String name, final String indexesStr, final EndArrayCallback callback) { JSONArray endArr = arr(name); final int[] indexes = parseIndexes(indexesStr); int i = 0; while (i < indexes.length - 1) { endArr = arrayAt(endArr, indexes[i++]); } return callback.callback(endArr, indexes[i]); } private static int[] parseIndexes(final String s) { int[] indexes = null; List list = new ArrayList(); final StringTokenizer st = new StringTokenizer(s, "[]"); while (st.hasMoreTokens()) { final int index = Integer.valueOf(st.nextToken()); if (index < 0) { throw new RuntimeException(String.format("Illegal index %1$d in \"%2$s\"", index, s)); } list.add(index); } indexes = new int[list.size()]; int i = 0; for (Integer tmp : list.toArray(new Integer[list.size()])) { indexes[i++] = tmp; } return indexes; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/AddressUtils.java ================================================ package com.ruoyi.common.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.http.HttpUtils; /** * 获取地址类 * * @author ruoyi */ public class AddressUtils { private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); // IP地址查询 public static final String IP_URL = "https://whois.pconline.com.cn/ipJson.jsp"; // 未知地址 public static final String UNKNOWN = "XX XX"; public static String getRealAddressByIP(String ip) { // 内网不查询 if (IpUtils.internalIp(ip)) { return "内网IP"; } if (RuoYiConfig.isAddressEnabled()) { try { String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK); if (StringUtils.isEmpty(rspStr)) { log.error("获取地理位置异常 {}", ip); return UNKNOWN; } JSONObject obj = JSONObject.parseObject(rspStr); String region = obj.getString("pro"); String city = obj.getString("city"); return String.format("%s %s", region, city); } catch (Exception e) { log.error("获取地理位置异常 {}", e); } } return UNKNOWN; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/Arith.java ================================================ package com.ruoyi.common.utils; import java.math.BigDecimal; import java.math.RoundingMode; /** * 精确的浮点数运算 * * @author ruoyi */ public class Arith { /** 默认除法运算精度 */ private static final int DEF_DIV_SCALE = 10; /** 这个类不能实例化 */ private Arith() { } /** * 提供精确的加法运算。 * @param v1 被加数 * @param v2 加数 * @return 两个参数的和 */ public static double add(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.add(b2).doubleValue(); } /** * 提供精确的减法运算。 * @param v1 被减数 * @param v2 减数 * @return 两个参数的差 */ public static double sub(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.subtract(b2).doubleValue(); } /** * 提供精确的乘法运算。 * @param v1 被乘数 * @param v2 乘数 * @return 两个参数的积 */ public static double mul(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.multiply(b2).doubleValue(); } /** * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 * 小数点以后10位,以后的数字四舍五入。 * @param v1 被除数 * @param v2 除数 * @return 两个参数的商 */ public static double div(double v1, double v2) { return div(v1, v2, DEF_DIV_SCALE); } /** * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 * 定精度,以后的数字四舍五入。 * @param v1 被除数 * @param v2 除数 * @param scale 表示表示需要精确到小数点以后几位。 * @return 两个参数的商 */ public static double div(double v1, double v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); if (b1.compareTo(BigDecimal.ZERO) == 0) { return BigDecimal.ZERO.doubleValue(); } return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); } /** * 提供精确的小数位四舍五入处理。 * @param v 需要四舍五入的数字 * @param scale 小数点后保留几位 * @return 四舍五入后的结果 */ public static double round(double v, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b = new BigDecimal(Double.toString(v)); return b.divide(BigDecimal.ONE, scale, RoundingMode.HALF_UP).doubleValue(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java ================================================ package com.ruoyi.common.utils; import java.util.Iterator; import java.util.Set; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.utils.spring.SpringUtils; /** * Cache工具类 * * @author ruoyi */ public class CacheUtils { private static Logger logger = LoggerFactory.getLogger(CacheUtils.class); private static CacheManager cacheManager = SpringUtils.getBean(CacheManager.class); private static final String SYS_CACHE = "sys-cache"; /** * 获取SYS_CACHE缓存 * * @param key * @return */ public static Object get(String key) { return get(SYS_CACHE, key); } /** * 获取SYS_CACHE缓存 * * @param key * @param defaultValue * @return */ public static Object get(String key, Object defaultValue) { Object value = get(key); return value != null ? value : defaultValue; } /** * 写入SYS_CACHE缓存 * * @param key * @return */ public static void put(String key, Object value) { put(SYS_CACHE, key, value); } /** * 从SYS_CACHE缓存中移除 * * @param key * @return */ public static void remove(String key) { remove(SYS_CACHE, key); } /** * 获取缓存 * * @param cacheName * @param key * @return */ public static Object get(String cacheName, String key) { return getCache(cacheName).get(getKey(key)); } /** * 获取缓存 * * @param cacheName * @param key * @param defaultValue * @return */ public static Object get(String cacheName, String key, Object defaultValue) { Object value = get(cacheName, getKey(key)); return value != null ? value : defaultValue; } /** * 写入缓存 * * @param cacheName * @param key * @param value */ public static void put(String cacheName, String key, Object value) { getCache(cacheName).put(getKey(key), value); } /** * 从缓存中移除 * * @param cacheName * @param key */ public static void remove(String cacheName, String key) { getCache(cacheName).remove(getKey(key)); } /** * 从缓存中移除所有 * * @param cacheName */ public static void removeAll(String cacheName) { Cache cache = getCache(cacheName); Set keys = cache.keys(); for (Iterator it = keys.iterator(); it.hasNext();) { cache.remove(it.next()); } logger.info("清理缓存: {} => {}", cacheName, keys); } /** * 从缓存中移除指定key * * @param keys */ public static void removeByKeys(Set keys) { removeByKeys(SYS_CACHE, keys); } /** * 从缓存中移除指定key * * @param cacheName * @param keys */ public static void removeByKeys(String cacheName, Set keys) { for (Iterator it = keys.iterator(); it.hasNext();) { remove(it.next()); } logger.info("清理缓存: {} => {}", cacheName, keys); } /** * 获取缓存键名 * * @param key * @return */ private static String getKey(String key) { return key; } /** * 获得一个Cache,没有则显示日志。 * * @param cacheName * @return */ public static Cache getCache(String cacheName) { Cache cache = cacheManager.getCache(cacheName); if (cache == null) { throw new RuntimeException("当前系统中没有定义“" + cacheName + "”这个缓存。"); } return cache; } /** * 获取所有缓存 * * @return 缓存组 */ public static String[] getCacheNames() { return ((EhCacheManager) cacheManager).getCacheManager().getCacheNames(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/CookieUtils.java ================================================ package com.ruoyi.common.utils; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; /** * Cookie工具类 * * @author ruoyi */ public class CookieUtils { /** * 设置 Cookie(生成时间为1天) * * @param name 名称 * @param value 值 */ public static void setCookie(HttpServletResponse response, String name, String value) { setCookie(response, name, value, 60 * 60 * 24); } /** * 设置 Cookie * * @param name 名称 * @param value 值 * @param maxAge 生存时间(单位秒) * @param uri 路径 */ public static void setCookie(HttpServletResponse response, String name, String value, String path) { setCookie(response, name, value, path, 60 * 60 * 24); } /** * 设置 Cookie * * @param name 名称 * @param value 值 * @param maxAge 生存时间(单位秒) * @param uri 路径 */ public static void setCookie(HttpServletResponse response, String name, String value, int maxAge) { setCookie(response, name, value, "/", maxAge); } /** * 设置 Cookie * * @param name 名称 * @param value 值 * @param maxAge 生存时间(单位秒) * @param uri 路径 */ public static void setCookie(HttpServletResponse response, String name, String value, String path, int maxAge) { Cookie cookie = new Cookie(name, null); cookie.setPath(path); cookie.setMaxAge(maxAge); try { cookie.setValue(URLEncoder.encode(value, "utf-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } response.addCookie(cookie); } /** * 获得指定Cookie的值 * * @param name 名称 * @return 值 */ public static String getCookie(HttpServletRequest request, String name) { return getCookie(request, null, name, false); } /** * 获得指定Cookie的值,并删除。 * * @param name 名称 * @return 值 */ public static String getCookie(HttpServletRequest request, HttpServletResponse response, String name) { return getCookie(request, response, name, true); } /** * 获得指定Cookie的值 * * @param request 请求对象 * @param response 响应对象 * @param name 名字 * @param isRemove 是否移除 * @return 值 */ public static String getCookie(HttpServletRequest request, HttpServletResponse response, String name, boolean isRemove) { String value = null; Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals(name)) { try { value = URLDecoder.decode(cookie.getValue(), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } if (isRemove) { cookie.setMaxAge(0); response.addCookie(cookie); } } } } return value; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java ================================================ package com.ruoyi.common.utils; import java.lang.management.ManagementFactory; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Date; import org.apache.commons.lang3.time.DateFormatUtils; /** * 时间工具类 * * @author ruoyi */ @SuppressWarnings("deprecation") public class DateUtils extends org.apache.commons.lang3.time.DateUtils { public static String YYYY = "yyyy"; public static String YYYY_MM = "yyyy-MM"; public static String YYYY_MM_DD = "yyyy-MM-dd"; public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; private static String[] parsePatterns = { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; /** * 获取当前Date型日期 * * @return Date() 当前日期 */ public static Date getNowDate() { return new Date(); } /** * 获取当前日期, 默认格式为yyyy-MM-dd * * @return String */ public static String getDate() { return dateTimeNow(YYYY_MM_DD); } public static final String getTime() { return dateTimeNow(YYYY_MM_DD_HH_MM_SS); } public static final String dateTimeNow() { return dateTimeNow(YYYYMMDDHHMMSS); } public static final String dateTimeNow(final String format) { return parseDateToStr(format, new Date()); } public static final String dateTime(final Date date) { return parseDateToStr(YYYY_MM_DD, date); } public static final String parseDateToStr(final String format, final Date date) { return new SimpleDateFormat(format).format(date); } public static final Date dateTime(final String format, final String ts) { try { return new SimpleDateFormat(format).parse(ts); } catch (ParseException e) { throw new RuntimeException(e); } } /** * 日期路径 即年/月/日 如2018/08/08 */ public static final String datePath() { Date now = new Date(); return DateFormatUtils.format(now, "yyyy/MM/dd"); } /** * 日期路径 即年/月/日 如20180808 */ public static final String dateTime() { Date now = new Date(); return DateFormatUtils.format(now, "yyyyMMdd"); } /** * 日期型字符串转化为日期 格式 */ public static Date parseDate(Object str) { if (str == null) { return null; } try { return parseDate(str.toString(), parsePatterns); } catch (ParseException e) { return null; } } /** * 获取服务器启动时间 */ public static Date getServerStartDate() { long time = ManagementFactory.getRuntimeMXBean().getStartTime(); return new Date(time); } /** * 计算相差天数 */ public static int differentDaysByMillisecond(Date date1, Date date2) { return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); } /** * 计算时间差 * * @param endDate 最后时间 * @param startTime 开始时间 * @return 时间差(天/小时/分钟) */ public static String timeDistance(Date endDate, Date startTime) { long nd = 1000 * 24 * 60 * 60; long nh = 1000 * 60 * 60; long nm = 1000 * 60; // long ns = 1000; // 获得两个时间的毫秒时间差异 long diff = endDate.getTime() - startTime.getTime(); // 计算差多少天 long day = diff / nd; // 计算差多少小时 long hour = diff % nd / nh; // 计算差多少分钟 long min = diff % nd % nh / nm; // 计算差多少秒//输出结果 // long sec = diff % nd % nh % nm / ns; return day + "天" + hour + "小时" + min + "分钟"; } /** * 增加 LocalDateTime ==> Date */ public static Date toDate(LocalDateTime temporalAccessor) { ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); return Date.from(zdt.toInstant()); } /** * 增加 LocalDate ==> Date */ public static Date toDate(LocalDate temporalAccessor) { LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); return Date.from(zdt.toInstant()); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/DesensitizedUtil.java ================================================ package com.ruoyi.common.utils; /** * 脱敏工具类 * * @author ruoyi */ public class DesensitizedUtil { /** * 密码的全部字符都用*代替,比如:****** * * @param password 密码 * @return 脱敏后的密码 */ public static String password(String password) { if (StringUtils.isBlank(password)) { return StringUtils.EMPTY; } return StringUtils.repeat('*', password.length()); } /** * 车牌中间用*代替,如果是错误的车牌,不处理 * * @param carLicense 完整的车牌号 * @return 脱敏后的车牌 */ public static String carLicense(String carLicense) { if (StringUtils.isBlank(carLicense)) { return StringUtils.EMPTY; } // 普通车牌 if (carLicense.length() == 7) { carLicense = StringUtils.hide(carLicense, 3, 6); } else if (carLicense.length() == 8) { // 新能源车牌 carLicense = StringUtils.hide(carLicense, 3, 7); } return carLicense; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java ================================================ package com.ruoyi.common.utils; import java.util.List; import org.springframework.stereotype.Component; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.domain.entity.SysDictData; /** * 字典工具类 * * @author ruoyi */ @Component public class DictUtils { /** * 分隔符 */ public static final String SEPARATOR = ","; /** * 设置字典缓存 * * @param key 参数键 * @param dictDatas 字典数据列表 */ public static void setDictCache(String key, List dictDatas) { CacheUtils.put(getCacheName(), getCacheKey(key), dictDatas); } /** * 获取字典缓存 * * @param key 参数键 * @return dictDatas 字典数据列表 */ public static List getDictCache(String key) { Object cacheObj = CacheUtils.get(getCacheName(), getCacheKey(key)); if (StringUtils.isNotNull(cacheObj)) { return StringUtils.cast(cacheObj); } return null; } /** * 根据字典类型和字典值获取字典标签 * * @param dictType 字典类型 * @param dictValue 字典值 * @return 字典标签 */ public static String getDictLabel(String dictType, String dictValue) { if (StringUtils.isEmpty(dictValue)) { return StringUtils.EMPTY; } return getDictLabel(dictType, dictValue, SEPARATOR); } /** * 根据字典类型和字典标签获取字典值 * * @param dictType 字典类型 * @param dictLabel 字典标签 * @return 字典值 */ public static String getDictValue(String dictType, String dictLabel) { if (StringUtils.isEmpty(dictLabel)) { return StringUtils.EMPTY; } return getDictValue(dictType, dictLabel, SEPARATOR); } /** * 根据字典类型和字典值获取字典标签 * * @param dictType 字典类型 * @param dictValue 字典值 * @param separator 分隔符 * @return 字典标签 */ public static String getDictLabel(String dictType, String dictValue, String separator) { StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); if (StringUtils.isNull(datas)) { return StringUtils.EMPTY; } if (StringUtils.containsAny(dictValue, separator)) { for (SysDictData dict : datas) { for (String value : dictValue.split(separator)) { if (value.equals(dict.getDictValue())) { propertyString.append(dict.getDictLabel()).append(separator); break; } } } } else { for (SysDictData dict : datas) { if (dictValue.equals(dict.getDictValue())) { return dict.getDictLabel(); } } } return StringUtils.stripEnd(propertyString.toString(), separator); } /** * 根据字典类型和字典标签获取字典值 * * @param dictType 字典类型 * @param dictLabel 字典标签 * @param separator 分隔符 * @return 字典值 */ public static String getDictValue(String dictType, String dictLabel, String separator) { StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); if (StringUtils.isNull(datas)) { return StringUtils.EMPTY; } if (StringUtils.containsAny(dictLabel, separator)) { for (SysDictData dict : datas) { for (String label : dictLabel.split(separator)) { if (label.equals(dict.getDictLabel())) { propertyString.append(dict.getDictValue()).append(separator); break; } } } } else { for (SysDictData dict : datas) { if (dictLabel.equals(dict.getDictLabel())) { return dict.getDictValue(); } } } return StringUtils.stripEnd(propertyString.toString(), separator); } /** * 根据字典类型获取字典所有值 * * @param dictType 字典类型 * @return 字典值 */ public static String getDictValues(String dictType) { StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); if (StringUtils.isNull(datas)) { return StringUtils.EMPTY; } for (SysDictData dict : datas) { propertyString.append(dict.getDictValue()).append(SEPARATOR); } return StringUtils.stripEnd(propertyString.toString(), SEPARATOR); } /** * 根据字典类型获取字典所有标签 * * @param dictType 字典类型 * @return 字典值 */ public static String getDictLabels(String dictType) { StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); if (StringUtils.isNull(datas)) { return StringUtils.EMPTY; } for (SysDictData dict : datas) { propertyString.append(dict.getDictLabel()).append(SEPARATOR); } return StringUtils.stripEnd(propertyString.toString(), SEPARATOR); } /** * 删除指定字典缓存 * * @param key 字典键 */ public static void removeDictCache(String key) { CacheUtils.remove(getCacheName(), getCacheKey(key)); } /** * 清空字典缓存 */ public static void clearDictCache() { CacheUtils.removeAll(getCacheName()); } /** * 获取cache name * * @return 缓存名 */ public static String getCacheName() { return Constants.SYS_DICT_CACHE; } /** * 设置cache key * * @param configKey 参数键 * @return 缓存键key */ public static String getCacheKey(String configKey) { return Constants.SYS_DICT_KEY + configKey; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java ================================================ package com.ruoyi.common.utils; import java.io.PrintWriter; import java.io.StringWriter; import org.apache.commons.lang3.exception.ExceptionUtils; /** * 错误信息处理类。 * * @author ruoyi */ public class ExceptionUtil { /** * 获取exception的详细错误信息。 */ public static String getExceptionMessage(Throwable e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw, true)); return sw.toString(); } public static String getRootErrorMessage(Exception e) { Throwable root = ExceptionUtils.getRootCause(e); root = (root == null ? e : root); if (root == null) { return ""; } String msg = root.getMessage(); if (msg == null) { return "null"; } return StringUtils.defaultString(msg); } /** * 检测异常e被触发的原因是不是因为异常cause。 * * @param e 捕获的异常。 * @param cause 异常触发原因。 * @return 如果异常e是由cause类异常触发,则返回true;否则返回false。 */ public static boolean isCausedBy(final Throwable e, final Class cause) { if (cause.isAssignableFrom(e.getClass())) { return true; } else { Throwable t = e.getCause(); while (t != null && t != e) { if (cause.isAssignableFrom(t.getClass())) { return true; } t = t.getCause(); } return false; } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/IpUtils.java ================================================ package com.ruoyi.common.utils; import java.net.InetAddress; import java.net.UnknownHostException; import jakarta.servlet.http.HttpServletRequest; /** * 获取IP方法 * * @author ruoyi */ public class IpUtils { public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"; // 匹配 ip public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")"; public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))"; // 匹配网段 public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")"; /** * 获取客户端IP * * @param request 请求对象 * @return IP地址 */ public static String getIpAddr(HttpServletRequest request) { if (request == null) { return "unknown"; } String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); } /** * 检查是否为内部IP地址 * * @param ip IP地址 * @return 结果 */ public static boolean internalIp(String ip) { byte[] addr = textToNumericFormatV4(ip); return internalIp(addr) || "127.0.0.1".equals(ip); } /** * 检查是否为内部IP地址 * * @param addr byte地址 * @return 结果 */ private static boolean internalIp(byte[] addr) { if (StringUtils.isNull(addr) || addr.length < 2) { return true; } final byte b0 = addr[0]; final byte b1 = addr[1]; // 10.x.x.x/8 final byte SECTION_1 = 0x0A; // 172.16.x.x/12 final byte SECTION_2 = (byte) 0xAC; final byte SECTION_3 = (byte) 0x10; final byte SECTION_4 = (byte) 0x1F; // 192.168.x.x/16 final byte SECTION_5 = (byte) 0xC0; final byte SECTION_6 = (byte) 0xA8; switch (b0) { case SECTION_1: return true; case SECTION_2: if (b1 >= SECTION_3 && b1 <= SECTION_4) { return true; } case SECTION_5: switch (b1) { case SECTION_6: return true; } default: return false; } } /** * 将IPv4地址转换成字节 * * @param text IPv4地址 * @return byte 字节 */ public static byte[] textToNumericFormatV4(String text) { if (text.length() == 0) { return null; } byte[] bytes = new byte[4]; String[] elements = text.split("\\.", -1); try { long l; int i; switch (elements.length) { case 1: l = Long.parseLong(elements[0]); if ((l < 0L) || (l > 4294967295L)) { return null; } bytes[0] = (byte) (int) (l >> 24 & 0xFF); bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 2: l = Integer.parseInt(elements[0]); if ((l < 0L) || (l > 255L)) { return null; } bytes[0] = (byte) (int) (l & 0xFF); l = Integer.parseInt(elements[1]); if ((l < 0L) || (l > 16777215L)) { return null; } bytes[1] = (byte) (int) (l >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 3: for (i = 0; i < 2; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)) { return null; } bytes[i] = (byte) (int) (l & 0xFF); } l = Integer.parseInt(elements[2]); if ((l < 0L) || (l > 65535L)) { return null; } bytes[2] = (byte) (int) (l >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 4: for (i = 0; i < 4; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)) { return null; } bytes[i] = (byte) (int) (l & 0xFF); } break; default: return null; } } catch (NumberFormatException e) { return null; } return bytes; } /** * 获取IP地址 * * @return 本地IP地址 */ public static String getHostIp() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { } return "127.0.0.1"; } /** * 获取主机名 * * @return 本地主机名 */ public static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { } return "未知"; } /** * 从多级反向代理中获得第一个非unknown IP地址 * * @param ip 获得的IP地址 * @return 第一个非unknown IP地址 */ public static String getMultistageReverseProxyIp(String ip) { // 多级反向代理检测 if (ip != null && ip.indexOf(",") > 0) { final String[] ips = ip.trim().split(","); for (String subIp : ips) { if (false == isUnknown(subIp)) { ip = subIp; break; } } } return StringUtils.substring(ip, 0, 255); } /** * 检测给定字符串是否为未知,多用于检测HTTP请求相关 * * @param checkString 被检测的字符串 * @return 是否未知 */ public static boolean isUnknown(String checkString) { return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); } /** * 是否为IP */ public static boolean isIP(String ip) { return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP); } /** * 是否为IP,或 *为间隔的通配符地址 */ public static boolean isIpWildCard(String ip) { return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD); } /** * 检测参数是否在ip通配符里 */ public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) { String[] s1 = ipWildCard.split("\\."); String[] s2 = ip.split("\\."); boolean isMatchedSeg = true; for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) { if (!s1[i].equals(s2[i])) { isMatchedSeg = false; break; } } return isMatchedSeg; } /** * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串 */ public static boolean isIPSegment(String ipSeg) { return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG); } /** * 判断ip是否在指定网段中 */ public static boolean ipIsInNetNoCheck(String iparea, String ip) { int idx = iparea.indexOf('-'); String[] sips = iparea.substring(0, idx).split("\\."); String[] sipe = iparea.substring(idx + 1).split("\\."); String[] sipt = ip.split("\\."); long ips = 0L, ipe = 0L, ipt = 0L; for (int i = 0; i < 4; ++i) { ips = ips << 8 | Integer.parseInt(sips[i]); ipe = ipe << 8 | Integer.parseInt(sipe[i]); ipt = ipt << 8 | Integer.parseInt(sipt[i]); } if (ips > ipe) { long t = ips; ips = ipe; ipe = t; } return ips <= ipt && ipt <= ipe; } /** * 校验ip是否符合过滤串规则 * * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99` * @param ip 校验IP地址 * @return boolean 结果 */ public static boolean isMatchedIp(String filter, String ip) { if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip)) { return false; } String[] ips = filter.split(";"); for (String iStr : ips) { if (isIP(iStr) && iStr.equals(ip)) { return true; } else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) { return true; } else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) { return true; } } return false; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/LogUtils.java ================================================ package com.ruoyi.common.utils; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.json.JSON; /** * 处理并记录日志文件 * * @author ruoyi */ public class LogUtils { public static final Logger ERROR_LOG = LoggerFactory.getLogger("sys-error"); public static final Logger ACCESS_LOG = LoggerFactory.getLogger("sys-access"); /** * 记录访问日志 [username][jsessionid][ip][accept][UserAgent][url][params][Referer] * * @param request * @throws Exception */ public static void logAccess(HttpServletRequest request) throws Exception { String username = getUsername(); String jsessionId = request.getRequestedSessionId(); String ip = IpUtils.getIpAddr(request); String accept = request.getHeader("accept"); String userAgent = request.getHeader("User-Agent"); String url = request.getRequestURI(); String params = getParams(request); StringBuilder s = new StringBuilder(); s.append(getBlock(username)); s.append(getBlock(jsessionId)); s.append(getBlock(ip)); s.append(getBlock(accept)); s.append(getBlock(userAgent)); s.append(getBlock(url)); s.append(getBlock(params)); s.append(getBlock(request.getHeader("Referer"))); getAccessLog().info(s.toString()); } /** * 记录异常错误 格式 [exception] * * @param message * @param e */ public static void logError(String message, Throwable e) { String username = getUsername(); StringBuilder s = new StringBuilder(); s.append(getBlock("exception")); s.append(getBlock(username)); s.append(getBlock(message)); ERROR_LOG.error(s.toString(), e); } /** * 记录页面错误 错误日志记录 [page/eception][username][statusCode][errorMessage][servletName][uri][exceptionName][ip][exception] * * @param request */ public static void logPageError(HttpServletRequest request) { String username = getUsername(); Integer statusCode = (Integer) request.getAttribute("jakarta.servlet.error.status_code"); String message = (String) request.getAttribute("jakarta.servlet.error.message"); String uri = (String) request.getAttribute("jakarta.servlet.error.request_uri"); Throwable t = (Throwable) request.getAttribute("jakarta.servlet.error.exception"); if (statusCode == null) { statusCode = 0; } StringBuilder s = new StringBuilder(); s.append(getBlock(t == null ? "page" : "exception")); s.append(getBlock(username)); s.append(getBlock(statusCode)); s.append(getBlock(message)); s.append(getBlock(IpUtils.getIpAddr(request))); s.append(getBlock(uri)); s.append(getBlock(request.getHeader("Referer"))); StringWriter sw = new StringWriter(); while (t != null) { t.printStackTrace(new PrintWriter(sw)); t = t.getCause(); } s.append(getBlock(sw.toString())); getErrorLog().error(s.toString()); } public static String getBlock(Object msg) { if (msg == null) { msg = ""; } return "[" + msg.toString() + "]"; } protected static String getParams(HttpServletRequest request) throws Exception { Map params = request.getParameterMap(); return JSON.marshal(params); } protected static String getUsername() { return (String) SecurityUtils.getSubject().getPrincipal(); } public static Logger getAccessLog() { return ACCESS_LOG; } public static Logger getErrorLog() { return ERROR_LOG; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/MapDataUtil.java ================================================ package com.ruoyi.common.utils; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import jakarta.servlet.http.HttpServletRequest; /** * Map通用处理方法 * * @author ruoyi */ public class MapDataUtil { public static Map convertDataMap(HttpServletRequest request) { Map properties = request.getParameterMap(); Map returnMap = new HashMap(); Iterator entries = properties.entrySet().iterator(); Map.Entry entry; String name = ""; String value = ""; while (entries.hasNext()) { entry = (Entry) entries.next(); name = (String) entry.getKey(); Object valueObj = entry.getValue(); if (null == valueObj) { value = ""; } else if (valueObj instanceof String[]) { String[] values = (String[]) valueObj; value = ""; for (int i = 0; i < values.length; i++) { value += values[i] + ","; } if (value.length() > 0) { value = value.substring(0, value.length() - 1); } } else { value = valueObj.toString(); } returnMap.put(name, value); } return returnMap; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java ================================================ package com.ruoyi.common.utils; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import com.ruoyi.common.utils.spring.SpringUtils; /** * 获取i18n资源文件 * * @author ruoyi */ public class MessageUtils { /** * 根据消息键和参数 获取消息 委托给spring messageSource * * @param code 消息键 * @param args 参数 * @return 获取国际化翻译值 */ public static String message(String code, Object... args) { MessageSource messageSource = SpringUtils.getBean(MessageSource.class); return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/PageUtils.java ================================================ package com.ruoyi.common.utils; import com.github.pagehelper.PageHelper; import com.ruoyi.common.core.page.PageDomain; import com.ruoyi.common.core.page.TableSupport; import com.ruoyi.common.utils.sql.SqlUtil; /** * 分页工具类 * * @author ruoyi */ public class PageUtils extends PageHelper { /** * 设置请求分页数据 */ public static void startPage() { PageDomain pageDomain = TableSupport.buildPageRequest(); Integer pageNum = pageDomain.getPageNum(); Integer pageSize = pageDomain.getPageSize(); String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); Boolean reasonable = pageDomain.getReasonable(); PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); } /** * 清理分页的线程变量 */ public static void clearPage() { PageHelper.clearPage(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java ================================================ package com.ruoyi.common.utils; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.SecureRandom; import java.util.Base64; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.text.Convert; /** * 客户端工具类 * * @author ruoyi */ public class ServletUtils { /** * 定义移动端请求的所有可能类型 */ private final static String[] agent = { "Android", "iPhone", "iPod", "iPad", "Windows Phone", "MQQBrowser" }; private static final SecureRandom secureRandom = new SecureRandom(); /** * 获取String参数 */ public static String getParameter(String name) { return getRequest().getParameter(name); } /** * 获取String参数 */ public static String getParameter(String name, String defaultValue) { return Convert.toStr(getRequest().getParameter(name), defaultValue); } /** * 获取Integer参数 */ public static Integer getParameterToInt(String name) { return Convert.toInt(getRequest().getParameter(name)); } /** * 获取Integer参数 */ public static Integer getParameterToInt(String name, Integer defaultValue) { return Convert.toInt(getRequest().getParameter(name), defaultValue); } /** * 获取Boolean参数 */ public static Boolean getParameterToBool(String name) { return Convert.toBool(getRequest().getParameter(name)); } /** * 获取Boolean参数 */ public static Boolean getParameterToBool(String name, Boolean defaultValue) { return Convert.toBool(getRequest().getParameter(name), defaultValue); } /** * 获取request */ public static HttpServletRequest getRequest() { return getRequestAttributes().getRequest(); } /** * 获取response */ public static HttpServletResponse getResponse() { return getRequestAttributes().getResponse(); } /** * 获取session */ public static HttpSession getSession() { return getRequest().getSession(); } public static ServletRequestAttributes getRequestAttributes() { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); return (ServletRequestAttributes) attributes; } /** * 将字符串渲染到客户端 * * @param response 渲染对象 * @param string 待渲染的字符串 * @return null */ public static String renderString(HttpServletResponse response, String string) { try { response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(string); } catch (IOException e) { e.printStackTrace(); } return null; } /** * 是否是Ajax异步请求 * * @param request */ public static boolean isAjaxRequest(HttpServletRequest request) { String accept = request.getHeader("accept"); if (accept != null && accept.contains("application/json")) { return true; } String xRequestedWith = request.getHeader("X-Requested-With"); if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) { return true; } String uri = request.getRequestURI(); if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) { return true; } String ajax = request.getParameter("__ajax"); return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); } /** * 判断User-Agent 是不是来自于手机 */ public static boolean checkAgentIsMobile(String ua) { boolean flag = false; if (!ua.contains("Windows NT") || (ua.contains("Windows NT") && ua.contains("compatible; MSIE 9.0;"))) { // 排除 苹果桌面系统 if (!ua.contains("Windows NT") && !ua.contains("Macintosh")) { for (String item : agent) { if (ua.contains(item)) { flag = true; break; } } } } return flag; } /** * 内容编码 * * @param str 内容 * @return 编码后的内容 */ public static String urlEncode(String str) { try { return URLEncoder.encode(str, Constants.UTF8); } catch (UnsupportedEncodingException e) { return StringUtils.EMPTY; } } /** * 内容解码 * * @param str 内容 * @return 解码后的内容 */ public static String urlDecode(String str) { try { return URLDecoder.decode(str, Constants.UTF8); } catch (UnsupportedEncodingException e) { return StringUtils.EMPTY; } } /** * 生成CSRF Token * * @return 解码后的内容 */ public static String generateToken() { byte[] bytes = new byte[32]; secureRandom.nextBytes(bytes); return Base64.getEncoder().encodeToString(bytes); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/ShiroUtils.java ================================================ package com.ruoyi.common.utils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.crypto.SecureRandomNumberGenerator; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.utils.bean.BeanUtils; /** * shiro 工具类 * * @author ruoyi */ public class ShiroUtils { public static Subject getSubject() { return SecurityUtils.getSubject(); } public static Session getSession() { return SecurityUtils.getSubject().getSession(); } public static void logout() { getSubject().logout(); } public static SysUser getSysUser() { SysUser user = null; Object obj = getSubject().getPrincipal(); if (StringUtils.isNotNull(obj)) { user = new SysUser(); BeanUtils.copyBeanProp(user, obj); } return user; } public static void setSysUser(SysUser user) { Subject subject = getSubject(); PrincipalCollection principalCollection = subject.getPrincipals(); String realmName = principalCollection.getRealmNames().iterator().next(); PrincipalCollection newPrincipalCollection = new SimplePrincipalCollection(user, realmName); // 重新加载Principal subject.runAs(newPrincipalCollection); } public static Long getUserId() { return getSysUser().getUserId().longValue(); } public static String getLoginName() { return getSysUser().getLoginName(); } public static String getIp() { return StringUtils.substring(getSubject().getSession().getHost(), 0, 128); } public static String getSessionId() { return String.valueOf(getSubject().getSession().getId()); } /** * 是否为管理员 * * @return 结果 */ public static boolean isAdmin() { return isAdmin(getUserId()); } /** * 是否为管理员 * * @param userId 用户ID * @return 结果 */ public static boolean isAdmin(Long userId) { return userId != null && 1L == userId; } /** * 生成随机盐 */ public static String randomSalt() { // 一个Byte占两个字节,此处生成的3字节,字符串长度为6 SecureRandomNumberGenerator secureRandom = new SecureRandomNumberGenerator(); String hex = secureRandom.nextBytes(3).toHex(); return hex; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java ================================================ package com.ruoyi.common.utils; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.Strings; import org.springframework.util.AntPathMatcher; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.text.StrFormatter; /** * 字符串工具类 * * @author ruoyi */ @SuppressWarnings("deprecation") public class StringUtils extends org.apache.commons.lang3.StringUtils { /** 空字符串 */ private static final String NULLSTR = ""; /** 下划线 */ private static final char SEPARATOR = '_'; /** 星号 */ private static final char ASTERISK = '*'; /** * 获取参数不为空值 * * @param value defaultValue 要判断的value * @return value 返回值 */ public static T nvl(T value, T defaultValue) { return value != null ? value : defaultValue; } /** * * 判断一个Collection是否为空, 包含List,Set,Queue * * @param coll 要判断的Collection * @return true:为空 false:非空 */ public static boolean isEmpty(Collection coll) { return isNull(coll) || coll.isEmpty(); } /** * * 判断一个Collection是否非空,包含List,Set,Queue * * @param coll 要判断的Collection * @return true:非空 false:空 */ public static boolean isNotEmpty(Collection coll) { return !isEmpty(coll); } /** * * 判断一个对象数组是否为空 * * @param objects 要判断的对象数组 ** @return true:为空 false:非空 */ public static boolean isEmpty(Object[] objects) { return isNull(objects) || (objects.length == 0); } /** * * 判断一个对象数组是否非空 * * @param objects 要判断的对象数组 * @return true:非空 false:空 */ public static boolean isNotEmpty(Object[] objects) { return !isEmpty(objects); } /** * * 判断一个Map是否为空 * * @param map 要判断的Map * @return true:为空 false:非空 */ public static boolean isEmpty(Map map) { return isNull(map) || map.isEmpty(); } /** * * 判断一个Map是否为空 * * @param map 要判断的Map * @return true:非空 false:空 */ public static boolean isNotEmpty(Map map) { return !isEmpty(map); } /** * * 判断一个字符串是否为空串 * * @param str String * @return true:为空 false:非空 */ public static boolean isEmpty(String str) { return isNull(str) || NULLSTR.equals(str.trim()); } /** * * 判断一个字符串是否为非空串 * * @param str String * @return true:非空串 false:空串 */ public static boolean isNotEmpty(String str) { return !isEmpty(str); } /** * * 判断一个对象是否为空 * * @param object Object * @return true:为空 false:非空 */ public static boolean isNull(Object object) { return object == null; } /** * * 判断一个对象是否非空 * * @param object Object * @return true:非空 false:空 */ public static boolean isNotNull(Object object) { return !isNull(object); } /** * * 判断一个对象是否是数组类型(Java基本型别的数组) * * @param object 对象 * @return true:是数组 false:不是数组 */ public static boolean isArray(Object object) { return isNotNull(object) && object.getClass().isArray(); } /** * 去空格 */ public static String trim(String str) { return (str == null ? "" : str.trim()); } /** * 替换指定字符串的指定区间内字符为"*" * * @param str 字符串 * @param startInclude 开始位置(包含) * @param endExclude 结束位置(不包含) * @return 替换后的字符串 */ public static String hide(CharSequence str, int startInclude, int endExclude) { if (isEmpty(str)) { return NULLSTR; } final int strLength = str.length(); if (startInclude > strLength) { return NULLSTR; } if (endExclude > strLength) { endExclude = strLength; } if (startInclude > endExclude) { // 如果起始位置大于结束位置,不替换 return NULLSTR; } final char[] chars = new char[strLength]; for (int i = 0; i < strLength; i++) { if (i >= startInclude && i < endExclude) { chars[i] = ASTERISK; } else { chars[i] = str.charAt(i); } } return new String(chars); } /** * 截取字符串 * * @param str 字符串 * @param start 开始 * @return 结果 */ public static String substring(final String str, int start) { if (str == null) { return NULLSTR; } if (start < 0) { start = str.length() + start; } if (start < 0) { start = 0; } if (start > str.length()) { return NULLSTR; } return str.substring(start); } /** * 截取字符串 * * @param str 字符串 * @param start 开始 * @param end 结束 * @return 结果 */ public static String substring(final String str, int start, int end) { if (str == null) { return NULLSTR; } if (end < 0) { end = str.length() + end; } if (start < 0) { start = str.length() + start; } if (end > str.length()) { end = str.length(); } if (start > end) { return NULLSTR; } if (start < 0) { start = 0; } if (end < 0) { end = 0; } return str.substring(start, end); } /** * 在字符串中查找第一个出现的 `open` 和最后一个出现的 `close` 之间的子字符串 * * @param str 要截取的字符串 * @param open 起始字符串 * @param close 结束字符串 * @return 截取结果 */ public static String substringBetweenLast(final String str, final String open, final String close) { if (isEmpty(str) || isEmpty(open) || isEmpty(close)) { return NULLSTR; } final int start = str.indexOf(open); if (start != INDEX_NOT_FOUND) { final int end = str.lastIndexOf(close); if (end != INDEX_NOT_FOUND) { return str.substring(start + open.length(), end); } } return NULLSTR; } /** * 格式化文本, {} 表示占位符
                    * 此方法只是简单将占位符 {} 按照顺序替换为参数
                    * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
                    * 例:
                    * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
                    * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
                    * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
                    * * @param template 文本模板,被替换的部分用 {} 表示 * @param params 参数值 * @return 格式化后的文本 */ public static String format(String template, Object... params) { if (isEmpty(params) || isEmpty(template)) { return template; } return StrFormatter.format(template, params); } /** * 是否为http(s)://开头 * * @param link 链接 * @return 结果 */ public static boolean ishttp(String link) { return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); } /** * 字符串转set * * @param str 字符串 * @param sep 分隔符 * @return set集合 */ public static final Set str2Set(String str, String sep) { return new HashSet(str2List(str, sep, true, false)); } /** * 字符串转list * * @param str 字符串 * @param sep 分隔符 * @return list集合 */ public static final List str2List(String str, String sep) { return str2List(str, sep, true, false); } /** * 字符串转list * * @param str 字符串 * @param sep 分隔符 * @param filterBlank 过滤纯空白 * @param trim 去掉首尾空白 * @return list集合 */ public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) { List list = new ArrayList(); if (StringUtils.isEmpty(str)) { return list; } // 过滤空白字符串 if (filterBlank && StringUtils.isBlank(str)) { return list; } String[] split = str.split(sep); for (String string : split) { if (filterBlank && StringUtils.isBlank(string)) { continue; } if (trim) { string = string.trim(); } list.add(string); } return list; } /** * 检查子字符串是否存在 * * @param seq 检查的字符串 * @param searchSeq 查找的字符串 * @return 结果 */ public static boolean contains(final CharSequence seq, final CharSequence searchSeq) { return Strings.CS.contains(seq, searchSeq); } /** * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value * * @param collection 给定的集合 * @param array 给定的数组 * @return boolean 结果 */ public static boolean containsAny(Collection collection, String... array) { if (isEmpty(collection) || isEmpty(array)) { return false; } else { for (String str : array) { if (collection.contains(str)) { return true; } } return false; } } /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 * * @param cs 指定字符串 * @param searchCharSequences 需要检查的字符串数组 * @return 是否包含任意一个字符串 */ public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { if (isEmpty(cs) || isEmpty(searchCharSequences)) { return false; } for (CharSequence testStr : searchCharSequences) { if (containsIgnoreCase(cs, testStr)) { return true; } } return false; } /** * 检查是否包含要搜索的字符串,忽略大小写 * * @param str 要检查的字符串 * @param searchStr 要查找的字符串 * @return 如果包含要搜索的字符串(忽略大小写)则返回true,如果不包含或返回false */ public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) { return Strings.CI.contains(str, searchStr); } /** * 检查字符串是否以任意前缀开始 * * @param sequence 要检查的字符串 * @param searchStrings 区分大小写的字符串前缀数组 * @return 结果 */ public static boolean startsWithAny(final CharSequence sequence, final CharSequence... searchStrings) { return Strings.CS.startsWithAny(sequence, searchStrings); } /** * 比较两个字符串是否相同 * * @param cs1 第一个字符串 * @param cs2 第二个字符串 * @return 如果给定对象与字符串相等,则返回 true;否则返回 false */ public static boolean equals(final CharSequence cs1, final CharSequence cs2) { return Strings.CS.equals(cs1, cs2); } /** * 替换字符串中所有匹配的字符 * * @param text 要搜索和替换的文本 * @param searchString 要搜索的字符串 * @param replacement 用于替换的字符串 * @return 处理完所有替换后的文本 */ public static String replace(final String text, final String searchString, final String replacement) { return Strings.CS.replace(text, searchString, replacement); } /** * 查找字符串首次出现位置的索引 * * @param seq 要检查的字符串 * @param searchSeq 要查找的字符串 * @return 返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1 */ public static int indexOf(final CharSequence seq, final CharSequence searchSeq) { return Strings.CS.indexOf(seq, searchSeq); } /** * 检查字符串是否以指定的后缀结尾 * * @param str 要检查的字符 * @param suffix 要检查的后缀 * @return 若参数与该字符串末尾相符 true;否则 false */ public static boolean endsWith(final CharSequence str, final CharSequence suffix) { return Strings.CS.endsWith(str, suffix); } /** * 将给定的字符串与数组进行比较 * * @param string 要比较的字符串 * @param searchStrings 字符串数组 * @return 如果字符串等于(区分大小写){@code searchStrings}中的任意其他元素,则返回true;如果{@code searchStrings}为null或不包含匹配项,则返回false */ public static boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) { return Strings.CS.equalsAny(string, searchStrings); } /** * 检查一个字符串是否以任意提供的区分大小写的后缀结尾。 * * @param sequence 要检查的字符串 * @param searchStrings 要查找的区分大小写的字符串数组 * @return 如果输入参数{@code sequence}为null且未提供任何{@code searchStrings},或者输入{@code sequence}以任意提供的区分大小写的{@code searchStrings}结尾,则返回{@code true}。 */ public static boolean endsWithAny(final CharSequence sequence, final CharSequence... searchStrings) { return Strings.CS.endsWithAny(sequence, searchStrings); } /** * 不区分大小写地检查字符序列是否以指定的后缀结尾 * * @param str 要检查的字符序列 * @param suffix 要查找的后缀 * @return 如果字符序列以该后缀结尾(不区分大小写),或两者均为{@code null},则返回{@code true} */ public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix) { return Strings.CI.endsWith(str, suffix); } /** * 指定范围内查找字符串,忽略大小写 * * @param str 要检查的字符串 * @param searchStr 要查找的字符串 * @return 搜索字符串的第一个索引,如果未找到匹配项则返回 -1 */ public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) { return Strings.CI.indexOf(str, searchStr); } /** * Compares given {@code string} to a CharSequences vararg of {@code searchStrings}, * returning {@code true} if the {@code string} is equal to any of the {@code searchStrings}, ignoring case. * * @param string to compare, may be {@code null}. * @param searchStrings a vararg of strings, may be {@code null}. * @return {@code true} if the string is equal (case-insensitive) to any other element of {@code searchStrings}; */ public static boolean equalsAnyIgnoreCase(final CharSequence string, final CharSequence... searchStrings) { return Strings.CI.equalsAny(string, searchStrings); } /** * 驼峰转下划线命名 */ public static String toUnderScoreCase(String str) { if (str == null) { return null; } StringBuilder sb = new StringBuilder(); // 前置字符是否大写 boolean preCharIsUpperCase = true; // 当前字符是否大写 boolean curreCharIsUpperCase = true; // 下一字符是否大写 boolean nexteCharIsUpperCase = true; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (i > 0) { preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); } else { preCharIsUpperCase = false; } curreCharIsUpperCase = Character.isUpperCase(c); if (i < (str.length() - 1)) { nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); } if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { sb.append(SEPARATOR); } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { sb.append(SEPARATOR); } sb.append(Character.toLowerCase(c)); } return sb.toString(); } /** * 是否包含字符串 * * @param str 验证字符串 * @param strs 字符串组 * @return 包含返回true */ public static boolean inStringIgnoreCase(String str, String... strs) { if (str != null && strs != null) { for (String s : strs) { if (str.equalsIgnoreCase(trim(s))) { return true; } } } return false; } /** * 删除最后一个字符串 * * @param str 输入字符串 * @param spit 以什么类型结尾的 * @return 截取后的字符串 */ public static String lastStringDel(String str, String spit) { if (!StringUtils.isEmpty(str) && str.endsWith(spit)) { return str.subSequence(0, str.length() - 1).toString(); } return str; } /** * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld * * @param name 转换前的下划线大写方式命名的字符串 * @return 转换后的驼峰式命名的字符串 */ public static String convertToCamelCase(String name) { StringBuilder result = new StringBuilder(); // 快速检查 if (name == null || name.isEmpty()) { // 没必要转换 return ""; } else if (!name.contains("_")) { // 不含下划线,仅将首字母大写 return name.substring(0, 1).toUpperCase() + name.substring(1); } // 用下划线将原始字符串分割 String[] camels = name.split("_"); for (String camel : camels) { // 跳过原始字符串中开头、结尾的下换线或双重下划线 if (camel.isEmpty()) { continue; } // 首字母大写 result.append(camel.substring(0, 1).toUpperCase()); result.append(camel.substring(1).toLowerCase()); } return result.toString(); } /** * 驼峰式命名法 * 例如:user_name->userName */ public static String toCamelCase(String s) { if (s == null) { return null; } if (s.indexOf(SEPARATOR) == -1) { return s; } s = s.toLowerCase(); StringBuilder sb = new StringBuilder(s.length()); boolean upperCase = false; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == SEPARATOR) { upperCase = true; } else if (upperCase) { sb.append(Character.toUpperCase(c)); upperCase = false; } else { sb.append(c); } } return sb.toString(); } /** * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 * * @param str 指定字符串 * @param strs 需要检查的字符串数组 * @return 是否匹配 */ public static boolean matches(String str, List strs) { if (isEmpty(str) || isEmpty(strs)) { return false; } for (String pattern : strs) { if (isMatch(pattern, str)) { return true; } } return false; } /** * 判断url是否与规则配置: * ? 表示单个字符; * * 表示一层路径内的任意字符串,不可跨层级; * ** 表示任意层路径; * * @param pattern 匹配规则 * @param url 需要匹配的url * @return */ public static boolean isMatch(String pattern, String url) { AntPathMatcher matcher = new AntPathMatcher(); return matcher.match(pattern, url); } @SuppressWarnings("unchecked") public static T cast(Object obj) { return (T) obj; } /** * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 * * @param num 数字对象 * @param size 字符串指定长度 * @return 返回数字的字符串格式,该字符串为指定长度。 */ public static final String padl(final Number num, final int size) { return padl(num.toString(), size, '0'); } /** * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 * * @param s 原始字符串 * @param size 字符串指定长度 * @param c 用于补齐的字符 * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 */ public static final String padl(final String s, final int size, final char c) { final StringBuilder sb = new StringBuilder(size); if (s != null) { final int len = s.length(); if (s.length() <= size) { for (int i = size - len; i > 0; i--) { sb.append(c); } sb.append(s); } else { return s.substring(len - size, len); } } else { for (int i = size; i > 0; i--) { sb.append(c); } } return sb.toString(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/Threads.java ================================================ package com.ruoyi.common.utils; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 线程相关工具类. * * @author ruoyi */ public class Threads { private static final Logger logger = LoggerFactory.getLogger(Threads.class); /** * sleep等待,单位为毫秒 */ public static void sleep(long milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException e) { return; } } /** * 停止线程池 * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. * 如果仍人超時,則強制退出. * 另对在shutdown时线程本身被调用中断做了处理. */ public static void shutdownAndAwaitTermination(ExecutorService pool) { if (pool != null && !pool.isShutdown()) { pool.shutdown(); try { if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { pool.shutdownNow(); if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { logger.info("Pool did not terminate"); } } } catch (InterruptedException ie) { pool.shutdownNow(); Thread.currentThread().interrupt(); } } } /** * 打印线程异常信息 */ public static void printException(Runnable r, Throwable t) { if (t == null && r instanceof Future) { try { Future future = (Future) r; if (future.isDone()) { future.get(); } } catch (CancellationException ce) { t = ce; } catch (ExecutionException ee) { t = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } if (t != null) { logger.error(t.getMessage(), t); } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java ================================================ package com.ruoyi.common.utils.bean; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Bean 工具类 * * @author ruoyi */ public class BeanUtils extends org.springframework.beans.BeanUtils { /** Bean方法名中属性名开始的下标 */ private static final int BEAN_METHOD_PROP_INDEX = 3; /** * 匹配getter方法的正则表达式 */ private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); /** * 匹配setter方法的正则表达式 */ private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); /** * Bean属性复制工具方法。 * * @param dest 目标对象 * @param src 源对象 */ public static void copyBeanProp(Object dest, Object src) { try { copyProperties(src, dest); } catch (Exception e) { e.printStackTrace(); } } /** * 获取对象的setter方法。 * * @param obj 对象 * @return 对象的setter方法列表 */ public static List getSetterMethods(Object obj) { // setter方法列表 List setterMethods = new ArrayList(); // 获取所有方法 Method[] methods = obj.getClass().getMethods(); // 查找setter方法 for (Method method : methods) { Matcher m = SET_PATTERN.matcher(method.getName()); if (m.matches() && (method.getParameterTypes().length == 1)) { setterMethods.add(method); } } // 返回setter方法列表 return setterMethods; } /** * 获取对象的getter方法。 * * @param obj 对象 * @return 对象的getter方法列表 */ public static List getGetterMethods(Object obj) { // getter方法列表 List getterMethods = new ArrayList(); // 获取所有方法 Method[] methods = obj.getClass().getMethods(); // 查找getter方法 for (Method method : methods) { Matcher m = GET_PATTERN.matcher(method.getName()); if (m.matches() && (method.getParameterTypes().length == 0)) { getterMethods.add(method); } } // 返回getter方法列表 return getterMethods; } /** * 检查Bean方法名中的属性名是否相等。
                    * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 * * @param m1 方法名1 * @param m2 方法名2 * @return 属性名一样返回true,否则返回false */ public static boolean isMethodPropEquals(String m1, String m2) { return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java ================================================ package com.ruoyi.common.utils.bean; import java.util.Set; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; /** * bean对象属性验证 * * @author ruoyi */ public class BeanValidators { public static void validateWithException(Validator validator, Object object, Class... groups) throws ConstraintViolationException { Set> constraintViolations = validator.validate(object, groups); if (!constraintViolations.isEmpty()) { throw new ConstraintViolationException(constraintViolations); } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java ================================================ package com.ruoyi.common.utils.file; import java.io.File; import org.apache.commons.lang3.StringUtils; /** * 文件类型工具类 * * @author ruoyi */ public class FileTypeUtils { /** * 获取文件类型 *

                    * 例如: ruoyi.txt, 返回: txt * * @param file 文件名 * @return 后缀(不含".") */ public static String getFileType(File file) { if (null == file) { return StringUtils.EMPTY; } return getFileType(file.getName()); } /** * 获取文件类型 *

                    * 例如: ruoyi.txt, 返回: txt * * @param fileName 文件名 * @return 后缀(不含".") */ public static String getFileType(String fileName) { int separatorIndex = fileName.lastIndexOf("."); if (separatorIndex < 0) { return ""; } return fileName.substring(separatorIndex + 1).toLowerCase(); } /** * 获取文件类型 * * @param photoByte 文件字节码 * @return 后缀(不含".") */ public static String getFileExtendName(byte[] photoByte) { String strFileExtendName = "JPG"; if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) { strFileExtendName = "GIF"; } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) { strFileExtendName = "JPG"; } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) { strFileExtendName = "BMP"; } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) { strFileExtendName = "PNG"; } return strFileExtendName; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java ================================================ package com.ruoyi.common.utils.file; import java.io.File; import java.io.IOException; import java.nio.file.Paths; import java.util.Objects; import org.apache.commons.io.FilenameUtils; import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException; import com.ruoyi.common.exception.file.FileSizeLimitExceededException; import com.ruoyi.common.exception.file.InvalidExtensionException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.common.utils.uuid.Seq; /** * 文件上传工具类 * * @author ruoyi */ public class FileUploadUtils { /** * 默认大小 50M */ public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L; /** * 默认的文件名最大长度 100 */ public static final int DEFAULT_FILE_NAME_LENGTH = 100; /** * 默认上传的地址 */ private static String defaultBaseDir = RuoYiConfig.getProfile(); public static void setDefaultBaseDir(String defaultBaseDir) { FileUploadUtils.defaultBaseDir = defaultBaseDir; } public static String getDefaultBaseDir() { return defaultBaseDir; } /** * 以默认配置进行文件上传 * * @param file 上传的文件 * @return 文件名称 * @throws Exception */ public static final String upload(MultipartFile file) throws IOException { try { return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); } catch (Exception e) { throw new IOException(e.getMessage(), e); } } /** * 根据文件路径上传 * * @param baseDir 相对应用的基目录 * @param file 上传的文件 * @return 文件名称 * @throws IOException */ public static final String upload(String baseDir, MultipartFile file) throws IOException { try { return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); } catch (Exception e) { throw new IOException(e.getMessage(), e); } } /** * 文件上传 * * @param baseDir 相对应用的基目录 * @param file 上传的文件 * @param allowedExtension 上传文件类型 * @return 返回上传成功的文件名 * @throws FileSizeLimitExceededException 如果超出最大大小 * @throws FileNameLengthLimitExceededException 文件名太长 * @throws IOException 比如读写文件出错时 * @throws InvalidExtensionException 文件校验异常 */ public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException { return upload(baseDir, file, allowedExtension, false); } /** * 文件上传 * * @param baseDir 相对应用的基目录 * @param file 上传的文件 * @param useCustomNaming 系统自定义文件名 * @param allowedExtension 上传文件类型 * @return 返回上传成功的文件名 * @throws FileSizeLimitExceededException 如果超出最大大小 * @throws FileNameLengthLimitExceededException 文件名太长 * @throws IOException 比如读写文件出错时 * @throws InvalidExtensionException 文件校验异常 */ public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension, boolean useCustomNaming) throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException { int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); } assertAllowed(file, allowedExtension); String fileName = useCustomNaming ? uuidFilename(file) : extractFilename(file); String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); file.transferTo(Paths.get(absPath)); return getPathFileName(baseDir, fileName); } /** * 编码文件名(日期格式目录 + 原文件名 + 序列值 + 后缀) */ public static final String extractFilename(MultipartFile file) { return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); } /** * 编编码文件名(日期格式目录 + UUID + 后缀) */ public static final String uuidFilename(MultipartFile file) { return StringUtils.format("{}/{}.{}", DateUtils.datePath(), IdUtils.fastSimpleUUID(), getExtension(file)); } public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { File desc = new File(uploadDir + File.separator + fileName); if (!desc.exists()) { if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } } return desc; } public static final String getPathFileName(String uploadDir, String fileName) throws IOException { int dirLastIndex = RuoYiConfig.getProfile().length() + 1; String currentDir = StringUtils.substring(uploadDir, dirLastIndex); return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; } /** * 文件大小校验 * * @param file 上传的文件 * @return * @throws FileSizeLimitExceededException 如果超出最大大小 * @throws InvalidExtensionException */ public static final void assertAllowed(MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, InvalidExtensionException { long size = file.getSize(); if (size > DEFAULT_MAX_SIZE) { throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); } String fileName = file.getOriginalFilename(); String extension = getExtension(file); if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) { throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, fileName); } else { throw new InvalidExtensionException(allowedExtension, extension, fileName); } } } /** * 判断MIME类型是否是允许的MIME类型 * * @param extension * @param allowedExtension * @return */ public static final boolean isAllowedExtension(String extension, String[] allowedExtension) { for (String str : allowedExtension) { if (str.equalsIgnoreCase(extension)) { return true; } } return false; } /** * 获取文件名的后缀 * * @param file 表单文件 * @return 后缀名 */ public static final String getExtension(MultipartFile file) { String extension = FilenameUtils.getExtension(file.getOriginalFilename()); if (StringUtils.isEmpty(extension)) { extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); } return extension; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java ================================================ package com.ruoyi.common.utils.file; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.uuid.IdUtils; /** * 文件处理工具类 * * @author ruoyi */ public class FileUtils { public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; /** * 输出指定文件的byte数组 * * @param filePath 文件路径 * @param os 输出流 * @return */ public static void writeBytes(String filePath, OutputStream os) throws IOException { FileInputStream fis = null; try { File file = new File(filePath); if (!file.exists()) { throw new FileNotFoundException(filePath); } fis = new FileInputStream(file); byte[] b = new byte[1024]; int length; while ((length = fis.read(b)) > 0) { os.write(b, 0, length); } } catch (IOException e) { throw e; } finally { IOUtils.close(os); IOUtils.close(fis); } } /** * 写数据到文件中 * * @param data 数据 * @return 目标文件 * @throws IOException IO异常 */ public static String writeImportBytes(byte[] data) throws IOException { return writeBytes(data, RuoYiConfig.getImportPath()); } /** * 写数据到文件中 * * @param data 数据 * @param uploadDir 目标文件 * @return 目标文件 * @throws IOException IO异常 */ public static String writeBytes(byte[] data, String uploadDir) throws IOException { FileOutputStream fos = null; String pathName = ""; try { String extension = getFileExtendName(data); pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); fos = new FileOutputStream(file); fos.write(data); } finally { IOUtils.close(fos); } return FileUploadUtils.getPathFileName(uploadDir, pathName); } /** * 移除路径中的请求前缀片段 * * @param filePath 文件路径 * @return 移除后的文件路径 */ public static String stripPrefix(String filePath) { return StringUtils.substringAfter(filePath, Constants.RESOURCE_PREFIX); } /** * 删除文件 * * @param filePath 文件 * @return */ public static boolean deleteFile(String filePath) { boolean flag = false; File file = new File(filePath); // 路径为文件且不为空则进行删除 if (file.isFile() && file.exists()) { flag = file.delete(); } return flag; } /** * 文件名称验证 * * @param filename 文件名称 * @return true 正常 false 非法 */ public static boolean isValidFilename(String filename) { return filename.matches(FILENAME_PATTERN); } /** * 检查文件是否可下载 * * @param resource 需要下载的文件 * @return true 正常 false 非法 */ public static boolean checkAllowDownload(String resource) { // 禁止目录上跳级别 if (StringUtils.contains(resource, "..")) { return false; } // 检查允许下载的文件规则 if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) { return true; } // 不在允许下载的文件规则 return false; } /** * 下载文件名重新编码 * * @param request 请求对象 * @param fileName 文件名 * @return 编码后的文件名 */ public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { final String agent = request.getHeader("USER-AGENT"); String filename = fileName; if (agent.contains("MSIE")) { // IE浏览器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐浏览器 filename = new String(fileName.getBytes(), "ISO8859-1"); } else if (agent.contains("Chrome")) { // google浏览器 filename = URLEncoder.encode(filename, "utf-8"); } else { // 其它浏览器 filename = URLEncoder.encode(filename, "utf-8"); } return filename; } /** * 下载文件名重新编码 * * @param response 响应对象 * @param realFileName 真实文件名 * @return */ public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException { String percentEncodedFileName = percentEncode(realFileName); StringBuilder contentDispositionValue = new StringBuilder(); contentDispositionValue.append("attachment; filename=") .append(percentEncodedFileName) .append(";") .append("filename*=") .append("utf-8''") .append(percentEncodedFileName); response.setHeader("Content-disposition", contentDispositionValue.toString()); } /** * 百分号编码工具方法 * * @param s 需要百分号编码的字符串 * @return 百分号编码后的字符串 */ public static String percentEncode(String s) throws UnsupportedEncodingException { String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); return encode.replaceAll("\\+", "%20"); } /** * 获取图像后缀 * * @param photoByte 图像数据 * @return 后缀名 */ public static String getFileExtendName(byte[] photoByte) { String strFileExtendName = "jpg"; if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) { strFileExtendName = "gif"; } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) { strFileExtendName = "jpg"; } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) { strFileExtendName = "bmp"; } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) { strFileExtendName = "png"; } return strFileExtendName; } /** * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png * * @param fileName 路径名称 * @return 没有文件路径的名称 */ public static String getName(String fileName) { if (fileName == null) { return null; } int lastUnixPos = fileName.lastIndexOf('/'); int lastWindowsPos = fileName.lastIndexOf('\\'); int index = Math.max(lastUnixPos, lastWindowsPos); return fileName.substring(index + 1); } /** * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi * * @param fileName 路径名称 * @return 没有文件路径和后缀的名称 */ public static String getNameNotSuffix(String fileName) { if (fileName == null) { return null; } String baseName = FilenameUtils.getBaseName(fileName); return baseName; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java ================================================ package com.ruoyi.common.utils.file; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.Arrays; import org.apache.poi.util.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.StringUtils; /** * 图片处理工具类 * * @author ruoyi */ public class ImageUtils { private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); public static byte[] getImage(String imagePath) { InputStream is = getFile(imagePath); try { return IOUtils.toByteArray(is); } catch (Exception e) { log.error("图片加载异常 {}", e); return null; } finally { IOUtils.closeQuietly(is); } } public static InputStream getFile(String imagePath) { try { byte[] result = readFile(imagePath); result = Arrays.copyOf(result, result.length); return new ByteArrayInputStream(result); } catch (Exception e) { log.error("获取图片异常 {}", e); } return null; } /** * 读取文件为字节数据 * * @param url 地址 * @return 字节数据 */ public static byte[] readFile(String url) { InputStream in = null; try { if (url.startsWith("http")) { // 网络地址 URL urlObj = new URL(url); URLConnection urlConnection = urlObj.openConnection(); urlConnection.setConnectTimeout(30 * 1000); urlConnection.setReadTimeout(60 * 1000); urlConnection.setDoInput(true); in = urlConnection.getInputStream(); } else { // 本机地址 String localPath = RuoYiConfig.getProfile(); String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); in = new FileInputStream(downloadPath); } return IOUtils.toByteArray(in); } catch (Exception e) { log.error("获取文件路径异常 {}", e); return null; } finally { IOUtils.closeQuietly(in); } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java ================================================ package com.ruoyi.common.utils.file; /** * 媒体类型工具类 * * @author ruoyi */ public class MimeTypeUtils { public static final String IMAGE_PNG = "image/png"; public static final String IMAGE_JPG = "image/jpg"; public static final String IMAGE_JPEG = "image/jpeg"; public static final String IMAGE_BMP = "image/bmp"; public static final String IMAGE_GIF = "image/gif"; public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; public static final String[] FLASH_EXTENSION = { "swf", "flv" }; public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", "asf", "rm", "rmvb" }; public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; public static final String[] DEFAULT_ALLOWED_EXTENSION = { // 图片 "bmp", "gif", "jpg", "jpeg", "png", // word excel powerpoint "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", // 压缩文件 "rar", "zip", "gz", "bz2", // 视频格式 "mp4", "avi", "rmvb", // pdf "pdf" }; public static String getExtension(String prefix) { switch (prefix) { case IMAGE_PNG: return "png"; case IMAGE_JPG: return "jpg"; case IMAGE_JPEG: return "jpeg"; case IMAGE_BMP: return "bmp"; case IMAGE_GIF: return "gif"; default: return ""; } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java ================================================ package com.ruoyi.common.utils.html; import com.ruoyi.common.utils.StringUtils; /** * 转义和反转义工具类 * * @author ruoyi */ public class EscapeUtil { public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; private static final char[][] TEXT = new char[64][]; static { for (int i = 0; i < 64; i++) { TEXT[i] = new char[] { (char) i }; } // special HTML characters TEXT['\''] = "'".toCharArray(); // 单引号 TEXT['"'] = """.toCharArray(); // 双引号 TEXT['&'] = "&".toCharArray(); // &符 TEXT['<'] = "<".toCharArray(); // 小于号 TEXT['>'] = ">".toCharArray(); // 大于号 } /** * 转义文本中的HTML字符为安全的字符 * * @param text 被转义的文本 * @return 转义后的文本 */ public static String escape(String text) { return encode(text); } /** * 还原被转义的HTML特殊字符 * * @param content 包含转义符的HTML内容 * @return 转换后的字符串 */ public static String unescape(String content) { return decode(content); } /** * 清除所有HTML标签,但是不删除标签内的内容 * * @param content 文本 * @return 清除标签后的文本 */ public static String clean(String content) { return new HTMLFilter().filter(content); } /** * Escape编码 * * @param text 被编码的文本 * @return 编码后的字符 */ private static String encode(String text) { if (StringUtils.isEmpty(text)) { return StringUtils.EMPTY; } final StringBuilder tmp = new StringBuilder(text.length() * 6); char c; for (int i = 0; i < text.length(); i++) { c = text.charAt(i); if (c < 256) { tmp.append("%"); if (c < 16) { tmp.append("0"); } tmp.append(Integer.toString(c, 16)); } else { tmp.append("%u"); if (c <= 0xfff) { // issue#I49JU8@Gitee tmp.append("0"); } tmp.append(Integer.toString(c, 16)); } } return tmp.toString(); } /** * Escape解码 * * @param content 被转义的内容 * @return 解码后的字符串 */ public static String decode(String content) { if (StringUtils.isEmpty(content)) { return content; } StringBuilder tmp = new StringBuilder(content.length()); int lastPos = 0, pos = 0; char ch; while (lastPos < content.length()) { pos = content.indexOf("%", lastPos); if (pos == lastPos) { if (content.charAt(pos + 1) == 'u') { ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); tmp.append(ch); lastPos = pos + 6; } else { ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); tmp.append(ch); lastPos = pos + 3; } } else { if (pos == -1) { tmp.append(content.substring(lastPos)); lastPos = content.length(); } else { tmp.append(content.substring(lastPos, pos)); lastPos = pos; } } } return tmp.toString(); } public static void main(String[] args) { String html = ""; String escape = EscapeUtil.escape(html); // String html = "ipt>alert(\"XSS\")ipt>"; // String html = "<123"; // String html = "123>"; System.out.println("clean: " + EscapeUtil.clean(html)); System.out.println("escape: " + escape); System.out.println("unescape: " + EscapeUtil.unescape(escape)); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java ================================================ package com.ruoyi.common.utils.html; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * HTML过滤器,用于去除XSS漏洞隐患。 * * @author ruoyi */ public final class HTMLFilter { /** * regex flag union representing /si modifiers in php **/ private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); private static final Pattern P_END_ARROW = Pattern.compile("^>"); private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); private static final Pattern P_AMP = Pattern.compile("&"); private static final Pattern P_QUOTE = Pattern.compile("\""); private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); // @xxx could grow large... maybe use sesat's ReferenceMap private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); /** * set of allowed html elements, along with allowed attributes for each element **/ private final Map> vAllowed; /** * counts of open tags for each (allowable) html element **/ private final Map vTagCounts = new HashMap<>(); /** * html elements which must always be self-closing (e.g. "") **/ private final String[] vSelfClosingTags; /** * html elements which must always have separate opening and closing tags (e.g. "") **/ private final String[] vNeedClosingTags; /** * set of disallowed html elements **/ private final String[] vDisallowed; /** * attributes which should be checked for valid protocols **/ private final String[] vProtocolAtts; /** * allowed protocols **/ private final String[] vAllowedProtocols; /** * tags which should be removed if they contain no content (e.g. "" or "") **/ private final String[] vRemoveBlanks; /** * entities allowed within html markup **/ private final String[] vAllowedEntities; /** * flag determining whether comments are allowed in input String. */ private final boolean stripComment; private final boolean encodeQuotes; /** * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. */ private final boolean alwaysMakeTags; /** * Default constructor. */ public HTMLFilter() { vAllowed = new HashMap<>(); final ArrayList a_atts = new ArrayList<>(); a_atts.add("href"); a_atts.add("target"); vAllowed.put("a", a_atts); final ArrayList img_atts = new ArrayList<>(); img_atts.add("src"); img_atts.add("width"); img_atts.add("height"); img_atts.add("alt"); vAllowed.put("img", img_atts); final ArrayList no_atts = new ArrayList<>(); vAllowed.put("b", no_atts); vAllowed.put("strong", no_atts); vAllowed.put("i", no_atts); vAllowed.put("em", no_atts); vSelfClosingTags = new String[] { "img" }; vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; vDisallowed = new String[] {}; vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp. vProtocolAtts = new String[] { "src", "href" }; vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; stripComment = true; encodeQuotes = true; alwaysMakeTags = false; } /** * Map-parameter configurable constructor. * * @param conf map containing configuration. keys match field names. */ @SuppressWarnings("unchecked") public HTMLFilter(final Map conf) { assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); vDisallowed = (String[]) conf.get("vDisallowed"); vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); vProtocolAtts = (String[]) conf.get("vProtocolAtts"); vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); vAllowedEntities = (String[]) conf.get("vAllowedEntities"); stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; } private void reset() { vTagCounts.clear(); } // --------------------------------------------------------------- // my versions of some PHP library functions public static String chr(final int decimal) { return String.valueOf((char) decimal); } public static String htmlSpecialChars(final String s) { String result = s; result = regexReplace(P_AMP, "&", result); result = regexReplace(P_QUOTE, """, result); result = regexReplace(P_LEFT_ARROW, "<", result); result = regexReplace(P_RIGHT_ARROW, ">", result); return result; } // --------------------------------------------------------------- /** * given a user submitted input String, filter out any invalid or restricted html. * * @param input text (i.e. submitted by a user) than may contain html * @return "clean" version of input, with only valid, whitelisted html elements allowed */ public String filter(final String input) { reset(); String s = input; s = escapeComments(s); s = balanceHTML(s); s = checkTags(s); s = processRemoveBlanks(s); // s = validateEntities(s); return s; } public boolean isAlwaysMakeTags() { return alwaysMakeTags; } public boolean isStripComments() { return stripComment; } private String escapeComments(final String s) { final Matcher m = P_COMMENTS.matcher(s); final StringBuffer buf = new StringBuffer(); if (m.find()) { final String match = m.group(1); // (.*?) m.appendReplacement(buf, Matcher.quoteReplacement("")); } m.appendTail(buf); return buf.toString(); } private String balanceHTML(String s) { if (alwaysMakeTags) { // // try and form html // s = regexReplace(P_END_ARROW, "", s); // 不追加结束标签 s = regexReplace(P_BODY_TO_END, "<$1>", s); s = regexReplace(P_XML_CONTENT, "$1<$2", s); } else { // // escape stray brackets // s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); // // the last regexp causes '<>' entities to appear // (we need to do a lookahead assertion so that the last bracket can // be used in the next pass of the regexp) // s = regexReplace(P_BOTH_ARROWS, "", s); } return s; } private String checkTags(String s) { Matcher m = P_TAGS.matcher(s); final StringBuffer buf = new StringBuffer(); while (m.find()) { String replaceStr = m.group(1); replaceStr = processTag(replaceStr); m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); } m.appendTail(buf); // these get tallied in processTag // (remember to reset before subsequent calls to filter method) final StringBuilder sBuilder = new StringBuilder(buf.toString()); for (String key : vTagCounts.keySet()) { for (int ii = 0; ii < vTagCounts.get(key); ii++) { sBuilder.append(""); } } s = sBuilder.toString(); return s; } private String processRemoveBlanks(final String s) { String result = s; for (String tag : vRemoveBlanks) { if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) { P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); } result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) { P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); } result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); } return result; } private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) { Matcher m = regex_pattern.matcher(s); return m.replaceAll(replacement); } private String processTag(final String s) { // ending tags Matcher m = P_END_TAG.matcher(s); if (m.find()) { final String name = m.group(1).toLowerCase(); if (allowed(name)) { if (false == inArray(name, vSelfClosingTags)) { if (vTagCounts.containsKey(name)) { vTagCounts.put(name, vTagCounts.get(name) - 1); return ""; } } } } // starting tags m = P_START_TAG.matcher(s); if (m.find()) { final String name = m.group(1).toLowerCase(); final String body = m.group(2); String ending = m.group(3); // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); if (allowed(name)) { final StringBuilder params = new StringBuilder(); final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); final List paramNames = new ArrayList<>(); final List paramValues = new ArrayList<>(); while (m2.find()) { paramNames.add(m2.group(1)); // ([a-z0-9]+) paramValues.add(m2.group(3)); // (.*?) } while (m3.find()) { paramNames.add(m3.group(1)); // ([a-z0-9]+) paramValues.add(m3.group(3)); // ([^\"\\s']+) } String paramName, paramValue; for (int ii = 0; ii < paramNames.size(); ii++) { paramName = paramNames.get(ii).toLowerCase(); paramValue = paramValues.get(ii); // debug( "paramName='" + paramName + "'" ); // debug( "paramValue='" + paramValue + "'" ); // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); if (allowedAttribute(name, paramName)) { if (inArray(paramName, vProtocolAtts)) { paramValue = processParamProtocol(paramValue); } params.append(' ').append(paramName).append("=\"").append(paramValue).append("\""); } } if (inArray(name, vSelfClosingTags)) { ending = " /"; } if (inArray(name, vNeedClosingTags)) { ending = ""; } if (ending == null || ending.length() < 1) { if (vTagCounts.containsKey(name)) { vTagCounts.put(name, vTagCounts.get(name) + 1); } else { vTagCounts.put(name, 1); } } else { ending = " /"; } return "<" + name + params + ending + ">"; } else { return ""; } } // comments m = P_COMMENT.matcher(s); if (!stripComment && m.find()) { return "<" + m.group() + ">"; } return ""; } private String processParamProtocol(String s) { s = decodeEntities(s); final Matcher m = P_PROTOCOL.matcher(s); if (m.find()) { final String protocol = m.group(1); if (!inArray(protocol, vAllowedProtocols)) { // bad protocol, turn into local anchor link instead s = "#" + s.substring(protocol.length() + 1); if (s.startsWith("#//")) { s = "#" + s.substring(3); } } } return s; } private String decodeEntities(String s) { StringBuffer buf = new StringBuffer(); Matcher m = P_ENTITY.matcher(s); while (m.find()) { final String match = m.group(1); final int decimal = Integer.decode(match).intValue(); m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); } m.appendTail(buf); s = buf.toString(); buf = new StringBuffer(); m = P_ENTITY_UNICODE.matcher(s); while (m.find()) { final String match = m.group(1); final int decimal = Integer.valueOf(match, 16).intValue(); m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); } m.appendTail(buf); s = buf.toString(); buf = new StringBuffer(); m = P_ENCODE.matcher(s); while (m.find()) { final String match = m.group(1); final int decimal = Integer.valueOf(match, 16).intValue(); m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); } m.appendTail(buf); s = buf.toString(); s = validateEntities(s); return s; } private String validateEntities(final String s) { StringBuffer buf = new StringBuffer(); // validate entities throughout the string Matcher m = P_VALID_ENTITIES.matcher(s); while (m.find()) { final String one = m.group(1); // ([^&;]*) final String two = m.group(2); // (?=(;|&|$)) m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); } m.appendTail(buf); return encodeQuotes(buf.toString()); } private String encodeQuotes(final String s) { if (encodeQuotes) { StringBuffer buf = new StringBuffer(); Matcher m = P_VALID_QUOTES.matcher(s); while (m.find()) { final String one = m.group(1); // (>|^) final String two = m.group(2); // ([^<]+?) final String three = m.group(3); // (<|$) // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); } m.appendTail(buf); return buf.toString(); } else { return s; } } private String checkEntity(final String preamble, final String term) { return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; } private boolean isValidEntity(final String entity) { return inArray(entity, vAllowedEntities); } private static boolean inArray(final String s, final String[] array) { for (String item : array) { if (item != null && item.equals(s)) { return true; } } return false; } private boolean allowed(final String name) { return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); } private boolean allowedAttribute(final String name, final String paramName) { return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java ================================================ package com.ruoyi.common.utils.http; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.StringUtils; import org.springframework.http.MediaType; /** * 通用http发送方法 * * @author ruoyi */ public class HttpUtils { private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); /** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @return 所代表远程资源的响应结果 */ public static String sendGet(String url) { return sendGet(url, StringUtils.EMPTY); } /** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { return sendGet(url, param, Constants.UTF8); } /** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @param contentType 编码类型 * @return 所代表远程资源的响应结果 */ public static String sendGet(String url, String param, String contentType) { StringBuilder result = new StringBuilder(); BufferedReader in = null; try { String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; log.info("sendGet - {}", urlNameString); URL realUrl = new URL(urlNameString); URLConnection connection = realUrl.openConnection(); connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); connection.connect(); in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); String line; while ((line = in.readLine()) != null) { result.append(line); } log.info("recv - {}", result); } catch (ConnectException e) { log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); } finally { try { if (in != null) { in.close(); } } catch (Exception ex) { log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); } } return result.toString(); } /** * 向指定 URL 发送POST方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param) { return sendPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE); } /** * 向指定 URL 发送POST方法的请求 * * @param url 发送请求的 URL * @param param 请求参数 * @param contentType 内容类型 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param, String contentType) { PrintWriter out = null; BufferedReader in = null; StringBuilder result = new StringBuilder(); try { log.info("sendPost - {}", url); URL realUrl = new URL(url); URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("Content-Type", contentType); conn.setDoOutput(true); conn.setDoInput(true); out = new PrintWriter(conn.getOutputStream()); out.print(param); out.flush(); in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); String line; while ((line = in.readLine()) != null) { result.append(line); } log.info("recv - {}", result); } catch (ConnectException e) { log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); } finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); } } return result.toString(); } public static String sendSSLPost(String url, String param) { return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE); } public static String sendSSLPost(String url, String param, String contentType) { StringBuilder result = new StringBuilder(); String urlNameString = url + "?" + param; try { log.info("sendSSLPost - {}", urlNameString); SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); URL console = new URL(urlNameString); HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("Content-Type", contentType); conn.setDoOutput(true); conn.setDoInput(true); conn.setSSLSocketFactory(sc.getSocketFactory()); conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); conn.connect(); InputStream is = conn.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String ret = ""; while ((ret = br.readLine()) != null) { if (ret != null && !"".equals(ret.trim())) { result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); } } log.info("recv - {}", result); conn.disconnect(); br.close(); } catch (ConnectException e) { log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); } return result.toString(); } private static class TrustAnyTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } } private static class TrustAnyHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return true; } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/http/UserAgentUtils.java ================================================ package com.ruoyi.common.utils.http; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.ruoyi.common.utils.StringUtils; import nl.basjes.parse.useragent.UserAgent; import nl.basjes.parse.useragent.UserAgentAnalyzer; /** * UserAgent解析工具类 * * @author ruoyi */ public class UserAgentUtils { public static final String UNKNOWN = ""; // 浏览器正则表达式模式 private static final Pattern CHROME_PATTERN = Pattern.compile("Chrome/(\\d+)(?:\\.\\d+)*"); private static final Pattern FIREFOX_PATTERN = Pattern.compile("Firefox/(\\d+)(?:\\.\\d+)*"); private static final Pattern EDGE_PATTERN = Pattern.compile("Edg(?:e)?/(\\d+)(?:\\.\\d+)*"); private static final Pattern SAFARI_PATTERN = Pattern.compile("Version/(\\d+)(?:\\.\\d+)*"); private static final Pattern OPERA_PATTERN = Pattern.compile("Opera/(\\d+)(?:\\.\\d+)*"); private static final Pattern IE_PATTERN = Pattern.compile("(?:MSIE |Trident/.*rv:)(\\d+)(?:\\.\\d+)*"); private static final Pattern SAMSUNG_PATTERN = Pattern.compile("SamsungBrowser/(\\d+)(?:\\.\\d+)*"); private static final Pattern UC_PATTERN = Pattern.compile("UCBrowser/(\\d+)(?:\\.\\d+)*"); private static final Pattern QQ_PATTERN = Pattern.compile("QQBrowser/(\\d+)(?:\\.\\d+)*"); private static final Pattern WECHAT_PATTERN = Pattern.compile("MicroMessenger/(\\d+)(?:\\.\\d+)*"); private static final Pattern BAIDU_PATTERN = Pattern.compile("baidubrowser/(\\d+)(?:\\.\\d+)*"); // 操作系统正则表达式模式 private static final Pattern WINDOWS_PATTERN = Pattern.compile("Windows NT (\\d+\\.\\d+)"); private static final Pattern MACOS_PATTERN = Pattern.compile("Mac OS X (\\d+[_\\d]*)"); private static final Pattern ANDROID_PATTERN = Pattern.compile("Android (\\d+)(?:\\.\\d+)*"); private static final Pattern IOS_PATTERN = Pattern.compile("OS[\\s_](\\d+)(?:_\\d+)*"); private static final Pattern LINUX_PATTERN = Pattern.compile("Linux"); private static final Pattern CHROMEOS_PATTERN = Pattern.compile("CrOS"); private static final UserAgentAnalyzer userAgentAnalyzer = UserAgentAnalyzer .newBuilder().hideMatcherLoadStats() .withCache(5000) .showMinimalVersion() .withField(UserAgent.AGENT_NAME_VERSION) .withField(UserAgent.OPERATING_SYSTEM_NAME_VERSION) .build(); /** * 获取客户端浏览器 */ public static String getBrowser(String userAgent) { UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent); String agentNameVersion = iua.get(UserAgent.AGENT_NAME_VERSION).getValue(); if (StringUtils.isBlank(agentNameVersion) || agentNameVersion.contains("??")) { return formatBrowser(userAgent); } return agentNameVersion; } /** * 获取客户端操作系统 */ public static String getOperatingSystem(String userAgent) { UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent); String operatingSystemNameVersion = iua.get(UserAgent.OPERATING_SYSTEM_NAME_VERSION).getValue(); if (StringUtils.isBlank(operatingSystemNameVersion) || operatingSystemNameVersion.contains("??")) { return formatOperatingSystem(userAgent); } return operatingSystemNameVersion; } /** * 全面浏览器检测 */ private static String formatBrowser(String browser) { // Chrome系列浏览器 Matcher chromeMatcher = CHROME_PATTERN.matcher(browser); if (chromeMatcher.find() && (browser.contains("Chrome") || browser.contains("CriOS"))) { return "Chrome" + chromeMatcher.group(1); } // Firefox Matcher firefoxMatcher = FIREFOX_PATTERN.matcher(browser); if (firefoxMatcher.find()) { return "Firefox" + firefoxMatcher.group(1); } // Edge浏览器 Matcher edgeMatcher = EDGE_PATTERN.matcher(browser); if (edgeMatcher.find()) { return "Edge" + edgeMatcher.group(1); } // Safari浏览器(需排除Chrome) Matcher safariMatcher = SAFARI_PATTERN.matcher(browser); if (safariMatcher.find() && !browser.contains("Chrome")) { return "Safari" + safariMatcher.group(1); } // 微信内置浏览器 Matcher wechatMatcher = WECHAT_PATTERN.matcher(browser); if (wechatMatcher.find()) { return "WeChat" + wechatMatcher.group(1); } // UC浏览器 Matcher ucMatcher = UC_PATTERN.matcher(browser); if (ucMatcher.find()) { return "UC Browser" + ucMatcher.group(1); } // QQ浏览器 Matcher qqMatcher = QQ_PATTERN.matcher(browser); if (qqMatcher.find()) { return "QQ Browser" + qqMatcher.group(1); } // 百度浏览器 Matcher baiduMatcher = BAIDU_PATTERN.matcher(browser); if (baiduMatcher.find()) { return "Baidu Browser" + baiduMatcher.group(1); } // Samsung浏览器 Matcher samsungMatcher = SAMSUNG_PATTERN.matcher(browser); if (samsungMatcher.find()) { return "Samsung Browser" + samsungMatcher.group(1); } // Opera浏览器 Matcher operaMatcher = OPERA_PATTERN.matcher(browser); if (operaMatcher.find()) { return "Opera" + operaMatcher.group(1); } // IE浏览器 Matcher ieMatcher = IE_PATTERN.matcher(browser); if (ieMatcher.find()) { return "Internet Explorer" + ieMatcher.group(1); } return UNKNOWN; } /** * 检测操作系统 */ private static String formatOperatingSystem(String operatingSystem) { // Windows系统 Matcher windowsMatcher = WINDOWS_PATTERN.matcher(operatingSystem); if (windowsMatcher.find()) { return "Windows" + getWindowsVersionDisplay(windowsMatcher.group(1)); } // macOS系统 Matcher macMatcher = MACOS_PATTERN.matcher(operatingSystem); if (macMatcher.find()) { String version = macMatcher.group(1).replace("_", "."); return "macOS" + extractMajorVersion(version); } // Android系统 Matcher androidMatcher = ANDROID_PATTERN.matcher(operatingSystem); if (androidMatcher.find()) { return "Android" + extractMajorVersion(androidMatcher.group(1)); } // iOS系统 Matcher iosMatcher = IOS_PATTERN.matcher(operatingSystem); if (iosMatcher.find() && (operatingSystem.contains("iPhone") || operatingSystem.contains("iPad"))) { return "iOS" + extractMajorVersion(iosMatcher.group(1)); } // Linux系统 if (LINUX_PATTERN.matcher(operatingSystem).find() && !operatingSystem.contains("Android")) { return "Linux"; } // Chrome OS if (CHROMEOS_PATTERN.matcher(operatingSystem).find()) { return "Chrome OS"; } return UNKNOWN; } /** * 提取优化的主版本号 */ private static String extractMajorVersion(String fullVersion) { if (StringUtils.isEmpty(fullVersion)) { return StringUtils.EMPTY; } try { // 清理版本号中的非数字字符 String cleanVersion = fullVersion.replaceAll("[^0-9.]", ""); String[] parts = cleanVersion.split("\\."); if (parts.length > 0) { String firstPart = parts[0]; if (firstPart.matches("\\d+")) { int version = Integer.parseInt(firstPart); // 处理三位数版本号(如142 -> 14) if (version >= 100) { return String.valueOf(version / 10); } return firstPart; } } } catch (NumberFormatException e) { // 版本号解析失败,返回原始值 } return fullVersion; } /** * Windows版本号显示优化 */ private static String getWindowsVersionDisplay(String version) { switch (version) { case "10.0": return "10"; case "6.3": return "8.1"; case "6.2": return "8"; case "6.1": return "7"; case "6.0": return "Vista"; case "5.1": return "XP"; case "5.0": return "2000"; default: return extractMajorVersion(version); } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java ================================================ package com.ruoyi.common.utils.poi; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Workbook; /** * Excel数据格式处理适配器 * * @author ruoyi */ public interface ExcelHandlerAdapter { /** * 格式化 * * @param value 单元格数据值 * @param args excel注解args参数组 * @param cell 单元格对象 * @param wb 工作簿对象 * * @return 处理后的值 */ Object format(Object value, String[] args, Cell cell, Workbook wb); } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java ================================================ package com.ruoyi.common.utils.poi; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; import java.text.DecimalFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.poi.hssf.usermodel.HSSFClientAnchor; import org.apache.poi.hssf.usermodel.HSSFPicture; import org.apache.poi.hssf.usermodel.HSSFPictureData; import org.apache.poi.hssf.usermodel.HSSFShape; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.DataFormat; import org.apache.poi.ss.usermodel.DataValidation; import org.apache.poi.ss.usermodel.DataValidationConstraint; import org.apache.poi.ss.usermodel.DataValidationHelper; import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Name; import org.apache.poi.ss.usermodel.PictureData; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.util.IOUtils; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.XSSFDataValidation; import org.apache.poi.xssf.usermodel.XSSFDrawing; import org.apache.poi.xssf.usermodel.XSSFPicture; import org.apache.poi.xssf.usermodel.XSSFShape; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.annotation.Excel.Type; import com.ruoyi.common.annotation.Excels; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.UtilException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DictUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileTypeUtils; import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.ImageUtils; import com.ruoyi.common.utils.reflect.ReflectUtils; /** * Excel相关处理 * * @author ruoyi */ public class ExcelUtil { private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); public static final String SEPARATOR = ","; public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; /** * 用于dictType属性数据存储,避免重复查缓存 */ public Map sysDictMap = new HashMap(); /** * 单元格样式缓存 */ private Map cellStyleCache = new HashMap(); /** * Excel sheet最大行数,默认65536 */ public static final int sheetSize = 65536; /** * 工作表名称 */ private String sheetName; /** * 导出类型(EXPORT:导出数据;IMPORT:导入模板) */ private Type type; /** * 工作薄对象 */ private Workbook wb; /** * 工作表对象 */ private Sheet sheet; /** * 样式列表 */ private Map styles; /** * 导入导出数据列表 */ private List list; /** * 注解列表 */ private List fields; /** * 当前行号 */ private int rownum; /** * 标题 */ private String title; /** * 最大高度 */ private short maxHeight; /** * 合并后最后行数 */ private int subMergedLastRowNum = 0; /** * 合并后开始行数 */ private int subMergedFirstRowNum = 1; /** * 对象的子列表方法 */ private Map subMethods; /** * 对象的子列表属性 */ private Map> subFieldsMap; /** * 统计列表 */ private Map statistics = new HashMap(); /** * 实体对象 */ public Class clazz; /** * 需要显示列属性 */ public String[] includeFields; /** * 需要排除列属性 */ public String[] excludeFields; public ExcelUtil(Class clazz) { this.clazz = clazz; } /** * 仅在Excel中显示列属性 * * @param fields 列属性名 示例[单个"name"/多个"id","name"] */ public void showColumn(String... fields) { this.includeFields = fields; } /** * 隐藏Excel中列属性 * * @param fields 列属性名 示例[单个"name"/多个"id","name"] */ public void hideColumn(String... fields) { this.excludeFields = fields; } public void init(List list, String sheetName, String title, Type type) { if (list == null) { list = new ArrayList(); } this.list = list; this.sheetName = sheetName; this.type = type; this.title = title; createExcelField(); createWorkbook(); createTitle(); createSubHead(); } /** * 创建excel第一行标题 */ public void createTitle() { if (StringUtils.isNotEmpty(title)) { int titleLastCol = this.fields.size() - 1; if (isSubList()) { for (List currentSubFields : subFieldsMap.values()) { titleLastCol = titleLastCol + currentSubFields.size() - 1; } } Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); titleRow.setHeightInPoints(30); Cell titleCell = titleRow.createCell(0); titleCell.setCellStyle(styles.get("title")); titleCell.setCellValue(title); sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, titleLastCol)); } } /** * 创建对象的子列表名称 */ public void createSubHead() { if (isSubList()) { Row subRow = sheet.createRow(rownum); int column = 0; for (Object[] objects : fields) { Field field = (Field) objects[0]; Excel attr = (Excel) objects[1]; CellStyle cellStyle = styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())); if (Collection.class.isAssignableFrom(field.getType())) { Cell cell = subRow.createCell(column); cell.setCellValue(attr.name()); cell.setCellStyle(cellStyle); int subFieldSize = subFieldsMap != null ? subFieldsMap.get(field.getName()).size() : 0; if (subFieldSize > 1) { CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); sheet.addMergedRegion(cellAddress); } column += subFieldSize; } else { Cell cell = subRow.createCell(column++); cell.setCellValue(attr.name()); cell.setCellStyle(cellStyle); } } rownum++; } } /** * 对excel表单默认第一个索引名转换成list * * @param is 输入流 * @return 转换后集合 */ public List importExcel(InputStream is) { return importExcel(is, 0); } /** * 对excel表单默认第一个索引名转换成list * * @param is 输入流 * @param titleNum 标题占用行数 * @return 转换后集合 */ public List importExcel(InputStream is, int titleNum) { List list = null; try { list = importExcel(StringUtils.EMPTY, is, titleNum); } catch (Exception e) { log.error("导入Excel异常{}", e.getMessage()); throw new UtilException(e.getMessage()); } finally { IOUtils.closeQuietly(is); } return list; } /** * 对excel表单指定表格索引名转换成list * * @param sheetName 表格索引名 * @param titleNum 标题占用行数 * @param is 输入流 * @return 转换后集合 */ public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception { this.type = Type.IMPORT; this.wb = WorkbookFactory.create(is); List list = new ArrayList(); // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); if (sheet == null) { throw new IOException("文件sheet不存在"); } boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); Map> pictures = null; if (isXSSFWorkbook) { pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); } else { pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); } // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 int rows = sheet.getLastRowNum(); if (rows > 0) { // 定义一个map用于存放excel列的序号和field. Map cellMap = new HashMap(); // 获取表头 Row heard = sheet.getRow(titleNum); if (heard == null) { throw new UtilException("文件标题行为空,请检查Excel文件格式"); } for (int i = 0; i < heard.getLastCellNum(); i++) { Cell cell = heard.getCell(i); if (StringUtils.isNotNull(cell)) { String value = this.getCellValue(heard, i).toString(); cellMap.put(value, i); } } // 有数据时才处理 得到类的所有field. List fields = this.getFields(); Map fieldsMap = new HashMap(); for (Object[] objects : fields) { Excel attr = (Excel) objects[1]; Integer column = cellMap.get(attr.name()); if (column != null) { fieldsMap.put(column, objects); } } for (int i = titleNum + 1; i <= rows; i++) { // 从第2行开始取数据,默认第一行是表头. Row row = sheet.getRow(i); // 判断当前行是否是空行 if (isRowEmpty(row)) { continue; } T entity = null; for (Map.Entry entry : fieldsMap.entrySet()) { Object val = this.getCellValue(row, entry.getKey()); // 如果不存在实例则新建. entity = (entity == null ? clazz.getDeclaredConstructor().newInstance() : entity); // 从map中得到对应列的field. Field field = (Field) entry.getValue()[0]; Excel attr = (Excel) entry.getValue()[1]; // 取得类型,并根据对象类型设置值. Class fieldType = field.getType(); if (String.class == fieldType) { String s = Convert.toStr(val); if (s.matches("^\\d+\\.0$")) { val = StringUtils.substringBefore(s, ".0"); } else { String dateFormat = field.getAnnotation(Excel.class).dateFormat(); if (StringUtils.isNotEmpty(dateFormat)) { val = parseDateToStr(dateFormat, val); } else { val = Convert.toStr(val); } } } else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) { val = Convert.toInt(val); } else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) { val = Convert.toLong(val); } else if (Double.TYPE == fieldType || Double.class == fieldType) { val = Convert.toDouble(val); } else if (Float.TYPE == fieldType || Float.class == fieldType) { val = Convert.toFloat(val); } else if (BigDecimal.class == fieldType) { val = Convert.toBigDecimal(val); } else if (Date.class == fieldType) { if (val instanceof String) { val = DateUtils.parseDate(val); } else if (val instanceof Double) { val = DateUtil.getJavaDate((Double) val); } } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) { val = Convert.toBool(val, false); } if (StringUtils.isNotNull(fieldType)) { String propertyName = field.getName(); if (StringUtils.isNotEmpty(attr.targetAttr())) { propertyName = field.getName() + "." + attr.targetAttr(); } if (StringUtils.isNotEmpty(attr.readConverterExp())) { val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); } else if (StringUtils.isNotEmpty(attr.dictType())) { if (!sysDictMap.containsKey(attr.dictType() + val)) { String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); sysDictMap.put(attr.dictType() + val, dictValue); } val = sysDictMap.get(attr.dictType() + val); } else if (!attr.handler().equals(ExcelHandlerAdapter.class)) { val = dataFormatHandlerAdapter(val, attr, null); } else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) { StringBuilder propertyString = new StringBuilder(); List images = pictures.get(row.getRowNum() + "_" + entry.getKey()); for (PictureData picture : images) { byte[] data = picture.getData(); String fileName = FileUtils.writeImportBytes(data); propertyString.append(fileName).append(SEPARATOR); } val = StringUtils.stripEnd(propertyString.toString(), SEPARATOR); } ReflectUtils.invokeSetter(entity, propertyName, val); } } list.add(entity); } } return list; } /** * 对list数据源将其里面的数据导入到excel表单 * * @param list 导出数据集合 * @param sheetName 工作表的名称 * @return 结果 */ public AjaxResult exportExcel(List list, String sheetName) { return exportExcel(list, sheetName, StringUtils.EMPTY); } /** * 对list数据源将其里面的数据导入到excel表单 * * @param list 导出数据集合 * @param sheetName 工作表的名称 * @param title 标题 * @return 结果 */ public AjaxResult exportExcel(List list, String sheetName, String title) { this.init(list, sheetName, title, Type.EXPORT); return exportExcel(); } /** * 对list数据源将其里面的数据导入到excel表单 * * @param response 返回数据 * @param list 导出数据集合 * @param sheetName 工作表的名称 * @return 结果 */ public void exportExcel(HttpServletResponse response, List list, String sheetName) { exportExcel(response, list, sheetName, StringUtils.EMPTY); } /** * 对list数据源将其里面的数据导入到excel表单 * * @param response 返回数据 * @param list 导出数据集合 * @param sheetName 工作表的名称 * @param title 标题 * @return 结果 */ public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); this.init(list, sheetName, title, Type.EXPORT); exportExcel(response); } /** * 对list数据源将其里面的数据导入到excel表单 * * @param sheetName 工作表的名称 * @return 结果 */ public AjaxResult importTemplateExcel(String sheetName) { return importTemplateExcel(sheetName, StringUtils.EMPTY); } /** * 对list数据源将其里面的数据导入到excel表单 * * @param sheetName 工作表的名称 * @param title 标题 * @return 结果 */ public AjaxResult importTemplateExcel(String sheetName, String title) { this.init(null, sheetName, title, Type.IMPORT); return exportExcel(); } /** * 对list数据源将其里面的数据导入到excel表单 * * @param sheetName 工作表的名称 * @return 结果 */ public void importTemplateExcel(HttpServletResponse response, String sheetName) { importTemplateExcel(response, sheetName, StringUtils.EMPTY); } /** * 对list数据源将其里面的数据导入到excel表单 * * @param sheetName 工作表的名称 * @param title 标题 * @return 结果 */ public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); this.init(null, sheetName, title, Type.IMPORT); exportExcel(response); } /** * 对list数据源将其里面的数据导入到excel表单 * * @return 结果 */ public void exportExcel(HttpServletResponse response) { try { writeSheet(); wb.write(response.getOutputStream()); } catch (Exception e) { log.error("导出Excel异常{}", e.getMessage()); } finally { IOUtils.closeQuietly(wb); } } /** * 对list数据源将其里面的数据导入到excel表单 * * @return 结果 */ public AjaxResult exportExcel() { OutputStream out = null; try { writeSheet(); String filename = encodingFilename(sheetName); out = new FileOutputStream(getAbsoluteFile(filename)); wb.write(out); return AjaxResult.success(filename); } catch (Exception e) { log.error("导出Excel异常{}", e.getMessage()); throw new UtilException("导出Excel失败,请联系网站管理员!"); } finally { IOUtils.closeQuietly(wb); IOUtils.closeQuietly(out); } } /** * 创建写入数据到Sheet */ public void writeSheet() { // 取出一共有多少个sheet. int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); for (int index = 0; index < sheetNo; index++) { createSheet(sheetNo, index); // 产生一行 Row row = sheet.createRow(rownum); int column = 0; // 写入各个字段的列头名称 for (Object[] os : fields) { Field field = (Field) os[0]; Excel excel = (Excel) os[1]; if (Collection.class.isAssignableFrom(field.getType())) { List currentSubFields = subFieldsMap.get(field.getName()); for (Field subField : currentSubFields) { Excel subExcel = subField.getAnnotation(Excel.class); this.createHeadCell(subExcel, row, column++); } } else { this.createHeadCell(excel, row, column++); } } if (Type.EXPORT.equals(type)) { fillExcelData(index); addStatisticsRow(); } } } /** * 填充excel数据 * * @param index 序号 */ @SuppressWarnings("unchecked") public void fillExcelData(int index) { int startNo = index * sheetSize; int endNo = Math.min(startNo + sheetSize, list.size()); int currentRowNum = rownum + 1; // 从标题行后开始 for (int i = startNo; i < endNo; i++) { Row row = sheet.createRow(currentRowNum); T vo = (T) list.get(i); int column = 0; int maxSubListSize = getCurrentMaxSubListSize(vo); for (Object[] os : fields) { Field field = (Field) os[0]; Excel excel = (Excel) os[1]; if (Collection.class.isAssignableFrom(field.getType())) { try { Collection subList = (Collection) getTargetValue(vo, field, excel); List currentSubFields = subFieldsMap.get(field.getName()); if (subList != null && !subList.isEmpty()) { int subIndex = 0; for (Object subVo : subList) { Row subRow = sheet.getRow(currentRowNum + subIndex); if (subRow == null) { subRow = sheet.createRow(currentRowNum + subIndex); } int subColumn = column; for (Field subField : currentSubFields) { Excel subExcel = subField.getAnnotation(Excel.class); addCell(subExcel, subRow, (T) subVo, subField, subColumn++); } subIndex++; } } column += currentSubFields.size(); } catch (Exception e) { log.error("填充集合数据失败", e); } } else { // 创建单元格并设置值 addCell(excel, row, vo, field, column); if (maxSubListSize > 1 && excel.needMerge()) { sheet.addMergedRegion(new CellRangeAddress(currentRowNum, currentRowNum + maxSubListSize - 1, column, column)); } column++; } } currentRowNum += maxSubListSize; } } /** * 获取子列表最大数 */ private int getCurrentMaxSubListSize(T vo) { int maxSubListSize = 1; for (Object[] os : fields) { Field field = (Field) os[0]; if (Collection.class.isAssignableFrom(field.getType())) { try { Collection subList = (Collection) getTargetValue(vo, field, (Excel) os[1]); if (subList != null && !subList.isEmpty()) { maxSubListSize = Math.max(maxSubListSize, subList.size()); } } catch (Exception e) { log.error("获取集合大小失败", e); } } } return maxSubListSize; } /** * 创建表格样式 * * @param wb 工作薄对象 * @return 样式列表 */ private Map createStyles(Workbook wb) { // 写入各条记录,每条记录对应excel表中的一行 Map styles = new HashMap(); CellStyle style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); Font titleFont = wb.createFont(); titleFont.setFontName("Arial"); titleFont.setFontHeightInPoints((short) 16); titleFont.setBold(true); style.setFont(titleFont); DataFormat dataFormat = wb.createDataFormat(); style.setDataFormat(dataFormat.getFormat("@")); styles.put("title", style); style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setBorderRight(BorderStyle.THIN); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderLeft(BorderStyle.THIN); style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderTop(BorderStyle.THIN); style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderBottom(BorderStyle.THIN); style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); Font dataFont = wb.createFont(); dataFont.setFontName("Arial"); dataFont.setFontHeightInPoints((short) 10); style.setFont(dataFont); styles.put("data", style); style = wb.createCellStyle(); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setDataFormat(dataFormat.getFormat("######0.00")); Font totalFont = wb.createFont(); totalFont.setFontName("Arial"); totalFont.setFontHeightInPoints((short) 10); style.setFont(totalFont); styles.put("total", style); styles.putAll(annotationHeaderStyles(wb, styles)); styles.putAll(annotationDataStyles(wb)); return styles; } /** * 根据Excel注解创建表格头样式 * * @param wb 工作薄对象 * @return 自定义样式列表 */ private Map annotationHeaderStyles(Workbook wb, Map styles) { Map headerStyles = new HashMap(); for (Object[] os : fields) { Excel excel = (Excel) os[1]; String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); if (!headerStyles.containsKey(key)) { CellStyle style = wb.createCellStyle(); style.cloneStyleFrom(styles.get("data")); style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setFillForegroundColor(excel.headerBackgroundColor().index); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); Font headerFont = wb.createFont(); headerFont.setFontName("Arial"); headerFont.setFontHeightInPoints((short) 10); headerFont.setBold(true); headerFont.setColor(excel.headerColor().index); style.setFont(headerFont); // 设置表格头单元格文本形式 DataFormat dataFormat = wb.createDataFormat(); style.setDataFormat(dataFormat.getFormat("@")); headerStyles.put(key, style); } } return headerStyles; } /** * 根据Excel注解创建表格列样式 * * @param wb 工作薄对象 * @return 自定义样式列表 */ private Map annotationDataStyles(Workbook wb) { Map styles = new HashMap(); for (Object[] os : fields) { Field field = (Field) os[0]; Excel excel = (Excel) os[1]; if (Collection.class.isAssignableFrom(field.getType())) { ParameterizedType pt = (ParameterizedType) field.getGenericType(); Class subClass = (Class) pt.getActualTypeArguments()[0]; List subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); for (Field subField : subFields) { Excel subExcel = subField.getAnnotation(Excel.class); annotationDataStyles(styles, subField, subExcel); } } else { annotationDataStyles(styles, field, excel); } } return styles; } /** * 根据Excel注解创建表格列样式 * * @param styles 自定义样式列表 * @param field 属性列信息 * @param excel 注解信息 */ public void annotationDataStyles(Map styles, Field field, Excel excel) { String key = StringUtils.format("data_{}_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType(), excel.wrapText()); if (!styles.containsKey(key)) { CellStyle style = wb.createCellStyle(); style.setAlignment(excel.align()); style.setVerticalAlignment(VerticalAlignment.CENTER); style.setBorderRight(BorderStyle.THIN); style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderLeft(BorderStyle.THIN); style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderTop(BorderStyle.THIN); style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setBorderBottom(BorderStyle.THIN); style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setFillForegroundColor(excel.backgroundColor().getIndex()); style.setWrapText(excel.wrapText()); Font dataFont = wb.createFont(); dataFont.setFontName("Arial"); dataFont.setFontHeightInPoints((short) 10); dataFont.setColor(excel.color().index); style.setFont(dataFont); if (ColumnType.TEXT == excel.cellType()) { DataFormat dataFormat = wb.createDataFormat(); style.setDataFormat(dataFormat.getFormat("@")); } styles.put(key, style); } } /** * 创建单元格 */ public Cell createHeadCell(Excel attr, Row row, int column) { // 创建列 Cell cell = row.createCell(column); // 写入列信息 cell.setCellValue(attr.name()); setDataValidation(attr, row, column); cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); if (isSubList()) { // 填充默认样式,防止合并单元格样式失效 sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); if (attr.needMerge()) { sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); } } return cell; } /** * 设置单元格信息 * * @param value 单元格值 * @param attr 注解相关 * @param cell 单元格信息 */ public void setCellVo(Object value, Excel attr, Cell cell) { if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType()) { String cellValue = Convert.toStr(value); // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) { cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); } if (value instanceof Collection && StringUtils.equals("[]", cellValue)) { cellValue = StringUtils.EMPTY; } cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); } else if (ColumnType.NUMERIC == attr.cellType()) { if (StringUtils.isNotNull(value)) { cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); } } else if (ColumnType.IMAGE == attr.cellType()) { ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); String propertyValue = Convert.toStr(value); if (StringUtils.isNotEmpty(propertyValue)) { List imagePaths = StringUtils.str2List(propertyValue, SEPARATOR); for (String imagePath : imagePaths) { byte[] data = ImageUtils.getImage(imagePath); getDrawingPatriarch(cell.getSheet()).createPicture(anchor, cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); } } } } /** * 获取画布 */ public static Drawing getDrawingPatriarch(Sheet sheet) { if (sheet.getDrawingPatriarch() == null) { sheet.createDrawingPatriarch(); } return sheet.getDrawingPatriarch(); } /** * 获取图片类型,设置图片插入类型 */ public int getImageType(byte[] value) { String type = FileTypeUtils.getFileExtendName(value); if ("JPG".equalsIgnoreCase(type)) { return Workbook.PICTURE_TYPE_JPEG; } else if ("PNG".equalsIgnoreCase(type)) { return Workbook.PICTURE_TYPE_PNG; } return Workbook.PICTURE_TYPE_JPEG; } /** * 创建表格样式 */ public void setDataValidation(Excel attr, Row row, int column) { if (attr.name().indexOf("注:") >= 0) { sheet.setColumnWidth(column, 6000); } else { // 设置列宽 sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); } if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict()) { String[] comboArray = attr.combo(); if (attr.comboReadDict()) { if (!sysDictMap.containsKey("combo_" + attr.dictType())) { String labels = DictUtils.getDictLabels(attr.dictType()); sysDictMap.put("combo_" + attr.dictType(), labels); } String val = sysDictMap.get("combo_" + attr.dictType()); comboArray = StringUtils.split(val, DictUtils.SEPARATOR); } if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255) { // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 setXSSFValidationWithHidden(sheet, comboArray, attr.prompt(), 1, 100, column, column); } else { // 提示信息或只能选择不能输入的列内容. setPromptOrValidation(sheet, comboArray, attr.prompt(), 1, 100, column, column); } } } /** * 添加单元格 */ @SuppressWarnings("deprecation") public Cell addCell(Excel attr, Row row, T vo, Field field, int column) { Cell cell = null; try { // 设置行高 row.setHeight(maxHeight); // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. if (attr.isExport()) { // 创建cell cell = row.createCell(column); if (isSubListValue(vo) && getListCellValue(vo) > 1 && attr.needMerge()) { if (subMergedLastRowNum >= subMergedFirstRowNum) { sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column)); } } cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); // 用于读取对象中的属性 Object value = getTargetValue(vo, field, attr); String dateFormat = attr.dateFormat(); String readConverterExp = attr.readConverterExp(); String separator = attr.separator(); String dictType = attr.dictType(); if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) { cell.setCellStyle(createCellStyle(cell.getCellStyle(), dateFormat)); cell.setCellValue(parseDateToStr(dateFormat, value)); } else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) { cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); } else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) { if (!sysDictMap.containsKey(dictType + value)) { String lable = convertDictByExp(Convert.toStr(value), dictType, separator); sysDictMap.put(dictType + value, lable); } cell.setCellValue(sysDictMap.get(dictType + value)); } else if (value instanceof BigDecimal && -1 != attr.scale()) { cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); } else if (!attr.handler().equals(ExcelHandlerAdapter.class)) { cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell)); } else { // 设置列类型 setCellVo(value, attr, cell); } addStatisticsData(column, Convert.toStr(value), attr); } } catch (Exception e) { log.error("导出Excel失败{}", e); } return cell; } /** * 使用自定义格式,同时避免样式污染 * * @param cellStyle 从此样式复制 * @param format 格式匹配的字符串 * @return 格式化后CellStyle对象 */ private CellStyle createCellStyle(CellStyle cellStyle, String format) { String key = cellStyle.getIndex() + "|" + format; CellStyle cached = cellStyleCache.get(key); if (cached != null) { return cached; } CellStyle style = wb.createCellStyle(); style.cloneStyleFrom(cellStyle); style.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat(format)); cellStyleCache.put(key, style); return style; } /** * 设置 POI XSSFSheet 单元格提示或选择框 * * @param sheet 表单 * @param textlist 下拉框显示的内容 * @param promptContent 提示内容 * @param firstRow 开始行 * @param endRow 结束行 * @param firstCol 开始列 * @param endCol 结束列 */ public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) { DataValidationHelper helper = sheet.getDataValidationHelper(); DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); DataValidation dataValidation = helper.createValidation(constraint, regions); if (StringUtils.isNotEmpty(promptContent)) { // 如果设置了提示信息则鼠标放上去提示 dataValidation.createPromptBox("", promptContent); dataValidation.setShowPromptBox(true); } // 处理Excel兼容性问题 if (dataValidation instanceof XSSFDataValidation) { dataValidation.setSuppressDropDownArrow(true); dataValidation.setShowErrorBox(true); } else { dataValidation.setSuppressDropDownArrow(false); } sheet.addValidationData(dataValidation); } /** * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). * * @param sheet 要设置的sheet. * @param textlist 下拉框显示的内容 * @param promptContent 提示内容 * @param firstRow 开始行 * @param endRow 结束行 * @param firstCol 开始列 * @param endCol 结束列 */ public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) { String hideSheetName = "combo_" + firstCol + "_" + endCol; Sheet hideSheet = null; String hideSheetDataName = hideSheetName + "_data"; Name name = wb.getName(hideSheetDataName); if (name != null) { // 名称已存在,尝试从名称的引用中找到sheet名称 String refersToFormula = name.getRefersToFormula(); if (StringUtils.isNotEmpty(refersToFormula) && refersToFormula.contains("!")) { String sheetNameFromFormula = refersToFormula.substring(0, refersToFormula.indexOf("!")); hideSheet = wb.getSheet(sheetNameFromFormula); } } if (hideSheet == null) { hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 for (int i = 0; i < textlist.length; i++) { hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); } // 创建名称,可被其他单元格引用 name = wb.createName(); name.setNameName(hideSheetDataName); name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); } DataValidationHelper helper = sheet.getDataValidationHelper(); // 加载下拉列表内容 DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetDataName); // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); // 数据有效性对象 DataValidation dataValidation = helper.createValidation(constraint, regions); if (StringUtils.isNotEmpty(promptContent)) { // 如果设置了提示信息则鼠标放上去提示 dataValidation.createPromptBox("", promptContent); dataValidation.setShowPromptBox(true); } // 处理Excel兼容性问题 if (dataValidation instanceof XSSFDataValidation) { dataValidation.setSuppressDropDownArrow(true); dataValidation.setShowErrorBox(true); } else { dataValidation.setSuppressDropDownArrow(false); } sheet.addValidationData(dataValidation); // 设置hiddenSheet隐藏 wb.setSheetHidden(wb.getSheetIndex(hideSheet), true); } /** * 解析导出值 0=男,1=女,2=未知 * * @param propertyValue 参数值 * @param converterExp 翻译注解 * @param separator 分隔符 * @return 解析后值 */ public static String convertByExp(String propertyValue, String converterExp, String separator) { StringBuilder propertyString = new StringBuilder(); String[] convertSource = converterExp.split(SEPARATOR); for (String item : convertSource) { String[] itemArray = item.split("="); if (StringUtils.containsAny(propertyValue, separator)) { for (String value : propertyValue.split(separator)) { if (itemArray[0].equals(value)) { propertyString.append(itemArray[1] + separator); break; } } } else { if (itemArray[0].equals(propertyValue)) { return itemArray[1]; } } } return StringUtils.stripEnd(propertyString.toString(), separator); } /** * 反向解析值 男=0,女=1,未知=2 * * @param propertyValue 参数值 * @param converterExp 翻译注解 * @param separator 分隔符 * @return 解析后值 */ public static String reverseByExp(String propertyValue, String converterExp, String separator) { StringBuilder propertyString = new StringBuilder(); String[] convertSource = converterExp.split(SEPARATOR); for (String item : convertSource) { String[] itemArray = item.split("="); if (StringUtils.containsAny(propertyValue, separator)) { for (String value : propertyValue.split(separator)) { if (itemArray[1].equals(value)) { propertyString.append(itemArray[0] + separator); break; } } } else { if (itemArray[1].equals(propertyValue)) { return itemArray[0]; } } } return StringUtils.stripEnd(propertyString.toString(), separator); } /** * 解析字典值 * * @param dictValue 字典值 * @param dictType 字典类型 * @param separator 分隔符 * @return 字典标签 */ public static String convertDictByExp(String dictValue, String dictType, String separator) { return DictUtils.getDictLabel(dictType, dictValue, separator); } /** * 反向解析值字典值 * * @param dictLabel 字典标签 * @param dictType 字典类型 * @param separator 分隔符 * @return 字典值 */ public static String reverseDictByExp(String dictLabel, String dictType, String separator) { return DictUtils.getDictValue(dictType, dictLabel, separator); } /** * 数据处理器 * * @param value 数据值 * @param excel 数据注解 * @return */ public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) { try { Object instance = excel.handler().getDeclaredConstructor().newInstance(); Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class }); value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb); } catch (Exception e) { log.error("不能格式化数据 " + excel.handler(), e.getMessage()); } return Convert.toStr(value); } /** * 合计统计信息 */ private void addStatisticsData(Integer index, String text, Excel entity) { if (entity != null && entity.isStatistics()) { Double temp = 0D; if (!statistics.containsKey(index)) { statistics.put(index, temp); } try { temp = Double.valueOf(text); } catch (NumberFormatException e) { } statistics.put(index, statistics.get(index) + temp); } } /** * 创建统计行 */ public void addStatisticsRow() { if (statistics.size() > 0) { Row row = sheet.createRow(sheet.getLastRowNum() + 1); Set keys = statistics.keySet(); Cell cell = row.createCell(0); cell.setCellStyle(styles.get("total")); cell.setCellValue("合计"); for (Integer key : keys) { cell = row.createCell(key); cell.setCellStyle(styles.get("total")); cell.setCellValue(statistics.get(key)); } statistics.clear(); } } /** * 编码文件名 */ public String encodingFilename(String filename) { return UUID.randomUUID() + "_" + filename + ".xlsx"; } /** * 获取下载路径 * * @param filename 文件名称 */ public String getAbsoluteFile(String filename) { String downloadPath = RuoYiConfig.getDownloadPath() + filename; File desc = new File(downloadPath); if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } return downloadPath; } /** * 获取bean中的属性值 * * @param vo 实体对象 * @param field 字段 * @param excel 注解 * @return 最终的属性值 * @throws Exception */ private Object getTargetValue(T vo, Field field, Excel excel) throws Exception { field.setAccessible(true); Object o = field.get(vo); if (StringUtils.isNotEmpty(excel.targetAttr())) { String target = excel.targetAttr(); if (target.contains(".")) { String[] targets = target.split("[.]"); for (String name : targets) { o = getValue(o, name); } } else { o = getValue(o, target); } } return o; } /** * 以类的属性的get方法方法形式获取值 * * @param o * @param name * @return value * @throws Exception */ private Object getValue(Object o, String name) throws Exception { if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) { Class clazz = o.getClass(); Field field = clazz.getDeclaredField(name); field.setAccessible(true); o = field.get(o); } return o; } /** * 得到所有定义字段 */ private void createExcelField() { this.fields = getFields(); this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); this.maxHeight = getRowHeight(); } /** * 获取字段注解信息 */ public List getFields() { List fields = new ArrayList(); List tempFields = new ArrayList<>(); subFieldsMap = new HashMap<>(); subMethods = new HashMap<>(); tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); if (StringUtils.isNotEmpty(includeFields)) { for (Field field : tempFields) { if (ArrayUtils.contains(this.includeFields, field.getName()) || field.isAnnotationPresent(Excels.class)) { addField(fields, field); } } } else if (StringUtils.isNotEmpty(excludeFields)) { for (Field field : tempFields) { if (!ArrayUtils.contains(this.excludeFields, field.getName())) { addField(fields, field); } } } else { for (Field field : tempFields) { addField(fields, field); } } return fields; } /** * 添加字段信息 */ public void addField(List fields, Field field) { // 单注解 if (field.isAnnotationPresent(Excel.class)) { Excel attr = field.getAnnotation(Excel.class); if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) { fields.add(new Object[] { field, attr }); } if (Collection.class.isAssignableFrom(field.getType())) { String fieldName = field.getName(); subMethods.put(fieldName, getSubMethod(fieldName, clazz)); ParameterizedType pt = (ParameterizedType) field.getGenericType(); Class subClass = (Class) pt.getActualTypeArguments()[0]; subFieldsMap.put(fieldName, FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class)); } } // 多注解 if (field.isAnnotationPresent(Excels.class)) { Excels attrs = field.getAnnotation(Excels.class); Excel[] excels = attrs.value(); for (Excel attr : excels) { if (StringUtils.isNotEmpty(includeFields)) { if (ArrayUtils.contains(this.includeFields, field.getName() + "." + attr.targetAttr()) && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) { fields.add(new Object[] { field, attr }); } } else { if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) { fields.add(new Object[] { field, attr }); } } } } } /** * 根据注解获取最大行高 */ public short getRowHeight() { double maxHeight = 0; for (Object[] os : this.fields) { Excel excel = (Excel) os[1]; maxHeight = Math.max(maxHeight, excel.height()); } return (short) (maxHeight * 20); } /** * 创建一个工作簿 */ public void createWorkbook() { this.wb = new SXSSFWorkbook(500); this.sheet = wb.createSheet(); wb.setSheetName(0, sheetName); this.styles = createStyles(wb); } /** * 创建工作表 * * @param sheetNo sheet数量 * @param index 序号 */ public void createSheet(int sheetNo, int index) { // 设置工作表的名称. if (sheetNo > 1 && index > 0) { this.sheet = wb.createSheet(); this.createTitle(); int actualIndex = wb.getSheetIndex(this.sheet); wb.setSheetName(actualIndex, sheetName + index); } } /** * 获取单元格值 * * @param row 获取的行 * @param column 获取单元格列号 * @return 单元格值 */ public Object getCellValue(Row row, int column) { if (row == null) { return row; } Object val = ""; try { Cell cell = row.getCell(column); if (StringUtils.isNotNull(cell)) { if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) { val = cell.getNumericCellValue(); if (DateUtil.isCellDateFormatted(cell)) { val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 } else { if ((Double) val % 1 != 0) { val = new BigDecimal(val.toString()); } else { val = new DecimalFormat("0").format(val); } } } else if (cell.getCellType() == CellType.STRING) { val = cell.getStringCellValue(); } else if (cell.getCellType() == CellType.BOOLEAN) { val = cell.getBooleanCellValue(); } else if (cell.getCellType() == CellType.ERROR) { val = cell.getErrorCellValue(); } } } catch (Exception e) { return val; } return val; } /** * 判断是否是空行 * * @param row 判断的行 * @return */ private boolean isRowEmpty(Row row) { if (row == null) { return true; } for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) { Cell cell = row.getCell(i); if (cell != null && cell.getCellType() != CellType.BLANK) { return false; } } return true; } /** * 获取Excel2003图片 * * @param sheet 当前sheet对象 * @param workbook 工作簿对象 * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData */ public static Map> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) { Map> sheetIndexPicMap = new HashMap<>(); List pictures = workbook.getAllPictures(); if (!pictures.isEmpty() && sheet.getDrawingPatriarch() != null) { for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) { if (shape instanceof HSSFPicture) { HSSFPicture pic = (HSSFPicture) shape; HSSFClientAnchor anchor = (HSSFClientAnchor) pic.getAnchor(); String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); } } } return sheetIndexPicMap; } /** * 获取Excel2007图片 * * @param sheet 当前sheet对象 * @param workbook 工作簿对象 * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData */ public static Map> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) { Map> sheetIndexPicMap = new HashMap<>(); for (POIXMLDocumentPart dr : sheet.getRelations()) { if (dr instanceof XSSFDrawing) { XSSFDrawing drawing = (XSSFDrawing) dr; for (XSSFShape shape : drawing.getShapes()) { if (shape instanceof XSSFPicture) { XSSFPicture pic = (XSSFPicture) shape; XSSFClientAnchor anchor = pic.getPreferredSize(); CTMarker ctMarker = anchor.getFrom(); String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); } } } } return sheetIndexPicMap; } /** * 格式化不同类型的日期对象 * * @param dateFormat 日期格式 * @param val 被格式化的日期对象 * @return 格式化后的日期字符 */ public String parseDateToStr(String dateFormat, Object val) { if (val == null) { return ""; } String str; if (val instanceof Date) { str = DateUtils.parseDateToStr(dateFormat, (Date) val); } else if (val instanceof LocalDateTime) { str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); } else if (val instanceof LocalDate) { str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); } else { str = val.toString(); } return str; } /** * 是否有对象的子列表 */ public boolean isSubList() { return !StringUtils.isEmpty(subFieldsMap); } /** * 是否有对象的子列表,集合不为空 */ public boolean isSubListValue(T vo) { return !StringUtils.isEmpty(subFieldsMap) && getListCellValue(vo) > 0; } /** * 获取集合的值 */ public int getListCellValue(Object obj) { Collection value; int max = 0; try { for (String s : subMethods.keySet()) { value = (Collection) subMethods.get(s).invoke(obj); if (value.size() > max) { max = value.size(); } } } catch (Exception e) { return 0; } return max; } /** * 获取对象的子列表方法 * * @param name 名称 * @param pojoClass 类对象 * @return 子列表方法 */ public Method getSubMethod(String name, Class pojoClass) { StringBuffer getMethodName = new StringBuffer("get"); getMethodName.append(name.substring(0, 1).toUpperCase()); getMethodName.append(name.substring(1)); Method method = null; try { method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); } catch (Exception e) { log.error("获取对象异常{}", e.getMessage()); } return method; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java ================================================ package com.ruoyi.common.utils.reflect; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.Date; import org.apache.commons.lang3.Validate; import org.apache.poi.ss.usermodel.DateUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; /** * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. * * @author ruoyi */ @SuppressWarnings("rawtypes") public class ReflectUtils { private static final String SETTER_PREFIX = "set"; private static final String GETTER_PREFIX = "get"; private static final String CGLIB_CLASS_SEPARATOR = "$$"; private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); /** * 调用Getter方法. * 支持多级,如:对象名.对象名.方法 */ @SuppressWarnings("unchecked") public static E invokeGetter(Object obj, String propertyName) { Object object = obj; for (String name : StringUtils.split(propertyName, ".")) { String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); } return (E) object; } /** * 调用Setter方法, 仅匹配方法名。 * 支持多级,如:对象名.对象名.方法 */ public static void invokeSetter(Object obj, String propertyName, E value) { Object object = obj; String[] names = StringUtils.split(propertyName, "."); for (int i = 0; i < names.length; i++) { if (i < names.length - 1) { String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); } else { String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); invokeMethodByName(object, setterMethodName, new Object[] { value }); } } } /** * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. */ @SuppressWarnings("unchecked") public static E getFieldValue(final Object obj, final String fieldName) { Field field = getAccessibleField(obj, fieldName); if (field == null) { logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); return null; } E result = null; try { result = (E) field.get(obj); } catch (IllegalAccessException e) { logger.error("不可能抛出的异常{}", e.getMessage()); } return result; } /** * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. */ public static void setFieldValue(final Object obj, final String fieldName, final E value) { Field field = getAccessibleField(obj, fieldName); if (field == null) { // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); return; } try { field.set(obj, value); } catch (IllegalAccessException e) { logger.error("不可能抛出的异常: {}", e.getMessage()); } } /** * 直接调用对象方法, 无视private/protected修饰符. * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. * 同时匹配方法名+参数类型, */ @SuppressWarnings("unchecked") public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, final Object[] args) { if (obj == null || methodName == null) { return null; } Method method = getAccessibleMethod(obj, methodName, parameterTypes); if (method == null) { logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); return null; } try { return (E) method.invoke(obj, args); } catch (Exception e) { String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; throw convertReflectionExceptionToUnchecked(msg, e); } } /** * 直接调用对象方法, 无视private/protected修饰符, * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. * 只匹配函数名,如果有多个同名函数调用第一个。 */ @SuppressWarnings("unchecked") public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) { Method method = getAccessibleMethodByName(obj, methodName, args.length); if (method == null) { // 如果为空不报错,直接返回空。 logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); return null; } try { // 类型转换(将参数数据类型转换为目标方法参数类型) Class[] cs = method.getParameterTypes(); for (int i = 0; i < cs.length; i++) { if (args[i] != null && !args[i].getClass().equals(cs[i])) { if (cs[i] == String.class) { args[i] = Convert.toStr(args[i]); if (StringUtils.endsWith((String) args[i], ".0")) { args[i] = StringUtils.substringBefore((String) args[i], ".0"); } } else if (cs[i] == Integer.class) { args[i] = Convert.toInt(args[i]); } else if (cs[i] == Long.class) { args[i] = Convert.toLong(args[i]); } else if (cs[i] == Double.class) { args[i] = Convert.toDouble(args[i]); } else if (cs[i] == Float.class) { args[i] = Convert.toFloat(args[i]); } else if (cs[i] == Date.class) { if (args[i] instanceof String) { args[i] = DateUtils.parseDate(args[i]); } else { args[i] = DateUtil.getJavaDate((Double) args[i]); } } else if (cs[i] == boolean.class || cs[i] == Boolean.class) { args[i] = Convert.toBool(args[i]); } else if (cs[i] == BigDecimal.class) { args[i] = Convert.toBigDecimal(args[i]); } } } return (E) method.invoke(obj, args); } catch (Exception e) { String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; throw convertReflectionExceptionToUnchecked(msg, e); } } /** * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. * 如向上转型到Object仍无法找到, 返回null. */ public static Field getAccessibleField(final Object obj, final String fieldName) { // 为空不报错。直接返回 null if (obj == null) { return null; } Validate.notBlank(fieldName, "fieldName can't be blank"); for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { try { Field field = superClass.getDeclaredField(fieldName); makeAccessible(field); return field; } catch (NoSuchFieldException e) { continue; } } return null; } /** * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. * 如向上转型到Object仍无法找到, 返回null. * 匹配函数名+参数类型。 * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) */ public static Method getAccessibleMethod(final Object obj, final String methodName, final Class... parameterTypes) { // 为空不报错。直接返回 null if (obj == null) { return null; } Validate.notBlank(methodName, "methodName can't be blank"); for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { try { Method method = searchType.getDeclaredMethod(methodName, parameterTypes); makeAccessible(method); return method; } catch (NoSuchMethodException e) { continue; } } return null; } /** * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. * 如向上转型到Object仍无法找到, 返回null. * 只匹配函数名。 * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) */ public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) { // 为空不报错。直接返回 null if (obj == null) { return null; } Validate.notBlank(methodName, "methodName can't be blank"); for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { Method[] methods = searchType.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) { makeAccessible(method); return method; } } } return null; } /** * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 */ @SuppressWarnings("deprecation") public static void makeAccessible(Method method) { if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { method.setAccessible(true); } } /** * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 */ @SuppressWarnings("deprecation") public static void makeAccessible(Field field) { if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { field.setAccessible(true); } } /** * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 * 如无法找到, 返回Object.class. */ @SuppressWarnings("unchecked") public static Class getClassGenricType(final Class clazz) { return getClassGenricType(clazz, 0); } /** * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. * 如无法找到, 返回Object.class. */ public static Class getClassGenricType(final Class clazz, final int index) { Type genType = clazz.getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); return Object.class; } Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); if (index >= params.length || index < 0) { logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + params.length); return Object.class; } if (!(params[index] instanceof Class)) { logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); return Object.class; } return (Class) params[index]; } public static Class getUserClass(Object instance) { if (instance == null) { throw new RuntimeException("Instance must not be null"); } Class clazz = instance.getClass(); if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { Class superClass = clazz.getSuperclass(); if (superClass != null && !Object.class.equals(superClass)) { return superClass; } } return clazz; } /** * 将反射时的checked exception转换为unchecked exception. */ public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) { if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException || e instanceof NoSuchMethodException) { return new IllegalArgumentException(msg, e); } else if (e instanceof InvocationTargetException) { return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); } return new RuntimeException(msg, e); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/security/CipherUtils.java ================================================ package com.ruoyi.common.utils.security; import java.security.Key; import java.security.NoSuchAlgorithmException; import javax.crypto.KeyGenerator; /** * 对称密钥密码算法工具类 * * @author ruoyi */ public class CipherUtils { /** * 生成随机秘钥 * * @param keyBitSize 字节大小 * @param algorithmName 算法名称 * @return 创建密匙 */ public static Key generateNewKey(int keyBitSize, String algorithmName) { KeyGenerator kg; try { kg = KeyGenerator.getInstance(algorithmName); } catch (NoSuchAlgorithmException e) { String msg = "Unable to acquire " + algorithmName + " algorithm. This is required to function."; throw new IllegalStateException(msg, e); } kg.init(keyBitSize); return kg.generateKey(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/security/Md5Utils.java ================================================ package com.ruoyi.common.utils.security; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Md5加密方法 * * @author ruoyi */ public class Md5Utils { private static final Logger log = LoggerFactory.getLogger(Md5Utils.class); private static byte[] md5(String s) { MessageDigest algorithm; try { algorithm = MessageDigest.getInstance("MD5"); algorithm.reset(); algorithm.update(s.getBytes("UTF-8")); byte[] messageDigest = algorithm.digest(); return messageDigest; } catch (Exception e) { log.error("MD5 Error...", e); } return null; } private static final String toHex(byte hash[]) { if (hash == null) { return null; } StringBuffer buf = new StringBuffer(hash.length * 2); int i; for (i = 0; i < hash.length; i++) { if ((hash[i] & 0xff) < 0x10) { buf.append("0"); } buf.append(Long.toString(hash[i] & 0xff, 16)); } return buf.toString(); } public static String hash(String s) { try { return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); } catch (Exception e) { log.error("not supported charset...{}", e); return s; } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/security/PermissionUtils.java ================================================ package com.ruoyi.common.utils.security; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.PermissionConstants; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.StringUtils; /** * permission 工具类 * * @author ruoyi */ public class PermissionUtils { private static final Logger log = LoggerFactory.getLogger(PermissionUtils.class); /** * 查看数据的权限 */ public static final String VIEW_PERMISSION = "no.view.permission"; /** * 创建数据的权限 */ public static final String CREATE_PERMISSION = "no.create.permission"; /** * 修改数据的权限 */ public static final String UPDATE_PERMISSION = "no.update.permission"; /** * 删除数据的权限 */ public static final String DELETE_PERMISSION = "no.delete.permission"; /** * 导出数据的权限 */ public static final String EXPORT_PERMISSION = "no.export.permission"; /** * 其他数据的权限 */ public static final String PERMISSION = "no.permission"; /** * 权限错误消息提醒 * * @param permissionsStr 错误信息 * @return 提示信息 */ public static String getMsg(String permissionsStr) { String permission = StringUtils.substringBetween(permissionsStr, "[", "]"); String msg = MessageUtils.message(PERMISSION, permission); if (StringUtils.endsWithIgnoreCase(permission, PermissionConstants.ADD_PERMISSION)) { msg = MessageUtils.message(CREATE_PERMISSION, permission); } else if (StringUtils.endsWithIgnoreCase(permission, PermissionConstants.EDIT_PERMISSION)) { msg = MessageUtils.message(UPDATE_PERMISSION, permission); } else if (StringUtils.endsWithIgnoreCase(permission, PermissionConstants.REMOVE_PERMISSION)) { msg = MessageUtils.message(DELETE_PERMISSION, permission); } else if (StringUtils.endsWithIgnoreCase(permission, PermissionConstants.EXPORT_PERMISSION)) { msg = MessageUtils.message(EXPORT_PERMISSION, permission); } else if (StringUtils.endsWithAny(permission, new String[] { PermissionConstants.VIEW_PERMISSION, PermissionConstants.LIST_PERMISSION })) { msg = MessageUtils.message(VIEW_PERMISSION, permission); } return msg; } /** * 返回用户属性值 * * @param property 属性名称 * @return 用户属性值 */ public static Object getPrincipalProperty(String property) { Subject subject = SecurityUtils.getSubject(); if (subject != null) { Object principal = subject.getPrincipal(); try { BeanInfo bi = Introspector.getBeanInfo(principal.getClass()); for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { if (pd.getName().equals(property) == true) { return pd.getReadMethod().invoke(principal, (Object[]) null); } } } catch (Exception e) { log.error("Error reading property [{}] from principal of type [{}]", property, principal.getClass().getName()); } } return null; } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java ================================================ package com.ruoyi.common.utils.spring; import org.springframework.aop.framework.AopContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import com.ruoyi.common.utils.StringUtils; /** * spring工具类 方便在非spring管理环境中获取bean * * @author ruoyi */ @Component public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { /** Spring应用上下文环境 */ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws org.springframework.beans.BeansException * */ @SuppressWarnings("unchecked") public static T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws org.springframework.beans.BeansException * */ public static T getBean(Class clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static Class getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } /** * 获取当前的环境配置,无配置返回null * * @return 当前的环境配置 */ public static String[] getActiveProfiles() { return applicationContext.getEnvironment().getActiveProfiles(); } /** * 获取当前的环境配置,当有多个环境配置时,只获取第一个 * * @return 当前的环境配置 */ public static String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; } /** * 获取配置文件中的值 * * @param key 配置文件的key * @return 当前的配置文件的值 * */ public static String getRequiredProperty(String key) { return applicationContext.getEnvironment().getRequiredProperty(key); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java ================================================ package com.ruoyi.common.utils.sql; import com.ruoyi.common.exception.UtilException; import com.ruoyi.common.utils.StringUtils; /** * sql操作工具类 * * @author ruoyi */ public class SqlUtil { /** * 定义常用的 sql关键字 */ public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|information_schema|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; /** * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) */ public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; /** * 限制orderBy最大长度 */ private static final int ORDER_BY_MAX_LENGTH = 500; /** * 检查字符,防止注入绕过 */ public static String escapeOrderBySql(String value) { if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { throw new UtilException("参数不符合规范,不能进行查询"); } if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH) { throw new UtilException("参数已超过最大限制,不能进行查询"); } return value; } /** * 验证 order by 语法是否符合规范 */ public static boolean isValidOrderBySql(String value) { return value.matches(SQL_PATTERN); } /** * SQL关键字检查 */ public static void filterKeyword(String value) { if (StringUtils.isEmpty(value)) { return; } String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); for (String sqlKeyword : sqlKeywords) { if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { throw new UtilException("参数存在SQL注入风险"); } } } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java ================================================ package com.ruoyi.common.utils.uuid; /** * ID生成器工具类 * * @author ruoyi */ public class IdUtils { /** * 获取随机UUID * * @return 随机UUID */ public static String randomUUID() { return UUID.randomUUID().toString(); } /** * 简化的UUID,去掉了横线 * * @return 简化的UUID,去掉了横线 */ public static String simpleUUID() { return UUID.randomUUID().toString(true); } /** * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID * * @return 随机UUID */ public static String fastUUID() { return UUID.fastUUID().toString(); } /** * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID * * @return 简化的UUID,去掉了横线 */ public static String fastSimpleUUID() { return UUID.fastUUID().toString(true); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/Seq.java ================================================ package com.ruoyi.common.utils.uuid; import java.util.concurrent.atomic.AtomicInteger; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; /** * @author ruoyi 序列生成类 */ public class Seq { // 通用序列类型 public static final String commSeqType = "COMMON"; // 上传序列类型 public static final String uploadSeqType = "UPLOAD"; // 通用接口序列数 private static AtomicInteger commSeq = new AtomicInteger(1); // 上传接口序列数 private static AtomicInteger uploadSeq = new AtomicInteger(1); // 机器标识 private static final String machineCode = "A"; /** * 获取通用序列号 * * @return 序列值 */ public static String getId() { return getId(commSeqType); } /** * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 * * @return 序列值 */ public static String getId(String type) { AtomicInteger atomicInt = commSeq; if (uploadSeqType.equals(type)) { atomicInt = uploadSeq; } return getId(atomicInt, 3); } /** * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 * * @param atomicInt 序列数 * @param length 数值长度 * @return 序列值 */ public static String getId(AtomicInteger atomicInt, int length) { String result = DateUtils.dateTimeNow(); result += machineCode; result += getSeq(atomicInt, length); return result; } /** * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 * * @return 序列值 */ private synchronized static String getSeq(AtomicInteger atomicInt, int length) { // 先取值再+1 int value = atomicInt.getAndIncrement(); // 如果更新后值>=10 的 (length)幂次方则重置为1 int maxSeq = (int) Math.pow(10, length); if (atomicInt.get() >= maxSeq) { atomicInt.set(1); } // 转字符串,用0左补齐 return StringUtils.padl(value, length); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java ================================================ package com.ruoyi.common.utils.uuid; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import com.ruoyi.common.exception.UtilException; /** * 提供通用唯一识别码(universally unique identifier)(UUID)实现 * * @author ruoyi */ public final class UUID implements java.io.Serializable, Comparable { private static final long serialVersionUID = -1185015143654744140L; /** * SecureRandom 的单例 * */ private static class Holder { static final SecureRandom numberGenerator = getSecureRandom(); } /** 此UUID的最高64有效位 */ private final long mostSigBits; /** 此UUID的最低64有效位 */ private final long leastSigBits; /** * 私有构造 * * @param data 数据 */ private UUID(byte[] data) { long msb = 0; long lsb = 0; assert data.length == 16 : "data must be 16 bytes in length"; for (int i = 0; i < 8; i++) { msb = (msb << 8) | (data[i] & 0xff); } for (int i = 8; i < 16; i++) { lsb = (lsb << 8) | (data[i] & 0xff); } this.mostSigBits = msb; this.leastSigBits = lsb; } /** * 使用指定的数据构造新的 UUID。 * * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 */ public UUID(long mostSigBits, long leastSigBits) { this.mostSigBits = mostSigBits; this.leastSigBits = leastSigBits; } /** * 获取类型 4(伪随机生成的)UUID 的静态工厂。 * * @return 随机生成的 {@code UUID} */ public static UUID fastUUID() { return randomUUID(false); } /** * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 * * @return 随机生成的 {@code UUID} */ public static UUID randomUUID() { return randomUUID(true); } /** * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 * * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 * @return 随机生成的 {@code UUID} */ public static UUID randomUUID(boolean isSecure) { final Random ng = isSecure ? Holder.numberGenerator : getRandom(); byte[] randomBytes = new byte[16]; ng.nextBytes(randomBytes); randomBytes[6] &= 0x0f; /* clear version */ randomBytes[6] |= 0x40; /* set to version 4 */ randomBytes[8] &= 0x3f; /* clear variant */ randomBytes[8] |= 0x80; /* set to IETF variant */ return new UUID(randomBytes); } /** * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 * * @param name 用于构造 UUID 的字节数组。 * * @return 根据指定数组生成的 {@code UUID} */ public static UUID nameUUIDFromBytes(byte[] name) { MessageDigest md; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException nsae) { throw new InternalError("MD5 not supported"); } byte[] md5Bytes = md.digest(name); md5Bytes[6] &= 0x0f; /* clear version */ md5Bytes[6] |= 0x30; /* set to version 3 */ md5Bytes[8] &= 0x3f; /* clear variant */ md5Bytes[8] |= 0x80; /* set to IETF variant */ return new UUID(md5Bytes); } /** * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 * * @param name 指定 {@code UUID} 字符串 * @return 具有指定值的 {@code UUID} * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 * */ public static UUID fromString(String name) { String[] components = name.split("-"); if (components.length != 5) { throw new IllegalArgumentException("Invalid UUID string: " + name); } for (int i = 0; i < 5; i++) { components[i] = "0x" + components[i]; } long mostSigBits = Long.decode(components[0]).longValue(); mostSigBits <<= 16; mostSigBits |= Long.decode(components[1]).longValue(); mostSigBits <<= 16; mostSigBits |= Long.decode(components[2]).longValue(); long leastSigBits = Long.decode(components[3]).longValue(); leastSigBits <<= 48; leastSigBits |= Long.decode(components[4]).longValue(); return new UUID(mostSigBits, leastSigBits); } /** * 返回此 UUID 的 128 位值中的最低有效 64 位。 * * @return 此 UUID 的 128 位值中的最低有效 64 位。 */ public long getLeastSignificantBits() { return leastSigBits; } /** * 返回此 UUID 的 128 位值中的最高有效 64 位。 * * @return 此 UUID 的 128 位值中最高有效 64 位。 */ public long getMostSignificantBits() { return mostSigBits; } /** * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 *

                    * 版本号具有以下含意: *

                      *
                    • 1 基于时间的 UUID *
                    • 2 DCE 安全 UUID *
                    • 3 基于名称的 UUID *
                    • 4 随机生成的 UUID *
                    * * @return 此 {@code UUID} 的版本号 */ public int version() { // Version is bits masked by 0x000000000000F000 in MS long return (int) ((mostSigBits >> 12) & 0x0f); } /** * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 *

                    * 变体号具有以下含意: *

                      *
                    • 0 为 NCS 向后兼容保留 *
                    • 2 IETF RFC 4122(Leach-Salz), 用于此类 *
                    • 6 保留,微软向后兼容 *
                    • 7 保留供以后定义使用 *
                    * * @return 此 {@code UUID} 相关联的变体号 */ public int variant() { // This field is composed of a varying number of bits. // 0 - - Reserved for NCS backward compatibility // 1 0 - The IETF aka Leach-Salz variant (used by this class) // 1 1 0 Reserved, Microsoft backward compatibility // 1 1 1 Reserved for future definition. return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); } /** * 与此 UUID 相关联的时间戳值。 * *

                    * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
                    * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 * *

                    * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
                    * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 * * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 */ public long timestamp() throws UnsupportedOperationException { checkTimeBase(); return (mostSigBits & 0x0FFFL) << 48// | ((mostSigBits >> 16) & 0x0FFFFL) << 32// | mostSigBits >>> 32; } /** * 与此 UUID 相关联的时钟序列值。 * *

                    * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 *

                    * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 * UnsupportedOperationException。 * * @return 此 {@code UUID} 的时钟序列 * * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 */ public int clockSequence() throws UnsupportedOperationException { checkTimeBase(); return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); } /** * 与此 UUID 相关的节点值。 * *

                    * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 *

                    * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
                    * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 * * @return 此 {@code UUID} 的节点值 * * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 */ public long node() throws UnsupportedOperationException { checkTimeBase(); return leastSigBits & 0x0000FFFFFFFFFFFFL; } /** * 返回此{@code UUID} 的字符串表现形式。 * *

                    * UUID 的字符串表示形式由此 BNF 描述: * *

                         * {@code
                         * UUID                   = ----
                         * time_low               = 4*
                         * time_mid               = 2*
                         * time_high_and_version  = 2*
                         * variant_and_sequence   = 2*
                         * node                   = 6*
                         * hexOctet               = 
                         * hexDigit               = [0-9a-fA-F]
                         * }
                         * 
                    * * * * @return 此{@code UUID} 的字符串表现形式 * @see #toString(boolean) */ @Override public String toString() { return toString(false); } /** * 返回此{@code UUID} 的字符串表现形式。 * *

                    * UUID 的字符串表示形式由此 BNF 描述: * *

                         * {@code
                         * UUID                   = ----
                         * time_low               = 4*
                         * time_mid               = 2*
                         * time_high_and_version  = 2*
                         * variant_and_sequence   = 2*
                         * node                   = 6*
                         * hexOctet               = 
                         * hexDigit               = [0-9a-fA-F]
                         * }
                         * 
                    * * * * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 * @return 此{@code UUID} 的字符串表现形式 */ public String toString(boolean isSimple) { final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); // time_low builder.append(digits(mostSigBits >> 32, 8)); if (!isSimple) { builder.append('-'); } // time_mid builder.append(digits(mostSigBits >> 16, 4)); if (!isSimple) { builder.append('-'); } // time_high_and_version builder.append(digits(mostSigBits, 4)); if (!isSimple) { builder.append('-'); } // variant_and_sequence builder.append(digits(leastSigBits >> 48, 4)); if (!isSimple) { builder.append('-'); } // node builder.append(digits(leastSigBits, 12)); return builder.toString(); } /** * 返回此 UUID 的哈希码。 * * @return UUID 的哈希码值。 */ @Override public int hashCode() { long hilo = mostSigBits ^ leastSigBits; return ((int) (hilo >> 32)) ^ (int) hilo; } /** * 将此对象与指定对象比较。 *

                    * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 * * @param obj 要与之比较的对象 * * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} */ @Override public boolean equals(Object obj) { if ((null == obj) || (obj.getClass() != UUID.class)) { return false; } UUID id = (UUID) obj; return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); } // Comparison Operations /** * 将此 UUID 与指定的 UUID 比较。 * *

                    * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 * * @param val 与此 UUID 比较的 UUID * * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 * */ @Override public int compareTo(UUID val) { // The ordering is intentionally set up so that the UUIDs // can simply be numerically compared as two numbers return (this.mostSigBits < val.mostSigBits ? -1 : // (this.mostSigBits > val.mostSigBits ? 1 : // (this.leastSigBits < val.leastSigBits ? -1 : // (this.leastSigBits > val.leastSigBits ? 1 : // 0)))); } // ------------------------------------------------------------------------------------------------------------------- // Private method start /** * 返回指定数字对应的hex值 * * @param val 值 * @param digits 位 * @return 值 */ private static String digits(long val, int digits) { long hi = 1L << (digits * 4); return Long.toHexString(hi | (val & (hi - 1))).substring(1); } /** * 检查是否为time-based版本UUID */ private void checkTimeBase() { if (version() != 1) { throw new UnsupportedOperationException("Not a time-based UUID"); } } /** * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) * * @return {@link SecureRandom} */ public static SecureRandom getSecureRandom() { try { return SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { throw new UtilException(e); } } /** * 获取随机数生成器对象
                    * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 * * @return {@link ThreadLocalRandom} */ public static ThreadLocalRandom getRandom() { return ThreadLocalRandom.current(); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/xss/Xss.java ================================================ package com.ruoyi.common.xss; import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义xss校验注解 * * @author ruoyi */ @Retention(RetentionPolicy.RUNTIME) @Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) @Constraint(validatedBy = { XssValidator.class }) public @interface Xss { String message() default "不允许任何脚本运行"; Class[] groups() default {}; Class[] payload() default {}; } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/xss/XssFilter.java ================================================ package com.ruoyi.common.xss; import java.io.IOException; import java.util.ArrayList; import java.util.List; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import com.ruoyi.common.utils.StringUtils; /** * 防止XSS攻击的过滤器 * * @author ruoyi */ public class XssFilter implements Filter { /** * 排除链接 */ public List excludes = new ArrayList<>(); @Override public void init(FilterConfig filterConfig) throws ServletException { String tempExcludes = filterConfig.getInitParameter("excludes"); if (StringUtils.isNotEmpty(tempExcludes)) { String[] urls = tempExcludes.split(","); for (String url : urls) { excludes.add(url); } } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (handleExcludeURL(req, resp)) { chain.doFilter(request, response); return; } XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); chain.doFilter(xssRequest, response); } private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) { String url = request.getServletPath(); String method = request.getMethod(); // GET DELETE 不过滤 if (method == null || method.matches("GET") || method.matches("DELETE")) { return true; } return StringUtils.matches(url, excludes); } @Override public void destroy() { } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java ================================================ package com.ruoyi.common.xss; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import com.ruoyi.common.utils.html.EscapeUtil; /** * XSS过滤处理 * * @author ruoyi */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { /** * @param request */ public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values != null) { int length = values.length; String[] escapseValues = new String[length]; for (int i = 0; i < length; i++) { // 防xss攻击和过滤前后空格 escapseValues[i] = EscapeUtil.clean(values[i]).trim(); } return escapseValues; } return super.getParameterValues(name); } } ================================================ FILE: ruoyi-common/src/main/java/com/ruoyi/common/xss/XssValidator.java ================================================ package com.ruoyi.common.xss; import com.ruoyi.common.utils.StringUtils; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 自定义xss校验注解实现 * * @author ruoyi */ public class XssValidator implements ConstraintValidator { private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; @Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { if (StringUtils.isBlank(value)) { return true; } return !containsHtml(value); } public static boolean containsHtml(String value) { StringBuilder sHtml = new StringBuilder(); Pattern pattern = Pattern.compile(HTML_PATTERN); Matcher matcher = pattern.matcher(value); while (matcher.find()) { sHtml.append(matcher.group()); } return pattern.matcher(sHtml).matches(); } } ================================================ FILE: ruoyi-framework/pom.xml ================================================ ruoyi com.ruoyi 4.8.2 4.0.0 ruoyi-framework framework框架核心 org.springframework.boot spring-boot-starter-webmvc org.springframework.boot spring-boot-starter-aspectj com.alibaba druid-spring-boot-4-starter pro.fessional kaptcha servlet-api javax.servlet org.apache.shiro shiro-spring jakarta org.apache.shiro shiro-core jakarta org.apache.shiro shiro-web jakarta com.github.theborakompanioni thymeleaf-extras-shiro com.github.oshi oshi-core com.ruoyi ruoyi-system ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java ================================================ package com.ruoyi.framework.aspectj; import java.util.ArrayList; import java.util.List; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.context.PermissionContextHolder; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; /** * 数据过滤处理 * * @author ruoyi */ @Aspect @Component public class DataScopeAspect { /** * 数据权限过滤关键字 */ public static final String DATA_SCOPE = "dataScope"; @Before("@annotation(controllerDataScope)") public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable { clearDataScope(point); handleDataScope(point, controllerDataScope); } protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) { // 获取当前的用户 SysUser currentUser = ShiroUtils.getSysUser(); if (currentUser != null) { // 如果是超级管理员,则不过滤数据 if (!currentUser.isAdmin()) { String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext()); dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), controllerDataScope.userAlias(), permission); } } } /** * 数据范围过滤 * * @param joinPoint 切点 * @param user 用户 * @param deptAlias 部门别名 * @param userAlias 用户别名 * @param permission 权限字符 */ public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission) { StringBuilder sqlString = new StringBuilder(); List conditions = new ArrayList(); List scopeCustomIds = new ArrayList(); user.getRoles().forEach(role -> { if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && (StringUtils.isEmpty(permission) || StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))) { scopeCustomIds.add(Convert.toStr(role.getRoleId())); } }); for (SysRole role : user.getRoles()) { String dataScope = role.getDataScope(); if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE)) { continue; } if (StringUtils.isNotEmpty(permission) && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { continue; } if (Constants.Dept.DATA_SCOPE_ALL.equals(dataScope)) { sqlString = new StringBuilder(); conditions.add(dataScope); break; } else if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(dataScope)) { if (scopeCustomIds.size() > 1) { // 多个自定数据权限使用in查询,避免多次拼接。 sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, String.join(",", scopeCustomIds))); } else { sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); } } else if (Constants.Dept.DATA_SCOPE_DEPT.equals(dataScope)) { sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); } else if (Constants.Dept.DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { sqlString.append(StringUtils.format(" OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId())); } else if (Constants.Dept.DATA_SCOPE_SELF.equals(dataScope)) { if (StringUtils.isNotBlank(userAlias)) { sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); } else { // 数据权限为仅本人且没有userAlias别名不查询任何数据 sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); } } conditions.add(dataScope); } // 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据 if (StringUtils.isEmpty(conditions)) { sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); } if (StringUtils.isNotBlank(sqlString.toString())) { Object params = joinPoint.getArgs()[0]; if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { BaseEntity baseEntity = (BaseEntity) params; baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); } } } /** * 拼接权限sql前先清空params.dataScope参数防止注入 */ private void clearDataScope(final JoinPoint joinPoint) { Object params = joinPoint.getArgs()[0]; if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { BaseEntity baseEntity = (BaseEntity) params; baseEntity.getParams().put(DATA_SCOPE, ""); } } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java ================================================ package com.ruoyi.framework.aspectj; import java.util.Objects; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import com.ruoyi.common.annotation.DataSource; import com.ruoyi.common.config.datasource.DynamicDataSourceContextHolder; import com.ruoyi.common.utils.StringUtils; /** * 多数据源处理 * * @author ruoyi */ @Aspect @Order(1) @Component public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)" + "|| @within(com.ruoyi.common.annotation.DataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { DataSource dataSource = getDataSource(point); if (StringUtils.isNotNull(dataSource)) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } } /** * 获取需要切换的数据源 */ public DataSource getDataSource(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); if (Objects.nonNull(dataSource)) { return dataSource; } return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java ================================================ package com.ruoyi.framework.aspectj; import java.util.Collection; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.ArrayUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.NamedThreadLocal; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; import org.springframework.web.multipart.MultipartFile; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.support.spring.PropertyPreFilters; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.enums.BusinessStatus; import com.ruoyi.common.utils.ExceptionUtil; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.system.domain.SysOperLog; /** * 操作日志记录处理 * * @author ruoyi */ @Aspect @Component public class LogAspect { private static final Logger log = LoggerFactory.getLogger(LogAspect.class); /** 排除敏感属性字段 */ public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; /** 计算操作消耗时间 */ private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); /** 参数最大长度限制 */ private static final int PARAM_MAX_LENGTH = 2000; /** * 处理请求前执行 */ @Before(value = "@annotation(controllerLog)") public void doBefore(JoinPoint joinPoint, Log controllerLog) { TIME_THREADLOCAL.set(System.currentTimeMillis()); } /** * 处理完请求后执行 * * @param joinPoint 切点 */ @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { handleLog(joinPoint, controllerLog, null, jsonResult); } /** * 拦截异常操作 * * @param joinPoint 切点 * @param e 异常 */ @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { handleLog(joinPoint, controllerLog, e, null); } protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { try { // 获取当前的用户 SysUser currentUser = ShiroUtils.getSysUser(); // *========数据库日志=========*// SysOperLog operLog = new SysOperLog(); operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); // 请求的地址 String ip = ShiroUtils.getIp(); operLog.setOperIp(ip); operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); if (currentUser != null) { operLog.setOperName(currentUser.getLoginName()); if (StringUtils.isNotNull(currentUser.getDept()) && StringUtils.isNotEmpty(currentUser.getDept().getDeptName())) { operLog.setDeptName(currentUser.getDept().getDeptName()); } } if (e != null) { operLog.setStatus(BusinessStatus.FAIL.ordinal()); operLog.setErrorMsg(StringUtils.substring(Convert.toStr(e.getMessage(), ExceptionUtil.getExceptionMessage(e)), 0, 2000)); } // 设置方法名称 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); operLog.setMethod(className + "." + methodName + "()"); // 设置请求方式 operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); // 处理设置注解上的参数 getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); // 设置消耗时间 operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); // 保存数据库 AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } catch (Exception exp) { // 记录本地异常日志 log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); } finally { TIME_THREADLOCAL.remove(); } } /** * 获取注解中对方法的描述信息 用于Controller层注解 * * @param log 日志 * @param operLog 操作日志 * @throws Exception */ public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception { // 设置action动作 operLog.setBusinessType(log.businessType().ordinal()); // 设置标题 operLog.setTitle(log.title()); // 设置操作人类别 operLog.setOperatorType(log.operatorType().ordinal()); // 是否需要保存request,参数和值 if (log.isSaveRequestData()) { // 获取参数的信息,传入到数据库中。 setRequestValue(joinPoint, operLog, log.excludeParamNames()); } // 是否需要保存response,参数和值 if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) { operLog.setJsonResult(StringUtils.substring(JSONObject.toJSONString(jsonResult), 0, 2000)); } } /** * 获取请求的参数,放到log中 * * @param operLog 操作日志 * @throws Exception 异常 */ private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception { Map map = ServletUtils.getRequest().getParameterMap(); if (StringUtils.isNotEmpty(map)) { String params = JSONObject.toJSONString(map, excludePropertyPreFilter(excludeParamNames)); operLog.setOperParam(StringUtils.substring(params, 0, PARAM_MAX_LENGTH)); } else { Object args = joinPoint.getArgs(); if (StringUtils.isNotNull(args)) { String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); operLog.setOperParam(params); } } } /** * 忽略敏感属性 */ public PropertyPreFilters.MySimplePropertyPreFilter excludePropertyPreFilter(String[] excludeParamNames) { return new PropertyPreFilters().addFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); } /** * 参数拼装 */ private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { StringBuilder params = new StringBuilder(); if (paramsArray != null && paramsArray.length > 0) { for (Object o : paramsArray) { if (StringUtils.isNotNull(o) && !isFilterObject(o)) { try { Object jsonObj = JSONObject.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); params.append(jsonObj).append(" "); if (params.length() >= PARAM_MAX_LENGTH) { return StringUtils.substring(params.toString(), 0, PARAM_MAX_LENGTH); } } catch (Exception e) { log.error("请求参数拼装异常 msg:{}, 参数:{}", e.getMessage(), paramsArray, e); } } } } return params.toString(); } /** * 判断是否需要过滤的对象。 * * @param o 对象信息。 * @return 如果是需要过滤的对象,则返回true;否则返回false。 */ @SuppressWarnings("rawtypes") public boolean isFilterObject(final Object o) { Class clazz = o.getClass(); if (clazz.isArray()) { return clazz.getComponentType().isAssignableFrom(MultipartFile.class); } else if (Collection.class.isAssignableFrom(clazz)) { Collection collection = (Collection) o; for (Object value : collection) { return value instanceof MultipartFile; } } else if (Map.class.isAssignableFrom(clazz)) { Map map = (Map) o; for (Object value : map.entrySet()) { Map.Entry entry = (Map.Entry) value; return entry.getValue() instanceof MultipartFile; } } return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/PermissionsAspect.java ================================================ package com.ruoyi.framework.aspectj; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import com.ruoyi.common.core.context.PermissionContextHolder; import com.ruoyi.common.utils.StringUtils; /** * 自定义权限拦截器,将权限字符串放到当前请求中以便用于多个角色匹配符合要求的权限 * * @author ruoyi */ @Aspect @Component public class PermissionsAspect { @Before("@annotation(controllerRequiresPermissions)") public void doBefore(JoinPoint point, RequiresPermissions controllerRequiresPermissions) throws Throwable { handleRequiresPermissions(point, controllerRequiresPermissions); } protected void handleRequiresPermissions(final JoinPoint joinPoint, RequiresPermissions requiresPermissions) { PermissionContextHolder.setContext(StringUtils.join(requiresPermissions.value(), ",")); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java ================================================ package com.ruoyi.framework.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * 程序注解配置 * * @author ruoyi */ @Configuration // 表示通过aop框架暴露该代理对象,AopContext能够访问 @EnableAspectJAutoProxy(exposeProxy = true) // 指定要扫描的Mapper类的包的路径 @MapperScan("com.ruoyi.**.mapper") public class ApplicationConfig { } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java ================================================ package com.ruoyi.framework.config; import java.util.Properties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import static com.google.code.kaptcha.Constants.*; /** * 验证码配置 * * @author ruoyi */ @Configuration public class CaptchaConfig { @Bean(name = "captchaProducer") public DefaultKaptcha getKaptchaBean() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } @Bean(name = "captchaProducerMath") public DefaultKaptcha getKaptchaBeanMath() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); // 边框颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); // 验证码文本生成器 properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.framework.config.KaptchaTextCreator"); // 验证码文本字符间距 默认为2 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); // 验证码噪点颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); // 干扰实现类 properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java ================================================ package com.ruoyi.framework.config; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceBuilder; import com.alibaba.druid.spring.boot4.autoconfigure.properties.DruidStatProperties; import com.alibaba.druid.util.Utils; import com.ruoyi.common.enums.DataSourceType; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.framework.config.properties.DruidProperties; import com.ruoyi.framework.datasource.DynamicDataSource; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; /** * druid 配置多数据源 * * @author ruoyi */ @Configuration public class DruidConfig { @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource masterDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean @ConfigurationProperties("spring.datasource.druid.slave") @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") public DataSource slaveDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(DataSource masterDataSource) { Map targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); return new DynamicDataSource(masterDataSource, targetDataSources); } /** * 设置数据源 * * @param targetDataSources 备选数据源集合 * @param sourceName 数据源名称 * @param beanName bean名称 */ public void setDataSource(Map targetDataSources, String sourceName, String beanName) { try { DataSource dataSource = SpringUtils.getBean(beanName); targetDataSources.put(sourceName, dataSource); } catch (Exception e) { } } /** * 去除监控页面底部的广告 */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) { // 获取web监控页面的参数 DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); // 提取common.js的配置路径 String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); final String filePath = "support/http/resources/js/common.js"; // 创建filter进行过滤 Filter filter = new Filter() { @Override public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); // 重置缓冲区,响应头不会被重置 response.resetBuffer(); // 获取common.js String text = Utils.readFromResource(filePath); // 正则替换banner, 除去底部的广告信息 text = text.replaceAll("
                    ", ""); text = text.replaceAll("powered.*?shrek.wang", ""); response.getWriter().write(text); } @Override public void destroy() { } }; FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter); registrationBean.addUrlPatterns(commonJsPattern); return registrationBean; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java ================================================ package com.ruoyi.framework.config; import java.util.HashMap; import java.util.Map; import jakarta.servlet.DispatcherType; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.xss.XssFilter; /** * Filter配置 * * @author ruoyi */ @Configuration @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") public class FilterConfig { @Value("${xss.excludes}") private String excludes; @Value("${xss.urlPatterns}") private String urlPatterns; @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean public FilterRegistrationBean xssFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setFilter(new XssFilter()); registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); registration.setName("xssFilter"); registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); Map initParameters = new HashMap(); initParameters.put("excludes", excludes); registration.setInitParameters(initParameters); return registration; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/I18nConfig.java ================================================ package com.ruoyi.framework.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.SessionLocaleResolver; import com.ruoyi.common.constant.Constants; /** * 资源文件配置加载 * * @author ruoyi */ @Configuration public class I18nConfig implements WebMvcConfigurer { @Bean public LocaleResolver localeResolver() { SessionLocaleResolver slr = new SessionLocaleResolver(); // 默认语言 slr.setDefaultLocale(Constants.DEFAULT_LOCALE); return slr; } @Bean public LocaleChangeInterceptor localeChangeInterceptor() { LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); // 参数名 lci.setParamName("lang"); return lci; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(localeChangeInterceptor()); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java ================================================ package com.ruoyi.framework.config; import java.security.SecureRandom; import java.util.Random; import com.google.code.kaptcha.text.impl.DefaultTextCreator; /** * 验证码文本生成器 * * @author ruoyi */ public class KaptchaTextCreator extends DefaultTextCreator { private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); @Override public String getText() { Integer result = 0; Random random = new SecureRandom(); int x = random.nextInt(10); int y = random.nextInt(10); StringBuilder suChinese = new StringBuilder(); int randomoperands = random.nextInt(3); if (randomoperands == 0) { result = x * y; suChinese.append(CNUMBERS[x]); suChinese.append("*"); suChinese.append(CNUMBERS[y]); } else if (randomoperands == 1) { if (!(x == 0) && y % x == 0) { result = y / x; suChinese.append(CNUMBERS[y]); suChinese.append("/"); suChinese.append(CNUMBERS[x]); } else { result = x + y; suChinese.append(CNUMBERS[x]); suChinese.append("+"); suChinese.append(CNUMBERS[y]); } } else if (randomoperands == 2) { if (x >= y) { result = x - y; suChinese.append(CNUMBERS[x]); suChinese.append("-"); suChinese.append(CNUMBERS[y]); } else { result = y - x; suChinese.append(CNUMBERS[y]); suChinese.append("-"); suChinese.append(CNUMBERS[x]); } } else { result = x + y; suChinese.append(CNUMBERS[x]); suChinese.append("+"); suChinese.append(CNUMBERS[y]); } suChinese.append("=?@" + result); return suChinese.toString(); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java ================================================ package com.ruoyi.framework.config; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import javax.sql.DataSource; import org.apache.ibatis.io.VFS; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.ClassUtils; import com.ruoyi.common.utils.StringUtils; /** * Mybatis支持*匹配扫描包 * * @author ruoyi */ @Configuration public class MyBatisConfig { @Autowired private Environment env; static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; public static String setTypeAliasesPackage(String typeAliasesPackage) { ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); List allResult = new ArrayList(); try { for (String aliasesPackage : typeAliasesPackage.split(",")) { List result = new ArrayList(); aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; Resource[] resources = resolver.getResources(aliasesPackage); if (resources != null && resources.length > 0) { MetadataReader metadataReader = null; for (Resource resource : resources) { if (resource.isReadable()) { metadataReader = metadataReaderFactory.getMetadataReader(resource); try { result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } if (result.size() > 0) { HashSet hashResult = new HashSet(result); allResult.addAll(hashResult); } } if (allResult.size() > 0) { typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); } else { throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); } } catch (IOException e) { e.printStackTrace(); } return typeAliasesPackage; } public Resource[] resolveMapperLocations(String[] mapperLocations) { ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); List resources = new ArrayList(); if (mapperLocations != null) { for (String mapperLocation : mapperLocations) { try { Resource[] mappers = resourceResolver.getResources(mapperLocation); resources.addAll(Arrays.asList(mappers)); } catch (IOException e) { // ignore } } } return resources.toArray(new Resource[resources.size()]); } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); String mapperLocations = env.getProperty("mybatis.mapperLocations"); String configLocation = env.getProperty("mybatis.configLocation"); typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); VFS.addImplClass(SpringBootVFS.class); final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); sessionFactory.setTypeAliasesPackage(typeAliasesPackage); sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); return sessionFactory.getObject(); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java ================================================ package com.ruoyi.framework.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; /** * 通用配置 * * @author ruoyi */ @Configuration public class ResourcesConfig implements WebMvcConfigurer { /** * 首页地址 */ @Value("${shiro.user.indexUrl}") private String indexUrl; @Autowired private RepeatSubmitInterceptor repeatSubmitInterceptor; /** * 默认首页的设置,当输入域名是可以自动跳转到默认指定的网页 */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("forward:" + indexUrl); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { /** 本地文件上传路径 */ registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/"); /** swagger配置 */ registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); } /** * 自定义拦截规则 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java ================================================ package com.ruoyi.framework.config; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.config.ConfigurationException; import org.apache.shiro.lang.codec.Base64; import org.apache.shiro.lang.io.ResourceUtils; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.security.CipherUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.framework.shiro.realm.UserRealm; import com.ruoyi.framework.shiro.rememberMe.CustomCookieRememberMeManager; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; import com.ruoyi.framework.shiro.session.OnlineSessionFactory; import com.ruoyi.framework.shiro.web.CustomShiroFilterFactoryBean; import com.ruoyi.framework.shiro.web.filter.LogoutFilter; import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter; import com.ruoyi.framework.shiro.web.filter.csrf.CsrfValidateFilter; import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter; import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter; import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter; import com.ruoyi.framework.shiro.web.session.OnlineWebSessionManager; import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import jakarta.servlet.Filter; /** * 权限配置加载 * * @author ruoyi */ @Configuration public class ShiroConfig { /** * Session超时时间,单位为毫秒(默认30分钟) */ @Value("${shiro.session.expireTime}") private int expireTime; /** * 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟 */ @Value("${shiro.session.validationInterval}") private int validationInterval; /** * 同一个用户最大会话数 */ @Value("${shiro.session.maxSession}") private int maxSession; /** * 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户 */ @Value("${shiro.session.kickoutAfter}") private boolean kickoutAfter; /** * 验证码开关 */ @Value("${shiro.user.captchaEnabled}") private boolean captchaEnabled; /** * 验证码类型 */ @Value("${shiro.user.captchaType}") private String captchaType; /** * 设置Cookie的域名 */ @Value("${shiro.cookie.domain}") private String domain; /** * 设置cookie的有效访问路径 */ @Value("${shiro.cookie.path}") private String path; /** * 设置HttpOnly属性 */ @Value("${shiro.cookie.httpOnly}") private boolean httpOnly; /** * 设置Cookie的过期时间,秒为单位 */ @Value("${shiro.cookie.maxAge}") private int maxAge; /** * 设置cipherKey密钥 */ @Value("${shiro.cookie.cipherKey}") private String cipherKey; /** * 登录地址 */ @Value("${shiro.user.loginUrl}") private String loginUrl; /** * 权限认证失败地址 */ @Value("${shiro.user.unauthorizedUrl}") private String unauthorizedUrl; /** * 是否开启记住我功能 */ @Value("${shiro.rememberMe.enabled: false}") private boolean rememberMe; /** * 是否开启csrf */ @Value("${csrf.enabled: false}") private boolean csrfEnabled; /** * csrf白名单链接 */ @Value("${csrf.whites: ''}") private String csrfWhites; /** * 缓存管理器 使用Ehcache实现 */ @Bean public EhCacheManager getEhCacheManager() { net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi"); EhCacheManager em = new EhCacheManager(); if (StringUtils.isNull(cacheManager)) { em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream())); return em; } else { em.setCacheManager(cacheManager); return em; } } /** * 返回配置文件流 避免ehcache配置文件一直被占用,无法完全销毁项目重新部署 */ protected InputStream getCacheManagerConfigFileInputStream() { String configFile = "classpath:ehcache/ehcache-shiro.xml"; InputStream inputStream = null; try { inputStream = ResourceUtils.getInputStreamForPath(configFile); byte[] b = IOUtils.toByteArray(inputStream); InputStream in = new ByteArrayInputStream(b); return in; } catch (IOException e) { throw new ConfigurationException( "Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e); } finally { IOUtils.closeQuietly(inputStream); } } /** * 自定义Realm */ @Bean public UserRealm userRealm(EhCacheManager cacheManager) { UserRealm userRealm = new UserRealm(); userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE); userRealm.setCacheManager(cacheManager); return userRealm; } /** * 自定义sessionDAO会话 */ @Bean public OnlineSessionDAO sessionDAO() { OnlineSessionDAO sessionDAO = new OnlineSessionDAO(); return sessionDAO; } /** * 自定义sessionFactory会话 */ @Bean public OnlineSessionFactory sessionFactory() { OnlineSessionFactory sessionFactory = new OnlineSessionFactory(); return sessionFactory; } /** * 会话管理器 */ @Bean public OnlineWebSessionManager sessionManager() { OnlineWebSessionManager manager = new OnlineWebSessionManager(); // 加入缓存管理器 manager.setCacheManager(getEhCacheManager()); // 删除过期的session manager.setDeleteInvalidSessions(true); // 设置全局session超时时间 manager.setGlobalSessionTimeout(expireTime * 60 * 1000); // 去掉 JSESSIONID manager.setSessionIdUrlRewritingEnabled(false); // 定义要使用的无效的Session定时调度器 manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class)); // 是否定时检查session manager.setSessionValidationSchedulerEnabled(true); // 自定义SessionDao manager.setSessionDAO(sessionDAO()); // 自定义sessionFactory manager.setSessionFactory(sessionFactory()); return manager; } /** * 安全管理器 */ @Bean public SecurityManager securityManager(UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(userRealm); // 记住我 securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null); // 注入缓存管理器; securityManager.setCacheManager(getEhCacheManager()); // session管理器 securityManager.setSessionManager(sessionManager()); return securityManager; } /** * 退出过滤器 */ public LogoutFilter logoutFilter() { LogoutFilter logoutFilter = new LogoutFilter(); logoutFilter.setLoginUrl(loginUrl); return logoutFilter; } /** * csrf过滤器 */ public CsrfValidateFilter csrfValidateFilter() { CsrfValidateFilter csrfValidateFilter = new CsrfValidateFilter(); csrfValidateFilter.setEnabled(csrfEnabled); csrfValidateFilter.setCsrfWhites(StringUtils.str2List(csrfWhites, ",")); return csrfValidateFilter; } /** * Shiro过滤器配置 */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean(); // Shiro的核心安全接口,这个属性是必须的 shiroFilterFactoryBean.setSecurityManager(securityManager); // 身份认证失败,则跳转到登录页面的配置 shiroFilterFactoryBean.setLoginUrl(loginUrl); // 权限认证失败,则跳转到指定页面 shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl); // Shiro连接约束配置,即过滤链的定义 LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>(); // 对静态资源设置匿名访问 filterChainDefinitionMap.put("/favicon.ico**", "anon"); filterChainDefinitionMap.put("/ruoyi.png**", "anon"); filterChainDefinitionMap.put("/html/**", "anon"); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/docs/**", "anon"); filterChainDefinitionMap.put("/fonts/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/ajax/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/ruoyi/**", "anon"); filterChainDefinitionMap.put("/captcha/captchaImage**", "anon"); // 退出 logout地址,shiro去清除session filterChainDefinitionMap.put("/logout", "logout"); // 不需要拦截的访问 filterChainDefinitionMap.put("/login", "anon,captchaValidate"); // 注册相关 filterChainDefinitionMap.put("/register", "anon,captchaValidate"); // 系统权限列表 // filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll()); Map filters = new LinkedHashMap(); filters.put("onlineSession", onlineSessionFilter()); filters.put("syncOnlineSession", syncOnlineSessionFilter()); filters.put("captchaValidate", captchaValidateFilter()); filters.put("csrfValidateFilter", csrfValidateFilter()); filters.put("kickout", kickoutSessionFilter()); // 注销成功,则跳转到指定页面 filters.put("logout", logoutFilter()); shiroFilterFactoryBean.setFilters(filters); // 所有请求需要认证 filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession,csrfValidateFilter"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 自定义在线用户处理过滤器 */ public OnlineSessionFilter onlineSessionFilter() { OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter(); onlineSessionFilter.setLoginUrl(loginUrl); onlineSessionFilter.setOnlineSessionDAO(sessionDAO()); return onlineSessionFilter; } /** * 自定义在线用户同步过滤器 */ public SyncOnlineSessionFilter syncOnlineSessionFilter() { SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter(); syncOnlineSessionFilter.setOnlineSessionDAO(sessionDAO()); return syncOnlineSessionFilter; } /** * 自定义验证码过滤器 */ public CaptchaValidateFilter captchaValidateFilter() { CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter(); captchaValidateFilter.setCaptchaEnabled(captchaEnabled); captchaValidateFilter.setCaptchaType(captchaType); return captchaValidateFilter; } /** * cookie 属性设置 */ public SimpleCookie rememberMeCookie() { SimpleCookie cookie = new SimpleCookie("rememberMe"); cookie.setDomain(domain); cookie.setPath(path); cookie.setHttpOnly(httpOnly); cookie.setMaxAge(maxAge * 24 * 60 * 60); return cookie; } /** * 记住我 */ public CustomCookieRememberMeManager rememberMeManager() { CustomCookieRememberMeManager cookieRememberMeManager = new CustomCookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); if (StringUtils.isNotEmpty(cipherKey)) { cookieRememberMeManager.setCipherKey(Base64.decode(cipherKey)); } else { cookieRememberMeManager.setCipherKey(CipherUtils.generateNewKey(128, "AES").getEncoded()); } return cookieRememberMeManager; } /** * 同一个用户多设备登录限制 */ public KickoutSessionFilter kickoutSessionFilter() { KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter(); kickoutSessionFilter.setCacheManager(getEhCacheManager()); kickoutSessionFilter.setSessionManager(sessionManager()); // 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录 kickoutSessionFilter.setMaxSession(maxSession); // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序 kickoutSessionFilter.setKickoutAfter(kickoutAfter); // 被踢出后重定向到的地址; kickoutSessionFilter.setKickoutUrl("/login?kickout=1"); return kickoutSessionFilter; } /** * thymeleaf模板引擎和shiro框架的整合 */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } /** * 开启Shiro注解通知器 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( @Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java ================================================ package com.ruoyi.framework.config.properties; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import com.alibaba.druid.pool.DruidDataSource; /** * druid 配置属性 * * @author ruoyi */ @Configuration public class DruidProperties { @Value("${spring.datasource.druid.initialSize}") private int initialSize; @Value("${spring.datasource.druid.minIdle}") private int minIdle; @Value("${spring.datasource.druid.maxActive}") private int maxActive; @Value("${spring.datasource.druid.maxWait}") private int maxWait; @Value("${spring.datasource.druid.connectTimeout}") private int connectTimeout; @Value("${spring.datasource.druid.socketTimeout}") private int socketTimeout; @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") private int timeBetweenEvictionRunsMillis; @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") private int minEvictableIdleTimeMillis; @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") private int maxEvictableIdleTimeMillis; @Value("${spring.datasource.druid.validationQuery}") private String validationQuery; @Value("${spring.datasource.druid.testWhileIdle}") private boolean testWhileIdle; @Value("${spring.datasource.druid.testOnBorrow}") private boolean testOnBorrow; @Value("${spring.datasource.druid.testOnReturn}") private boolean testOnReturn; public DruidDataSource dataSource(DruidDataSource datasource) { /** 配置初始化大小、最小、最大 */ datasource.setInitialSize(initialSize); datasource.setMaxActive(maxActive); datasource.setMinIdle(minIdle); /** 配置获取连接等待超时的时间 */ datasource.setMaxWait(maxWait); /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */ datasource.setConnectTimeout(connectTimeout); /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */ datasource.setSocketTimeout(socketTimeout); /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); /** * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 */ datasource.setValidationQuery(validationQuery); /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ datasource.setTestWhileIdle(testWhileIdle); /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnBorrow(testOnBorrow); /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnReturn(testOnReturn); return datasource; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java ================================================ package com.ruoyi.framework.datasource; import java.util.Map; import javax.sql.DataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import com.ruoyi.common.config.datasource.DynamicDataSourceContextHolder; /** * 动态数据源 * * @author ruoyi */ public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java ================================================ package com.ruoyi.framework.interceptor; import java.lang.reflect.Method; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import com.ruoyi.common.json.JSON; import com.ruoyi.common.annotation.RepeatSubmit; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.ServletUtils; /** * 防止重复提交拦截器 * * @author ruoyi */ @Component public abstract class RepeatSubmitInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); if (annotation != null) { if (this.isRepeatSubmit(request, annotation)) { AjaxResult ajaxResult = AjaxResult.error(annotation.message()); ServletUtils.renderString(response, JSON.marshal(ajaxResult)); return false; } } return true; } else { return true; } } /** * 验证是否重复提交由子类实现具体的防重复提交的规则 * * @param request 请求对象 * @param annotation 防复注解 * @return 结果 */ public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception; } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java ================================================ package com.ruoyi.framework.interceptor.impl; import java.util.HashMap; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import org.springframework.stereotype.Component; import com.ruoyi.common.annotation.RepeatSubmit; import com.ruoyi.common.json.JSON; import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; /** * 判断请求url和数据是否和上一次相同, * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 * * @author ruoyi */ @Component public class SameUrlDataInterceptor extends RepeatSubmitInterceptor { public final String REPEAT_PARAMS = "repeatParams"; public final String REPEAT_TIME = "repeatTime"; public final String SESSION_REPEAT_KEY = "repeatData"; @SuppressWarnings("unchecked") @Override public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception { // 本次参数及系统时间 String nowParams = JSON.marshal(request.getParameterMap()); Map nowDataMap = new HashMap(); nowDataMap.put(REPEAT_PARAMS, nowParams); nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); // 请求地址(作为存放session的key值) String url = request.getRequestURI(); HttpSession session = request.getSession(); Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY); if (sessionObj != null) { Map sessionMap = (Map) sessionObj; if (sessionMap.containsKey(url)) { Map preDataMap = (Map) sessionMap.get(url); if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) { return true; } } } Map sessionMap = new HashMap(); sessionMap.put(url, nowDataMap); session.setAttribute(SESSION_REPEAT_KEY, sessionMap); return false; } /** * 判断参数是否相同 */ private boolean compareParams(Map nowMap, Map preMap) { String nowParams = (String) nowMap.get(REPEAT_PARAMS); String preParams = (String) preMap.get(REPEAT_PARAMS); return nowParams.equals(preParams); } /** * 判断两次间隔时间 */ private boolean compareTime(Map nowMap, Map preMap, int interval) { long time1 = (Long) nowMap.get(REPEAT_TIME); long time2 = (Long) preMap.get(REPEAT_TIME); if ((time1 - time2) < interval) { return true; } return false; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java ================================================ package com.ruoyi.framework.manager; import java.util.TimerTask; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import com.ruoyi.common.utils.Threads; import com.ruoyi.common.utils.spring.SpringUtils; /** * 异步任务管理器 * * @author liuhulu */ public class AsyncManager { /** * 操作延迟10毫秒 */ private final int OPERATE_DELAY_TIME = 10; /** * 异步操作任务调度线程池 */ private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); /** * 单例模式 */ private AsyncManager(){} private static AsyncManager me = new AsyncManager(); public static AsyncManager me() { return me; } /** * 执行任务 * * @param task 任务 */ public void execute(TimerTask task) { executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); } /** * 停止任务线程池 */ public void shutdown() { Threads.shutdownAndAwaitTermination(executor); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java ================================================ package com.ruoyi.framework.manager; import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler; import net.sf.ehcache.CacheManager; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import jakarta.annotation.PreDestroy; /** * 确保应用退出时能关闭后台线程 * * @author cj */ @Component public class ShutdownManager { private static final Logger logger = LoggerFactory.getLogger("sys-user"); @Autowired(required = false) private SpringSessionValidationScheduler springSessionValidationScheduler; @Autowired(required = false) private EhCacheManager ehCacheManager; @PreDestroy public void destroy() { shutdownSpringSessionValidationScheduler(); shutdownAsyncManager(); shutdownEhCacheManager(); } /** * 停止Seesion会话检查 */ private void shutdownSpringSessionValidationScheduler() { if (springSessionValidationScheduler != null && springSessionValidationScheduler.isEnabled()) { try { logger.info("====关闭会话验证任务===="); springSessionValidationScheduler.disableSessionValidation(); } catch (Exception e) { logger.error(e.getMessage(), e); } } } /** * 停止异步执行任务 */ private void shutdownAsyncManager() { try { logger.info("====关闭后台任务任务线程池===="); AsyncManager.me().shutdown(); } catch (Exception e) { logger.error(e.getMessage(), e); } } private void shutdownEhCacheManager() { try { logger.info("====关闭缓存===="); if (ehCacheManager != null) { CacheManager cacheManager = ehCacheManager.getCacheManager(); cacheManager.shutdown(); } } catch (Exception e) { logger.error(e.getMessage(), e); } } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java ================================================ package com.ruoyi.framework.manager.factory; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.utils.AddressUtils; import com.ruoyi.common.utils.LogUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.UserAgentUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.domain.SysLogininfor; import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.system.service.ISysOperLogService; import com.ruoyi.system.service.ISysUserOnlineService; import com.ruoyi.system.service.impl.SysLogininforServiceImpl; /** * 异步工厂(产生任务用) * * @author liuhulu * */ public class AsyncFactory { private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); /** * 同步session到数据库 * * @param session 在线用户会话 * @return 任务task */ public static TimerTask syncSessionToDb(final OnlineSession session) { return new TimerTask() { @Override public void run() { SysUserOnline online = new SysUserOnline(); online.setSessionId(String.valueOf(session.getId())); online.setDeptName(session.getDeptName()); online.setLoginName(session.getLoginName()); online.setStartTimestamp(session.getStartTimestamp()); online.setLastAccessTime(session.getLastAccessTime()); online.setExpireTime(session.getTimeout()); online.setIpaddr(session.getHost()); online.setLoginLocation(AddressUtils.getRealAddressByIP(session.getHost())); online.setBrowser(session.getBrowser()); online.setOs(session.getOs()); online.setStatus(session.getStatus()); // 序列化 OnlineSession,重启后可从 DB 恢复会话 try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(session); oos.close(); online.setSessionData(bos.toByteArray()); } catch (Exception e) { // 序列化失败不影响正常流程,仅记录日志 LoggerFactory.getLogger(AsyncFactory.class).warn("serialize OnlineSession failed", e); } SpringUtils.getBean(ISysUserOnlineService.class).saveOnline(online); } }; } /** * 操作日志记录 * * @param operLog 操作日志信息 * @return 任务task */ public static TimerTask recordOper(final SysOperLog operLog) { return new TimerTask() { @Override public void run() { // 远程查询操作地点 operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); } }; } /** * 记录登录信息 * * @param username 用户名 * @param status 状态 * @param message 消息 * @param args 列表 * @return 任务task */ public static TimerTask recordLogininfor(final String username, final String status, final String message, final Object... args) { final String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); final String ip = ShiroUtils.getIp(); return new TimerTask() { @Override public void run() { String address = AddressUtils.getRealAddressByIP(ip); StringBuilder s = new StringBuilder(); s.append(LogUtils.getBlock(ip)); s.append(address); s.append(LogUtils.getBlock(username)); s.append(LogUtils.getBlock(status)); s.append(LogUtils.getBlock(message)); // 打印信息到日志 sys_user_logger.info(s.toString(), args); // 获取客户端操作系统 String os = UserAgentUtils.getOperatingSystem(userAgent); // 获取客户端浏览器 String browser = UserAgentUtils.getBrowser(userAgent); // 封装对象 SysLogininfor logininfor = new SysLogininfor(); logininfor.setLoginName(username); logininfor.setIpaddr(ip); logininfor.setLoginLocation(address); logininfor.setBrowser(browser); logininfor.setOs(os); logininfor.setMsg(message); // 日志状态 if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) { logininfor.setStatus(Constants.SUCCESS); } else if (Constants.LOGIN_FAIL.equals(status)) { logininfor.setStatus(Constants.FAIL); } // 插入数据 SpringUtils.getBean(SysLogininforServiceImpl.class).insertLogininfor(logininfor); } }; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java ================================================ package com.ruoyi.framework.shiro.realm; import java.util.HashSet; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.ExcessiveAttemptsException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cache.Cache; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.exception.user.CaptchaException; import com.ruoyi.common.exception.user.RoleBlockedException; import com.ruoyi.common.exception.user.UserBlockedException; import com.ruoyi.common.exception.user.UserNotExistsException; import com.ruoyi.common.exception.user.UserPasswordNotMatchException; import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.framework.shiro.service.SysLoginService; import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysRoleService; /** * 自定义Realm 处理登录 权限 * * @author ruoyi */ public class UserRealm extends AuthorizingRealm { private static final Logger log = LoggerFactory.getLogger(UserRealm.class); @Autowired private ISysMenuService menuService; @Autowired private ISysRoleService roleService; @Autowired private SysLoginService loginService; /** * 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { SysUser user = ShiroUtils.getSysUser(); // 角色列表 Set roles = new HashSet(); // 功能列表 Set menus = new HashSet(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 管理员拥有所有权限 if (user.isAdmin()) { info.addRole("admin"); info.addStringPermission("*:*:*"); } else { roles = roleService.selectRoleKeys(user.getUserId()); menus = menuService.selectPermsByUserId(user.getUserId()); // 角色加入AuthorizationInfo认证对象 info.setRoles(roles); // 权限加入AuthorizationInfo认证对象 info.setStringPermissions(menus); } return info; } /** * 登录认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); String password = ""; if (upToken.getPassword() != null) { password = new String(upToken.getPassword()); } SysUser user = null; try { user = loginService.login(username, password); } catch (CaptchaException e) { throw new AuthenticationException(e.getMessage(), e); } catch (UserNotExistsException e) { throw new UnknownAccountException(e.getMessage(), e); } catch (UserPasswordNotMatchException e) { throw new IncorrectCredentialsException(e.getMessage(), e); } catch (UserPasswordRetryLimitExceedException e) { throw new ExcessiveAttemptsException(e.getMessage(), e); } catch (UserBlockedException e) { throw new LockedAccountException(e.getMessage(), e); } catch (RoleBlockedException e) { throw new LockedAccountException(e.getMessage(), e); } catch (Exception e) { log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage()); throw new AuthenticationException(e.getMessage(), e); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); return info; } /** * 清理指定用户授权信息缓存 */ public void clearCachedAuthorizationInfo(Object principal) { SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName()); this.clearCachedAuthorizationInfo(principals); } /** * 清理所有用户授权信息缓存 */ public void clearAllCachedAuthorizationInfo() { Cache cache = getAuthorizationCache(); if (cache != null) { for (Object key : cache.keys()) { cache.remove(key); } } } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/rememberMe/CustomCookieRememberMeManager.java ================================================ package com.ruoyi.framework.shiro.rememberMe; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.SubjectContext; import org.apache.shiro.web.mgt.CookieRememberMeManager; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.framework.shiro.service.SysLoginService; /** * 自定义CookieRememberMeManager * * @author ruoyi */ public class CustomCookieRememberMeManager extends CookieRememberMeManager { /** * 记住我时去掉角色的permissions权限字符串,防止http请求头过大。 */ @Override protected void rememberIdentity(Subject subject, PrincipalCollection principalCollection) { Map> rolePermissions = new HashMap<>(); // 清除角色的permissions权限字符串 for (Object principal : principalCollection) { if (principal instanceof SysUser) { List roles = ((SysUser) principal).getRoles(); for (SysRole role : roles) { rolePermissions.put(role, role.getPermissions()); role.setPermissions(null); } } } byte[] bytes = convertPrincipalsToBytes(principalCollection); // 恢复角色的permissions权限字符串 for (Object principal : principalCollection) { if (principal instanceof SysUser) { List roles = ((SysUser) principal).getRoles(); for (SysRole role : roles) { role.setPermissions(rolePermissions.get(role)); } } } rememberSerializedIdentity(subject, bytes); } /** * 取记住我身份时恢复角色permissions权限字符串。 */ @Override public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) { PrincipalCollection principals = super.getRememberedPrincipals(subjectContext); if (principals == null || principals.isEmpty()) { return principals; } for (Object principal : principals) { if (principal instanceof SysUser) { SpringUtils.getBean(SysLoginService.class).setRolePermission((SysUser) principal); } } return principals; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysLoginService.java ================================================ package com.ruoyi.framework.shiro.service; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.exception.user.BlackListException; import com.ruoyi.common.exception.user.CaptchaException; import com.ruoyi.common.exception.user.UserBlockedException; import com.ruoyi.common.exception.user.UserDeleteException; import com.ruoyi.common.exception.user.UserNotExistsException; import com.ruoyi.common.exception.user.UserPasswordNotMatchException; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.IpUtils; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysUserService; /** * 登录校验方法 * * @author ruoyi */ @Component public class SysLoginService { @Autowired private SysPasswordService passwordService; @Autowired private ISysUserService userService; @Autowired private ISysMenuService menuService; @Autowired private ISysConfigService configService; /** * 登录 */ public SysUser login(String username, String password) { // 验证码校验 if (ShiroConstants.CAPTCHA_ERROR.equals(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA))) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); } // 用户名或密码为空 错误 if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); throw new UserNotExistsException(); } // 密码如果不在指定范围内 错误 if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } // 用户名不在指定范围内 错误 if (username.length() < UserConstants.USERNAME_MIN_LENGTH || username.length() > UserConstants.USERNAME_MAX_LENGTH) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } // IP黑名单校验 String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); if (IpUtils.isMatchedIp(blackStr, ShiroUtils.getIp())) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); throw new BlackListException(); } // 查询用户信息 SysUser user = userService.selectUserByLoginName(username); /** if (user == null && maybeMobilePhoneNumber(username)) { user = userService.selectUserByPhoneNumber(username); } if (user == null && maybeEmail(username)) { user = userService.selectUserByEmail(username); } */ if (user == null) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists"))); throw new UserNotExistsException(); } if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.delete"))); throw new UserDeleteException(); } if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.blocked"))); throw new UserBlockedException(); } passwordService.validate(user, password); AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); setRolePermission(user); recordLoginInfo(user.getUserId()); return user; } /** private boolean maybeEmail(String username) { if (!username.matches(UserConstants.EMAIL_PATTERN)) { return false; } return true; } private boolean maybeMobilePhoneNumber(String username) { if (!username.matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN)) { return false; } return true; } */ /** * 设置角色权限 * * @param user 用户信息 */ public void setRolePermission(SysUser user) { List roles = user.getRoles(); if (!roles.isEmpty()) { // 设置permissions属性,以便数据权限匹配权限 for (SysRole role : roles) { if (StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && !role.isAdmin()) { Set rolePerms = menuService.selectPermsByRoleId(role.getRoleId()); role.setPermissions(rolePerms); } } } } /** * 记录登录信息 * * @param userId 用户ID */ public void recordLoginInfo(Long userId) { userService.updateLoginInfo(userId, ShiroUtils.getIp(), DateUtils.getNowDate()); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java ================================================ package com.ruoyi.framework.shiro.service; import java.util.concurrent.atomic.AtomicInteger; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.exception.user.UserPasswordNotMatchException; import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.security.Md5Utils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import jakarta.annotation.PostConstruct; /** * 登录密码方法 * * @author ruoyi */ @Component public class SysPasswordService { @Autowired private CacheManager cacheManager; private Cache loginRecordCache; @Value(value = "${user.password.maxRetryCount}") private String maxRetryCount; @PostConstruct public void init() { loginRecordCache = cacheManager.getCache(ShiroConstants.LOGIN_RECORD_CACHE); } public void validate(SysUser user, String password) { String loginName = user.getLoginName(); AtomicInteger retryCount = loginRecordCache.get(loginName); if (retryCount == null) { retryCount = new AtomicInteger(0); loginRecordCache.put(loginName, retryCount); } if (retryCount.incrementAndGet() > Integer.valueOf(maxRetryCount).intValue()) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount))); throw new UserPasswordRetryLimitExceedException(Integer.valueOf(maxRetryCount).intValue()); } if (!matches(user, password)) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.count", retryCount))); loginRecordCache.put(loginName, retryCount); throw new UserPasswordNotMatchException(); } else { clearLoginRecordCache(loginName); } } public boolean matches(SysUser user, String newPassword) { return user.getPassword().equals(encryptPassword(user.getLoginName(), newPassword, user.getSalt())); } public void clearLoginRecordCache(String loginName) { loginRecordCache.remove(loginName); } public String encryptPassword(String loginName, String password, String salt) { return Md5Utils.hash(loginName + password + salt); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysRegisterService.java ================================================ package com.ruoyi.framework.shiro.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.system.service.ISysUserService; /** * 注册校验方法 * * @author ruoyi */ @Component public class SysRegisterService { @Autowired private ISysUserService userService; @Autowired private SysPasswordService passwordService; /** * 注册 */ public String register(SysUser user) { String msg = "", loginName = user.getLoginName(), password = user.getPassword(); if (ShiroConstants.CAPTCHA_ERROR.equals(ServletUtils.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA))) { msg = "验证码错误"; } else if (StringUtils.isEmpty(loginName)) { msg = "用户名不能为空"; } else if (StringUtils.isEmpty(password)) { msg = "用户密码不能为空"; } else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { msg = "密码长度必须在5到20个字符之间"; } else if (loginName.length() < UserConstants.USERNAME_MIN_LENGTH || loginName.length() > UserConstants.USERNAME_MAX_LENGTH) { msg = "账户长度必须在2到20个字符之间"; } else if (!userService.checkLoginNameUnique(user)) { msg = "保存用户'" + loginName + "'失败,注册账号已存在"; } else { user.setPwdUpdateDate(DateUtils.getNowDate()); user.setUserName(loginName); user.setSalt(ShiroUtils.randomSalt()); user.setPassword(passwordService.encryptPassword(loginName, password, user.getSalt())); boolean regFlag = userService.registerUser(user); if (!regFlag) { msg = "注册失败,请联系系统管理人员"; } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.REGISTER, MessageUtils.message("user.register.success"))); } } return msg; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java ================================================ package com.ruoyi.framework.shiro.service; import java.io.Serializable; import org.apache.shiro.session.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.framework.shiro.session.OnlineSessionFactory; import com.ruoyi.system.service.ISysUserOnlineService; /** * 会话db操作处理 * * @author ruoyi */ @Component public class SysShiroService { @Autowired private ISysUserOnlineService onlineService; @Autowired private OnlineSessionFactory onlineSessionFactory; /** * 删除会话 * * @param onlineSession 会话信息 */ public void deleteSession(OnlineSession onlineSession) { onlineService.deleteOnlineById(String.valueOf(onlineSession.getId())); } /** * 获取会话信息 * * @param sessionId * @return */ public Session getSession(Serializable sessionId) { SysUserOnline userOnline = onlineService.selectOnlineById(String.valueOf(sessionId)); return StringUtils.isNull(userOnline) ? null : createSession(userOnline); } public Session createSession(SysUserOnline userOnline) { return onlineSessionFactory.createSession(userOnline); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java ================================================ package com.ruoyi.framework.shiro.session; import java.io.Serializable; import java.util.Date; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.enums.OnlineStatus; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.framework.shiro.service.SysShiroService; /** * 针对自定义的ShiroSession的db操作 * * @author ruoyi */ public class OnlineSessionDAO extends EnterpriseCacheSessionDAO { /** * 同步session到数据库的周期 单位为毫秒(默认1分钟) */ @Value("${shiro.session.dbSyncPeriod}") private int dbSyncPeriod; /** * 上次同步数据库的时间戳 */ private static final String LAST_SYNC_DB_TIMESTAMP = OnlineSessionDAO.class.getName() + "LAST_SYNC_DB_TIMESTAMP"; @Autowired private SysShiroService sysShiroService; public OnlineSessionDAO() { super(); } public OnlineSessionDAO(long expireTime) { super(); } /** * 根据会话ID获取会话 * * @param sessionId 会话ID * @return ShiroSession */ @Override protected Session doReadSession(Serializable sessionId) { return sysShiroService.getSession(sessionId); } @Override public void update(Session session) throws UnknownSessionException { super.update(session); } /** * 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用 */ public void syncToDb(OnlineSession onlineSession) { Date lastSyncTimestamp = (Date) onlineSession.getAttribute(LAST_SYNC_DB_TIMESTAMP); if (lastSyncTimestamp != null) { boolean needSync = true; long deltaTime = onlineSession.getLastAccessTime().getTime() - lastSyncTimestamp.getTime(); if (deltaTime < dbSyncPeriod * 60 * 1000) { // 时间差不足 无需同步 needSync = false; } // isGuest = true 访客 boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L; // session 数据变更了 同步 if (!isGuest && onlineSession.isAttributeChanged()) { needSync = true; } if (!needSync) { return; } } // 更新上次同步数据库时间 onlineSession.setAttribute(LAST_SYNC_DB_TIMESTAMP, onlineSession.getLastAccessTime()); // 更新完后 重置标识 if (onlineSession.isAttributeChanged()) { onlineSession.resetAttributeChanged(); } AsyncManager.me().execute(AsyncFactory.syncSessionToDb(onlineSession)); } /** * 当会话过期/停止(如用户退出时)属性等会调用 */ @Override protected void doDelete(Session session) { OnlineSession onlineSession = (OnlineSession) session; if (null == onlineSession) { return; } onlineSession.setStatus(OnlineStatus.off_line); sysShiroService.deleteSession(onlineSession); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionFactory.java ================================================ package com.ruoyi.framework.shiro.session; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import jakarta.servlet.http.HttpServletRequest; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.SessionContext; import org.apache.shiro.session.mgt.SessionFactory; import org.apache.shiro.web.session.mgt.WebSessionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.utils.IpUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.http.UserAgentUtils; import com.ruoyi.system.domain.SysUserOnline; /** * 自定义sessionFactory会话 * * @author ruoyi */ @Component public class OnlineSessionFactory implements SessionFactory { private static final Logger log = LoggerFactory.getLogger(OnlineSessionFactory.class); public Session createSession(SysUserOnline userOnline) { // 优先从序列化数据恢复完整 Session(包含 principals 认证信息) if (userOnline.getSessionData() != null && userOnline.getSessionData().length > 0) { try { ByteArrayInputStream bis = new ByteArrayInputStream(userOnline.getSessionData()); ObjectInputStream ois = new ObjectInputStream(bis); OnlineSession onlineSession = (OnlineSession) ois.readObject(); ois.close(); // 确保 sessionId 和 DB 一致 if (onlineSession.getId() == null) { onlineSession.setId(userOnline.getSessionId()); } return onlineSession; } catch (Exception e) { log.warn("deserialize OnlineSession failed, sessionId={}", userOnline.getSessionId(), e); } } // 降级:仅用基础字段重建(无认证信息,会触发重新登录) OnlineSession onlineSession = userOnline.getSession(); if (StringUtils.isNotNull(onlineSession) && onlineSession.getId() == null) { onlineSession.setId(userOnline.getSessionId()); } return onlineSession; } @Override public Session createSession(SessionContext initData) { OnlineSession session = new OnlineSession(); if (initData != null && initData instanceof WebSessionContext) { WebSessionContext sessionContext = (WebSessionContext) initData; HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest(); if (request != null) { String userAgent = request.getHeader("User-Agent"); // 获取客户端操作系统 String os = UserAgentUtils.getOperatingSystem(userAgent); // 获取客户端浏览器 String browser = UserAgentUtils.getBrowser(userAgent); session.setHost(IpUtils.getIpAddr(request)); session.setBrowser(browser); session.setOs(os); } } return session; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/util/AuthorizationUtils.java ================================================ package com.ruoyi.framework.shiro.util; import org.apache.shiro.SecurityUtils; import org.apache.shiro.mgt.RealmSecurityManager; import com.ruoyi.framework.shiro.realm.UserRealm; /** * 用户授权信息 * * @author ruoyi */ public class AuthorizationUtils { /** * 清理所有用户授权信息缓存 */ public static void clearAllCachedAuthorizationInfo() { getUserRealm().clearAllCachedAuthorizationInfo(); } /** * 获取自定义Realm */ public static UserRealm getUserRealm() { RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager(); return (UserRealm) rsm.getRealms().iterator().next(); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/CustomShiroFilterFactoryBean.java ================================================ package com.ruoyi.framework.shiro.web; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.InvalidRequestFilter; import org.apache.shiro.web.filter.mgt.DefaultFilter; import org.apache.shiro.web.filter.mgt.FilterChainManager; import org.apache.shiro.web.filter.mgt.FilterChainResolver; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.mgt.WebSecurityManager; import org.apache.shiro.web.servlet.AbstractShiroFilter; import org.apache.shiro.mgt.SecurityManager; import org.springframework.beans.factory.BeanInitializationException; import jakarta.servlet.Filter; import java.util.Map; /** * 自定义ShiroFilterFactoryBean解决资源中文路径问题 * * @author ruoyi */ public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean { @Override public Class getObjectType() { return MySpringShiroFilter.class; } @Override protected AbstractShiroFilter createInstance() throws Exception { SecurityManager securityManager = getSecurityManager(); if (securityManager == null) { String msg = "SecurityManager property must be set."; throw new BeanInitializationException(msg); } if (!(securityManager instanceof WebSecurityManager)) { String msg = "The security manager does not implement the WebSecurityManager interface."; throw new BeanInitializationException(msg); } FilterChainManager manager = createFilterChainManager(); // Expose the constructed FilterChainManager by first wrapping it in a // FilterChainResolver implementation. The AbstractShiroFilter implementations // do not know about FilterChainManagers - only resolvers: PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager); Map filterMap = manager.getFilters(); Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name()); if (invalidRequestFilter instanceof InvalidRequestFilter) { // 此处是关键,设置false跳过URL携带中文400,servletPath中文校验bug ((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false); } // Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built // FilterChainResolver. It doesn't matter that the instance is an anonymous inner class // here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts // injection of the SecurityManager and FilterChainResolver: return new MySpringShiroFilter((WebSecurityManager) securityManager, chainResolver); } private static final class MySpringShiroFilter extends AbstractShiroFilter { protected MySpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) { if (webSecurityManager == null) { throw new IllegalArgumentException("WebSecurityManager property cannot be null."); } else { this.setSecurityManager(webSecurityManager); if (resolver != null) { this.setFilterChainResolver(resolver); } } } } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java ================================================ package com.ruoyi.framework.shiro.web.filter; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import org.apache.shiro.session.SessionException; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.factory.AsyncFactory; import com.ruoyi.system.service.ISysUserOnlineService; /** * 退出过滤器 * * @author ruoyi */ public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter { private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class); /** * 退出后重定向的地址 */ private String loginUrl; public String getLoginUrl() { return loginUrl; } public void setLoginUrl(String loginUrl) { this.loginUrl = loginUrl; } @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { try { Subject subject = getSubject(request, response); String redirectUrl = getRedirectUrl(request, response, subject); try { SysUser user = ShiroUtils.getSysUser(); if (StringUtils.isNotNull(user)) { String loginName = user.getLoginName(); // 记录用户退出日志 AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); // 清理缓存 SpringUtils.getBean(ISysUserOnlineService.class).removeUserCache(loginName, ShiroUtils.getSessionId()); } // 退出登录 subject.logout(); } catch (SessionException ise) { log.error("logout fail.", ise); } issueRedirect(request, response, redirectUrl); } catch (Exception e) { log.error("Encountered session exception during logout. This can generally safely be ignored.", e); } return false; } /** * 退出跳转URL */ @Override protected String getRedirectUrl(ServletRequest request, ServletResponse response, Subject subject) { String url = getLoginUrl(); if (StringUtils.isNotEmpty(url)) { return url; } return super.getRedirectUrl(request, response, subject); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/captcha/CaptchaValidateFilter.java ================================================ package com.ruoyi.framework.shiro.web.filter.captcha; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import org.apache.shiro.web.filter.AccessControlFilter; import com.google.code.kaptcha.Constants; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; /** * 验证码过滤器 * * @author ruoyi */ public class CaptchaValidateFilter extends AccessControlFilter { /** * 是否开启验证码 */ private boolean captchaEnabled = true; /** * 验证码类型 */ private String captchaType = "math"; public void setCaptchaEnabled(boolean captchaEnabled) { this.captchaEnabled = captchaEnabled; } public void setCaptchaType(String captchaType) { this.captchaType = captchaType; } @Override public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { request.setAttribute(ShiroConstants.CURRENT_ENABLED, captchaEnabled); request.setAttribute(ShiroConstants.CURRENT_TYPE, captchaType); return super.onPreHandle(request, response, mappedValue); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 验证码禁用 或不是表单提交 允许访问 if (captchaEnabled == false || !"post".equals(httpServletRequest.getMethod().toLowerCase())) { return true; } return validateResponse(httpServletRequest, httpServletRequest.getParameter(ShiroConstants.CURRENT_VALIDATECODE)); } public boolean validateResponse(HttpServletRequest request, String validateCode) { Object obj = ShiroUtils.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY); String code = String.valueOf(obj != null ? obj : ""); // 验证码清除,防止多次使用。 request.getSession().removeAttribute(Constants.KAPTCHA_SESSION_KEY); if (StringUtils.isEmpty(validateCode) || !validateCode.equalsIgnoreCase(code)) { return false; } return true; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { request.setAttribute(ShiroConstants.CURRENT_CAPTCHA, ShiroConstants.CAPTCHA_ERROR); return true; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/csrf/CsrfValidateFilter.java ================================================ package com.ruoyi.framework.shiro.web.filter.csrf; import java.util.List; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.shiro.web.filter.AccessControlFilter; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; /** * csrf过滤器 * * @author ruoyi */ public class CsrfValidateFilter extends AccessControlFilter { /** * 白名单链接 */ private List csrfWhites; @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; if (!isAllowMethod(httpServletRequest)) { return true; } if (StringUtils.matches(httpServletRequest.getServletPath(), csrfWhites)) { return true; } return validateResponse(httpServletRequest, httpServletRequest.getHeader(ShiroConstants.X_CSRF_TOKEN)); } public boolean validateResponse(HttpServletRequest request, String requestToken) { Object obj = ShiroUtils.getSession().getAttribute(ShiroConstants.CSRF_TOKEN); String sessionToken = Convert.toStr(obj, ""); if (StringUtils.isEmpty(requestToken) || !requestToken.equalsIgnoreCase(sessionToken)) { return false; } return true; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { ServletUtils.renderString((HttpServletResponse) response, "{\"code\":\"1\",\"msg\":\"当前请求的安全验证未通过,请刷新页面后重试。\"}"); return false; } private boolean isAllowMethod(HttpServletRequest request) { String method = request.getMethod(); return "POST".equalsIgnoreCase(method); } public List getCsrfWhites() { return csrfWhites; } public void setCsrfWhites(List csrfWhites) { this.csrfWhites = csrfWhites; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/kickout/KickoutSessionFilter.java ================================================ package com.ruoyi.framework.shiro.web.filter.kickout; import java.io.IOException; import java.io.Serializable; import java.util.ArrayDeque; import java.util.Deque; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.DefaultSessionKey; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.AccessControlFilter; import org.apache.shiro.web.util.WebUtils; import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ShiroUtils; /** * 登录账号控制过滤器 * * @author ruoyi */ public class KickoutSessionFilter extends AccessControlFilter { private final static ObjectMapper objectMapper = new ObjectMapper(); /** * 同一个用户最大会话数 **/ private int maxSession = -1; /** * 踢出之前登录的/之后登录的用户 默认false踢出之前登录的用户 **/ private Boolean kickoutAfter = false; /** * 踢出后到的地址 **/ private String kickoutUrl; private SessionManager sessionManager; private Cache> cache; @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request, response); if (!subject.isAuthenticated() && !subject.isRemembered() || maxSession == -1) { // 如果没有登录或用户最大会话数为-1,直接进行之后的流程 return true; } try { Session session = subject.getSession(); // 当前登录用户 SysUser user = ShiroUtils.getSysUser(); String loginName = user.getLoginName(); Serializable sessionId = session.getId(); // 读取缓存用户 没有就存入 Deque deque = cache.get(loginName); if (deque == null) { // 初始化队列 deque = new ArrayDeque(); } // 如果队列里没有此sessionId,且用户没有被踢出;放入队列 if (!deque.contains(sessionId) && session.getAttribute("kickout") == null) { // 将sessionId存入队列 deque.push(sessionId); // 将用户的sessionId队列缓存 cache.put(loginName, deque); } // 如果队列里的sessionId数超出最大会话数,开始踢人 while (deque.size() > maxSession) { // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户; Serializable kickoutSessionId = kickoutAfter ? deque.removeFirst() : deque.removeLast(); // 踢出后再更新下缓存队列 cache.put(loginName, deque); try { // 获取被踢出的sessionId的session对象 Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId)); if (null != kickoutSession) { // 设置会话的kickout属性表示踢出了 kickoutSession.setAttribute("kickout", true); } } catch (Exception e) { // 面对异常,我们选择忽略 } } // 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址 if (session.getAttribute("kickout") != null && (Boolean) session.getAttribute("kickout") == true) { // 退出登录 subject.logout(); saveRequest(request); return isAjaxResponse(request, response); } return true; } catch (Exception e) { return isAjaxResponse(request, response); } } private boolean isAjaxResponse(ServletRequest request, ServletResponse response) throws IOException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; if (ServletUtils.isAjaxRequest(req)) { AjaxResult ajaxResult = AjaxResult.error("您已在别处登录,请您修改密码或重新登录"); ServletUtils.renderString(res, objectMapper.writeValueAsString(ajaxResult)); } else { WebUtils.issueRedirect(request, response, kickoutUrl); } return false; } public void setMaxSession(int maxSession) { this.maxSession = maxSession; } public void setKickoutAfter(boolean kickoutAfter) { this.kickoutAfter = kickoutAfter; } public void setKickoutUrl(String kickoutUrl) { this.kickoutUrl = kickoutUrl; } public void setSessionManager(SessionManager sessionManager) { this.sessionManager = sessionManager; } // 设置Cache的key的前缀 public void setCacheManager(CacheManager cacheManager) { // 必须和ehcache缓存配置中的缓存name一致 this.cache = cacheManager.getCache(ShiroConstants.SYS_USERCACHE); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java ================================================ package com.ruoyi.framework.shiro.web.filter.online; import java.io.IOException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.AccessControlFilter; import org.apache.shiro.web.util.WebUtils; import org.springframework.beans.factory.annotation.Value; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.enums.OnlineStatus; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; /** * 自定义访问控制 * * @author ruoyi */ public class OnlineSessionFilter extends AccessControlFilter { /** * 强制退出后重定向的地址 */ @Value("${shiro.user.loginUrl}") private String loginUrl; private OnlineSessionDAO onlineSessionDAO; /** * 表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false; */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { Subject subject = getSubject(request, response); if (subject == null || subject.getSession() == null) { return true; } Session session = onlineSessionDAO.readSession(subject.getSession().getId()); if (session != null && session instanceof OnlineSession) { OnlineSession onlineSession = (OnlineSession) session; request.setAttribute(ShiroConstants.ONLINE_SESSION, onlineSession); // 把user对象设置进去 boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L; if (isGuest == true) { SysUser user = ShiroUtils.getSysUser(); if (user != null) { onlineSession.setUserId(user.getUserId()); onlineSession.setLoginName(user.getLoginName()); onlineSession.setAvatar(user.getAvatar()); onlineSession.setDeptName(user.getDept().getDeptName()); onlineSession.markAttributeChanged(); onlineSessionDAO.update(onlineSession); } } if (onlineSession.getStatus() == OnlineStatus.off_line) { return false; } } return true; } /** * 表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request, response); if (subject != null) { subject.logout(); } saveRequestAndRedirectToLogin(request, response); return false; } // 跳转到登录页 @Override protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException { WebUtils.issueRedirect(request, response, loginUrl); } public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO) { this.onlineSessionDAO = onlineSessionDAO; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java ================================================ package com.ruoyi.framework.shiro.web.filter.sync; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import org.apache.shiro.web.filter.PathMatchingFilter; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; /** * 同步Session数据到Db * * @author ruoyi */ public class SyncOnlineSessionFilter extends PathMatchingFilter { private OnlineSessionDAO onlineSessionDAO; /** * 同步会话数据到DB 一次请求最多同步一次 防止过多处理 需要放到Shiro过滤器之前 */ @Override protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { OnlineSession session = (OnlineSession) request.getAttribute(ShiroConstants.ONLINE_SESSION); // 如果session stop了 也不同步 // session停止时间,如果stopTimestamp不为null,则代表已停止 if (session != null && session.getUserId() != null && session.getStopTimestamp() == null) { onlineSessionDAO.syncToDb(session); } return true; } public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO) { this.onlineSessionDAO = onlineSessionDAO; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java ================================================ package com.ruoyi.framework.shiro.web.session; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import org.apache.commons.lang3.time.DateUtils; import org.apache.shiro.session.ExpiredSessionException; import org.apache.shiro.session.InvalidSessionException; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.DefaultSessionKey; import org.apache.shiro.session.mgt.SessionKey; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.system.service.ISysUserOnlineService; /** * 主要是在此如果会话的属性修改了 就标识下其修改了 然后方便 OnlineSessionDao同步 * * @author ruoyi */ public class OnlineWebSessionManager extends DefaultWebSessionManager { private static final Logger log = LoggerFactory.getLogger(OnlineWebSessionManager.class); @Override public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException { super.setAttribute(sessionKey, attributeKey, value); if (value != null && needMarkAttributeChanged(attributeKey)) { OnlineSession session = getOnlineSession(sessionKey); session.markAttributeChanged(); } } private boolean needMarkAttributeChanged(Object attributeKey) { if (attributeKey == null) { return false; } String attributeKeyStr = attributeKey.toString(); // 优化 flash属性没必要持久化 if (attributeKeyStr.startsWith("org.springframework")) { return false; } if (attributeKeyStr.startsWith("javax.servlet") || attributeKeyStr.startsWith("jakarta.servlet")) { return false; } if (attributeKeyStr.equals(ShiroConstants.CURRENT_USERNAME)) { return false; } return true; } @Override public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException { Object removed = super.removeAttribute(sessionKey, attributeKey); if (removed != null) { OnlineSession s = getOnlineSession(sessionKey); s.markAttributeChanged(); } return removed; } public OnlineSession getOnlineSession(SessionKey sessionKey) { OnlineSession session = null; Object obj = doGetSession(sessionKey); if (StringUtils.isNotNull(obj)) { session = new OnlineSession(); BeanUtils.copyBeanProp(session, obj); } return session; } /** * 验证session是否有效 用于删除过期session */ @Override public void validateSessions() { if (log.isInfoEnabled()) { log.info("invalidation sessions..."); } int invalidCount = 0; int timeout = (int) this.getGlobalSessionTimeout(); if (timeout < 0) { // 永不过期不进行处理 return; } Date expiredDate = DateUtils.addMilliseconds(new Date(), 0 - timeout); ISysUserOnlineService userOnlineService = SpringUtils.getBean(ISysUserOnlineService.class); List userOnlineList = userOnlineService.selectOnlineByExpired(expiredDate); // 批量过期删除 List needOfflineIdList = new ArrayList(); for (SysUserOnline userOnline : userOnlineList) { try { SessionKey key = new DefaultSessionKey(userOnline.getSessionId()); Session session = retrieveSession(key); if (session != null) { throw new InvalidSessionException(); } } catch (InvalidSessionException e) { if (log.isDebugEnabled()) { boolean expired = (e instanceof ExpiredSessionException); String msg = "Invalidated session with id [" + userOnline.getSessionId() + "]" + (expired ? " (expired)" : " (stopped)"); log.debug(msg); } invalidCount++; needOfflineIdList.add(userOnline.getSessionId()); userOnlineService.removeUserCache(userOnline.getLoginName(), userOnline.getSessionId()); } } if (needOfflineIdList.size() > 0) { try { userOnlineService.batchDeleteOnline(needOfflineIdList); } catch (Exception e) { log.error("batch delete db session error.", e); } } if (log.isInfoEnabled()) { String msg = "Finished invalidation session."; if (invalidCount > 0) { msg += " [" + invalidCount + "] sessions were stopped."; } else { msg += " No sessions were stopped."; } log.info(msg); } } @Override protected Collection getActiveSessions() { throw new UnsupportedOperationException("getActiveSessions method not supported"); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java ================================================ package com.ruoyi.framework.shiro.web.session; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.shiro.session.mgt.DefaultSessionManager; import org.apache.shiro.session.mgt.SessionValidationScheduler; import org.apache.shiro.session.mgt.ValidatingSessionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import com.ruoyi.common.utils.Threads; /** * 自定义任务调度器完成 * * @author ruoyi */ @Component public class SpringSessionValidationScheduler implements SessionValidationScheduler { private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class); public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL; /** * 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。 */ @Autowired @Qualifier("scheduledExecutorService") private ScheduledExecutorService executorService; private volatile boolean enabled = false; /** * 会话验证管理器 */ @Autowired @Qualifier("sessionManager") @Lazy private ValidatingSessionManager sessionManager; // 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟 @Value("${shiro.session.validationInterval}") private long sessionValidationInterval; @Override public boolean isEnabled() { return this.enabled; } /** * Specifies how frequently (in milliseconds) this Scheduler will call the * {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions() * ValidatingSessionManager#validateSessions()} method. * *

                    * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}. * * @param sessionValidationInterval */ public void setSessionValidationInterval(long sessionValidationInterval) { this.sessionValidationInterval = sessionValidationInterval; } /** * Starts session validation by creating a spring PeriodicTrigger. */ @Override public void enableSessionValidation() { enabled = true; if (log.isDebugEnabled()) { log.debug("Scheduling session validation job using Spring Scheduler with " + "session validation interval of [" + sessionValidationInterval + "]ms..."); } try { executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (enabled) { sessionManager.validateSessions(); } } }, 1000, sessionValidationInterval * 60 * 1000, TimeUnit.MILLISECONDS); this.enabled = true; if (log.isDebugEnabled()) { log.debug("Session validation job successfully scheduled with Spring Scheduler."); } } catch (Exception e) { if (log.isErrorEnabled()) { log.error("Error starting the Spring Scheduler session validation job. Session validation may not occur.", e); } } } @Override public void disableSessionValidation() { if (log.isDebugEnabled()) { log.debug("Stopping Spring Scheduler session validation job..."); } if (this.enabled) { Threads.shutdownAndAwaitTermination(executorService); } this.enabled = false; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java ================================================ package com.ruoyi.framework.web.domain; import java.net.UnknownHostException; import java.util.LinkedList; import java.util.List; import java.util.Properties; import com.ruoyi.common.utils.Arith; import com.ruoyi.common.utils.IpUtils; import com.ruoyi.framework.web.domain.server.Cpu; import com.ruoyi.framework.web.domain.server.Jvm; import com.ruoyi.framework.web.domain.server.Mem; import com.ruoyi.framework.web.domain.server.Sys; import com.ruoyi.framework.web.domain.server.SysFile; import oshi.SystemInfo; import oshi.hardware.CentralProcessor; import oshi.hardware.CentralProcessor.TickType; import oshi.hardware.GlobalMemory; import oshi.hardware.HardwareAbstractionLayer; import oshi.software.os.FileSystem; import oshi.software.os.OSFileStore; import oshi.software.os.OperatingSystem; import oshi.util.Util; /** * 服务器相关信息 * * @author ruoyi */ public class Server { private static final int OSHI_WAIT_SECOND = 1000; /** * CPU相关信息 */ private Cpu cpu = new Cpu(); /** * 內存相关信息 */ private Mem mem = new Mem(); /** * JVM相关信息 */ private Jvm jvm = new Jvm(); /** * 服务器相关信息 */ private Sys sys = new Sys(); /** * 磁盘相关信息 */ private List sysFiles = new LinkedList(); public Cpu getCpu() { return cpu; } public void setCpu(Cpu cpu) { this.cpu = cpu; } public Mem getMem() { return mem; } public void setMem(Mem mem) { this.mem = mem; } public Jvm getJvm() { return jvm; } public void setJvm(Jvm jvm) { this.jvm = jvm; } public Sys getSys() { return sys; } public void setSys(Sys sys) { this.sys = sys; } public List getSysFiles() { return sysFiles; } public void setSysFiles(List sysFiles) { this.sysFiles = sysFiles; } public void copyTo() throws Exception { SystemInfo si = new SystemInfo(); HardwareAbstractionLayer hal = si.getHardware(); setCpuInfo(hal.getProcessor()); setMemInfo(hal.getMemory()); setSysInfo(); setJvmInfo(); setSysFiles(si.getOperatingSystem()); } /** * 设置CPU信息 */ private void setCpuInfo(CentralProcessor processor) { // CPU信息 long[] prevTicks = processor.getSystemCpuLoadTicks(); Util.sleep(OSHI_WAIT_SECOND); long[] ticks = processor.getSystemCpuLoadTicks(); long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; cpu.setCpuNum(processor.getLogicalProcessorCount()); cpu.setTotal(totalCpu); cpu.setSys(cSys); cpu.setUsed(user); cpu.setWait(iowait); cpu.setFree(idle); } /** * 设置内存信息 */ private void setMemInfo(GlobalMemory memory) { mem.setTotal(memory.getTotal()); mem.setUsed(memory.getTotal() - memory.getAvailable()); mem.setFree(memory.getAvailable()); } /** * 设置服务器信息 */ private void setSysInfo() { Properties props = System.getProperties(); sys.setComputerName(IpUtils.getHostName()); sys.setComputerIp(IpUtils.getHostIp()); sys.setOsName(props.getProperty("os.name")); sys.setOsArch(props.getProperty("os.arch")); sys.setUserDir(props.getProperty("user.dir")); } /** * 设置Java虚拟机 */ private void setJvmInfo() throws UnknownHostException { Properties props = System.getProperties(); jvm.setTotal(Runtime.getRuntime().totalMemory()); jvm.setMax(Runtime.getRuntime().maxMemory()); jvm.setFree(Runtime.getRuntime().freeMemory()); jvm.setVersion(props.getProperty("java.version")); jvm.setHome(props.getProperty("java.home")); } /** * 设置磁盘信息 */ private void setSysFiles(OperatingSystem os) { FileSystem fileSystem = os.getFileSystem(); List fsArray = fileSystem.getFileStores(); for (OSFileStore fs : fsArray) { long free = fs.getUsableSpace(); long total = fs.getTotalSpace(); long used = total - free; SysFile sysFile = new SysFile(); sysFile.setDirName(fs.getMount()); sysFile.setSysTypeName(fs.getType()); sysFile.setTypeName(fs.getName()); sysFile.setTotal(convertFileSize(total)); sysFile.setFree(convertFileSize(free)); sysFile.setUsed(convertFileSize(used)); sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); sysFiles.add(sysFile); } } /** * 字节转换 * * @param size 字节大小 * @return 转换后值 */ public String convertFileSize(long size) { long kb = 1024; long mb = kb * 1024; long gb = mb * 1024; if (size >= gb) { return String.format("%.1f GB", (float) size / gb); } else if (size >= mb) { float f = (float) size / mb; return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); } else if (size >= kb) { float f = (float) size / kb; return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); } else { return String.format("%d B", size); } } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java ================================================ package com.ruoyi.framework.web.domain.server; import com.ruoyi.common.utils.Arith; /** * CPU相关信息 * * @author ruoyi */ public class Cpu { /** * 核心数 */ private int cpuNum; /** * CPU总的使用率 */ private double total; /** * CPU系统使用率 */ private double sys; /** * CPU用户使用率 */ private double used; /** * CPU当前等待率 */ private double wait; /** * CPU当前空闲率 */ private double free; public int getCpuNum() { return cpuNum; } public void setCpuNum(int cpuNum) { this.cpuNum = cpuNum; } public double getTotal() { return Arith.round(Arith.mul(total, 100), 2); } public void setTotal(double total) { this.total = total; } public double getSys() { return Arith.round(Arith.mul(sys / total, 100), 2); } public void setSys(double sys) { this.sys = sys; } public double getUsed() { return Arith.round(Arith.mul(used / total, 100), 2); } public void setUsed(double used) { this.used = used; } public double getWait() { return Arith.round(Arith.mul(wait / total, 100), 2); } public void setWait(double wait) { this.wait = wait; } public double getFree() { return Arith.round(Arith.mul(free / total, 100), 2); } public void setFree(double free) { this.free = free; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java ================================================ package com.ruoyi.framework.web.domain.server; import java.lang.management.ManagementFactory; import com.ruoyi.common.utils.Arith; import com.ruoyi.common.utils.DateUtils; /** * JVM相关信息 * * @author ruoyi */ public class Jvm { /** * 当前JVM占用的内存总数(M) */ private double total; /** * JVM最大可用内存总数(M) */ private double max; /** * JVM空闲内存(M) */ private double free; /** * JDK版本 */ private String version; /** * JDK路径 */ private String home; public double getTotal() { return Arith.div(total, (1024 * 1024), 2); } public void setTotal(double total) { this.total = total; } public double getMax() { return Arith.div(max, (1024 * 1024), 2); } public void setMax(double max) { this.max = max; } public double getFree() { return Arith.div(free, (1024 * 1024), 2); } public void setFree(double free) { this.free = free; } public double getUsed() { return Arith.div(total - free, (1024 * 1024), 2); } public double getUsage() { return Arith.mul(Arith.div(total - free, total, 4), 100); } /** * 获取JDK名称 */ public String getName() { return ManagementFactory.getRuntimeMXBean().getVmName(); } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getHome() { return home; } public void setHome(String home) { this.home = home; } /** * JDK启动时间 */ public String getStartTime() { return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); } /** * JDK运行时间 */ public String getRunTime() { return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate()); } /** * 运行参数 */ public String getInputArgs() { return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java ================================================ package com.ruoyi.framework.web.domain.server; import com.ruoyi.common.utils.Arith; /** * 內存相关信息 * * @author ruoyi */ public class Mem { /** * 内存总量 */ private double total; /** * 已用内存 */ private double used; /** * 剩余内存 */ private double free; public double getTotal() { return Arith.div(total, (1024 * 1024 * 1024), 2); } public void setTotal(long total) { this.total = total; } public double getUsed() { return Arith.div(used, (1024 * 1024 * 1024), 2); } public void setUsed(long used) { this.used = used; } public double getFree() { return Arith.div(free, (1024 * 1024 * 1024), 2); } public void setFree(long free) { this.free = free; } public double getUsage() { return Arith.mul(Arith.div(used, total, 4), 100); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java ================================================ package com.ruoyi.framework.web.domain.server; /** * 系统相关信息 * * @author ruoyi */ public class Sys { /** * 服务器名称 */ private String computerName; /** * 服务器Ip */ private String computerIp; /** * 项目路径 */ private String userDir; /** * 操作系统 */ private String osName; /** * 系统架构 */ private String osArch; public String getComputerName() { return computerName; } public void setComputerName(String computerName) { this.computerName = computerName; } public String getComputerIp() { return computerIp; } public void setComputerIp(String computerIp) { this.computerIp = computerIp; } public String getUserDir() { return userDir; } public void setUserDir(String userDir) { this.userDir = userDir; } public String getOsName() { return osName; } public void setOsName(String osName) { this.osName = osName; } public String getOsArch() { return osArch; } public void setOsArch(String osArch) { this.osArch = osArch; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java ================================================ package com.ruoyi.framework.web.domain.server; /** * 系统文件相关信息 * * @author ruoyi */ public class SysFile { /** * 盘符路径 */ private String dirName; /** * 盘符类型 */ private String sysTypeName; /** * 文件类型 */ private String typeName; /** * 总大小 */ private String total; /** * 剩余大小 */ private String free; /** * 已经使用量 */ private String used; /** * 资源的使用率 */ private double usage; public String getDirName() { return dirName; } public void setDirName(String dirName) { this.dirName = dirName; } public String getSysTypeName() { return sysTypeName; } public void setSysTypeName(String sysTypeName) { this.sysTypeName = sysTypeName; } public String getTypeName() { return typeName; } public void setTypeName(String typeName) { this.typeName = typeName; } public String getTotal() { return total; } public void setTotal(String total) { this.total = total; } public String getFree() { return free; } public void setFree(String free) { this.free = free; } public String getUsed() { return used; } public void setUsed(String used) { this.used = used; } public double getUsage() { return usage; } public void setUsage(double usage) { this.usage = usage; } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java ================================================ package com.ruoyi.framework.web.exception; import jakarta.servlet.http.HttpServletRequest; import org.apache.shiro.authz.AuthorizationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.validation.BindException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MissingPathVariableException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.servlet.ModelAndView; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.DemoModeException; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.html.EscapeUtil; import com.ruoyi.common.utils.security.PermissionUtils; /** * 全局异常处理器 * * @author ruoyi */ @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 权限校验异常(ajax请求返回json,redirect请求跳转页面) */ @ExceptionHandler(AuthorizationException.class) public Object handleAuthorizationException(AuthorizationException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); if (ServletUtils.isAjaxRequest(request)) { return AjaxResult.error(PermissionUtils.getMsg(e.getMessage())); } else { return new ModelAndView("error/unauth"); } } /** * 请求方式不支持 */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); return AjaxResult.error(e.getMessage()); } /** * 拦截未知的运行时异常 */ @ExceptionHandler(RuntimeException.class) public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',发生未知异常.", requestURI, e); return AjaxResult.error(e.getMessage()); } /** * 系统异常 */ @ExceptionHandler(Exception.class) public AjaxResult handleException(Exception e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',发生系统异常.", requestURI, e); return AjaxResult.error(e.getMessage()); } /** * 业务异常 */ @ExceptionHandler(ServiceException.class) public Object handleServiceException(ServiceException e, HttpServletRequest request) { log.error(e.getMessage(), e); if (ServletUtils.isAjaxRequest(request)) { return AjaxResult.error(e.getMessage()); } else { return new ModelAndView("error/service", "errorMessage", e.getMessage()); } } /** * 请求路径中缺少必需的路径变量 */ @ExceptionHandler(MissingPathVariableException.class) public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); } /** * 请求参数类型不匹配 */ @ExceptionHandler(MethodArgumentTypeMismatchException.class) public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); String value = Convert.toStr(e.getValue()); if (StringUtils.isNotEmpty(value)) { value = EscapeUtil.clean(value); } log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value)); } /** * 自定义验证异常 */ @ExceptionHandler(BindException.class) public AjaxResult handleBindException(BindException e) { log.error(e.getMessage(), e); String message = e.getAllErrors().get(0).getDefaultMessage(); return AjaxResult.error(message); } /** * 演示模式异常 */ @ExceptionHandler(DemoModeException.class) public AjaxResult handleDemoModeException(DemoModeException e) { return AjaxResult.error("演示模式,不允许操作"); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java ================================================ package com.ruoyi.framework.web.service; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang3.ArrayUtils; import org.springframework.stereotype.Service; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.CacheUtils; /** * 缓存操作处理 * * @author ruoyi */ @Service public class CacheService { /** * 获取所有缓存名称 * * @return 缓存列表 */ public String[] getCacheNames() { String[] cacheNames = CacheUtils.getCacheNames(); return ArrayUtils.removeElement(cacheNames, Constants.SYS_AUTH_CACHE); } /** * 根据缓存名称获取所有键名 * * @param cacheName 缓存名称 * @return 键名列表 */ public Set getCacheKeys(String cacheName) { return new TreeSet<>(CacheUtils.getCache(cacheName).keys()); } /** * 根据缓存名称和键名获取内容值 * * @param cacheName 缓存名称 * @param cacheKey 键名 * @return 键值 */ public Object getCacheValue(String cacheName, String cacheKey) { return CacheUtils.get(cacheName, cacheKey); } /** * 根据名称删除缓存信息 * * @param cacheName 缓存名称 */ public void clearCacheName(String cacheName) { CacheUtils.removeAll(cacheName); } /** * 根据名称和键名删除缓存信息 * * @param cacheName 缓存名称 * @param cacheKey 键名 */ public void clearCacheKey(String cacheName, String cacheKey) { CacheUtils.remove(cacheName, cacheKey); } /** * 清理所有缓存 */ public void clearAll() { String[] cacheNames = getCacheNames(); for (String cacheName : cacheNames) { CacheUtils.removeAll(cacheName); } } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/ConfigService.java ================================================ package com.ruoyi.framework.web.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.system.service.ISysConfigService; /** * RuoYi首创 html调用 thymeleaf 实现参数管理 * * @author ruoyi */ @Service("config") public class ConfigService { @Autowired private ISysConfigService configService; /** * 根据键名查询参数配置信息 * * @param configKey 参数键名 * @return 参数键值 */ public String getKey(String configKey) { return configService.selectConfigByKey(configKey); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/DictService.java ================================================ package com.ruoyi.framework.web.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.system.service.ISysDictDataService; import com.ruoyi.system.service.ISysDictTypeService; /** * RuoYi首创 html调用 thymeleaf 实现字典读取 * * @author ruoyi */ @Service("dict") public class DictService { @Autowired private ISysDictTypeService dictTypeService; @Autowired private ISysDictDataService dictDataService; /** * 根据字典类型查询字典数据信息 * * @param dictType 字典类型 * @return 参数键值 */ public List getType(String dictType) { return dictTypeService.selectDictDataByType(dictType); } /** * 根据字典类型和字典键值查询字典数据信息 * * @param dictType 字典类型 * @param dictValue 字典键值 * @return 字典标签 */ public String getLabel(String dictType, String dictValue) { return dictDataService.selectDictLabel(dictType, dictValue); } } ================================================ FILE: ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java ================================================ package com.ruoyi.framework.web.service; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.ruoyi.common.utils.StringUtils; /** * RuoYi首创 js调用 thymeleaf 实现按钮权限可见性 * * @author ruoyi */ @Service("permission") public class PermissionService { private static final Logger log = LoggerFactory.getLogger(PermissionService.class); /** 没有权限,hidden用于前端隐藏按钮 */ public static final String NOACCESS = "hidden"; private static final String ROLE_DELIMETER = ","; private static final String PERMISSION_DELIMETER = ","; /** * 验证用户是否具备某权限,无权限返回hidden用于前端隐藏(如需返回Boolean使用isPermitted) * * @param permission 权限字符串 * @return 用户是否具备某权限 */ public String hasPermi(String permission) { return isPermitted(permission) ? StringUtils.EMPTY : NOACCESS; } /** * 验证用户是否不具备某权限,与 hasPermi逻辑相反。无权限返回hidden用于前端隐藏(如需返回Boolean使用isLacksPermitted) * * @param permission 权限字符串 * @return 用户是否不具备某权限 */ public String lacksPermi(String permission) { return isLacksPermitted(permission) ? StringUtils.EMPTY : NOACCESS; } /** * 验证用户是否具有以下任意一个权限,无权限返回hidden用于隐藏(如需返回Boolean使用hasAnyPermissions) * * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 * @return 用户是否具有以下任意一个权限 */ public String hasAnyPermi(String permissions) { return hasAnyPermissions(permissions, PERMISSION_DELIMETER) ? StringUtils.EMPTY : NOACCESS; } /** * 验证用户是否具备某角色,无权限返回hidden用于隐藏(如需返回Boolean使用isRole) * * @param role 角色字符串 * @return 用户是否具备某角色 */ public String hasRole(String role) { return isRole(role) ? StringUtils.EMPTY : NOACCESS; } /** * 验证用户是否不具备某角色,与hasRole逻辑相反。无权限返回hidden用于隐藏(如需返回Boolean使用isLacksRole) * * @param role 角色字符串 * @return 用户是否不具备某角色 */ public String lacksRole(String role) { return isLacksRole(role) ? StringUtils.EMPTY : NOACCESS; } /** * 验证用户是否具有以下任意一个角色,无权限返回hidden用于隐藏(如需返回Boolean使用isAnyRoles) * * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 * @return 用户是否具有以下任意一个角色 */ public String hasAnyRoles(String roles) { return isAnyRoles(roles, ROLE_DELIMETER) ? StringUtils.EMPTY : NOACCESS; } /** * 验证用户是否认证通过或已记住的用户。 * * @return 用户是否认证通过或已记住的用户 */ public boolean isUser() { Subject subject = SecurityUtils.getSubject(); return subject != null && subject.getPrincipal() != null; } /** * 判断用户是否拥有某个权限 * * @param permission 权限字符串 * @return 用户是否具备某权限 */ public boolean isPermitted(String permission) { return SecurityUtils.getSubject().isPermitted(permission); } /** * 判断用户是否不具备某权限,与 isPermitted逻辑相反。 * * @param permission 权限名称 * @return 用户是否不具备某权限 */ public boolean isLacksPermitted(String permission) { return isPermitted(permission) != true; } /** * 验证用户是否具有以下任意一个权限。 * * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 * @return 用户是否具有以下任意一个权限 */ public boolean hasAnyPermissions(String permissions) { return hasAnyPermissions(permissions, PERMISSION_DELIMETER); } /** * 验证用户是否具有以下任意一个权限。 * * @param permissions 以 delimeter 为分隔符的权限列表 * @param delimeter 权限列表分隔符 * @return 用户是否具有以下任意一个权限 */ public boolean hasAnyPermissions(String permissions, String delimeter) { Subject subject = SecurityUtils.getSubject(); if (subject != null) { if (delimeter == null || delimeter.length() == 0) { delimeter = PERMISSION_DELIMETER; } for (String permission : permissions.split(delimeter)) { if (permission != null && subject.isPermitted(permission.trim()) == true) { return true; } } } return false; } /** * 判断用户是否拥有某个角色 * * @param role 角色字符串 * @return 用户是否具备某角色 */ public boolean isRole(String role) { return SecurityUtils.getSubject().hasRole(role); } /** * 验证用户是否不具备某角色,与 isRole逻辑相反。 * * @param role 角色名称 * @return 用户是否不具备某角色 */ public boolean isLacksRole(String role) { return isRole(role) != true; } /** * 验证用户是否具有以下任意一个角色。 * * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 * @return 用户是否具有以下任意一个角色 */ public boolean isAnyRoles(String roles) { return isAnyRoles(roles, ROLE_DELIMETER); } /** * 验证用户是否具有以下任意一个角色。 * * @param roles 以 delimeter 为分隔符的角色列表 * @param delimeter 角色列表分隔符 * @return 用户是否具有以下任意一个角色 */ public boolean isAnyRoles(String roles, String delimeter) { Subject subject = SecurityUtils.getSubject(); if (subject != null) { if (delimeter == null || delimeter.length() == 0) { delimeter = ROLE_DELIMETER; } for (String role : roles.split(delimeter)) { if (subject.hasRole(role.trim()) == true) { return true; } } } return false; } /** * 返回用户属性值 * * @param property 属性名称 * @return 用户属性值 */ public Object getPrincipalProperty(String property) { Subject subject = SecurityUtils.getSubject(); if (subject != null) { Object principal = subject.getPrincipal(); try { BeanInfo bi = Introspector.getBeanInfo(principal.getClass()); for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { if (pd.getName().equals(property) == true) { return pd.getReadMethod().invoke(principal, (Object[]) null); } } } catch (Exception e) { log.error("Error reading property [{}] from principal of type [{}]", property, principal.getClass().getName()); } } return null; } } ================================================ FILE: ruoyi-generator/pom.xml ================================================ ruoyi com.ruoyi 4.8.2 4.0.0 ruoyi-generator generator代码生成 org.apache.velocity velocity-engine-core com.ruoyi ruoyi-common com.alibaba druid-spring-boot-4-starter ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java ================================================ package com.ruoyi.generator.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; /** * 读取代码生成相关配置 * * @author ruoyi */ @Component @ConfigurationProperties(prefix = "gen") @PropertySource(value = { "classpath:generator.yml" }) public class GenConfig { /** 作者 */ public static String author; /** 生成包路径 */ public static String packageName; /** 自动去除表前缀 */ public static boolean autoRemovePre; /** 表前缀 */ public static String tablePrefix; /** 是否允许生成文件覆盖到本地(自定义路径) */ public static boolean allowOverwrite; public static String getAuthor() { return author; } @Value("${author}") public void setAuthor(String author) { GenConfig.author = author; } public static String getPackageName() { return packageName; } @Value("${packageName}") public void setPackageName(String packageName) { GenConfig.packageName = packageName; } public static boolean getAutoRemovePre() { return autoRemovePre; } @Value("${autoRemovePre}") public void setAutoRemovePre(boolean autoRemovePre) { GenConfig.autoRemovePre = autoRemovePre; } public static String getTablePrefix() { return tablePrefix; } @Value("${tablePrefix}") public void setTablePrefix(String tablePrefix) { GenConfig.tablePrefix = tablePrefix; } public static boolean isAllowOverwrite() { return allowOverwrite; } @Value("${allowOverwrite}") public void setAllowOverwrite(boolean allowOverwrite) { GenConfig.allowOverwrite = allowOverwrite; } } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java ================================================ package com.ruoyi.generator.controller; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; import com.alibaba.fastjson.JSON; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.CxSelect; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.security.PermissionUtils; import com.ruoyi.common.utils.sql.SqlUtil; import com.ruoyi.generator.config.GenConfig; import com.ruoyi.generator.domain.GenTable; import com.ruoyi.generator.domain.GenTableColumn; import com.ruoyi.generator.service.IGenTableColumnService; import com.ruoyi.generator.service.IGenTableService; /** * 代码生成 操作处理 * * @author ruoyi */ @Controller @RequestMapping("/tool/gen") public class GenController extends BaseController { private String prefix = "tool/gen"; @Autowired private IGenTableService genTableService; @Autowired private IGenTableColumnService genTableColumnService; @RequiresPermissions("tool:gen:view") @GetMapping() public String gen() { return prefix + "/gen"; } /** * 查询代码生成列表 */ @RequiresPermissions("tool:gen:list") @PostMapping("/list") @ResponseBody public TableDataInfo genList(GenTable genTable) { startPage(); List list = genTableService.selectGenTableList(genTable); return getDataTable(list); } /** * 查询数据库列表 */ @RequiresPermissions("tool:gen:list") @PostMapping("/db/list") @ResponseBody public TableDataInfo dataList(GenTable genTable) { startPage(); List list = genTableService.selectDbTableList(genTable); return getDataTable(list); } /** * 查询数据表字段列表 */ @RequiresPermissions("tool:gen:list") @PostMapping("/column/list") @ResponseBody public TableDataInfo columnList(GenTableColumn genTableColumn) { TableDataInfo dataInfo = new TableDataInfo(); List list = genTableColumnService.selectGenTableColumnListByTableId(genTableColumn); dataInfo.setRows(list); dataInfo.setTotal(list.size()); return dataInfo; } /** * 导入表结构 */ @RequiresPermissions("tool:gen:list") @GetMapping("/importTable") public String importTable() { return prefix + "/importTable"; } /** * 创建表结构 */ @GetMapping("/createTable") public String createTable() { return prefix + "/createTable"; } /** * 导入表结构(保存) */ @RequiresPermissions("tool:gen:list") @Log(title = "代码生成", businessType = BusinessType.IMPORT) @PostMapping("/importTable") @ResponseBody public AjaxResult importTableSave(String tables) { String[] tableNames = Convert.toStrArray(tables); // 查询表信息 List tableList = genTableService.selectDbTableListByNames(tableNames); String operName = Convert.toStr(PermissionUtils.getPrincipalProperty("loginName")); genTableService.importGenTable(tableList, operName); return AjaxResult.success(); } /** * 修改代码生成业务 */ @RequiresPermissions("tool:gen:edit") @GetMapping("/edit/{tableId}") public String edit(@PathVariable("tableId") Long tableId, ModelMap mmap) { GenTable table = genTableService.selectGenTableById(tableId); List genTables = genTableService.selectGenTableAll(); List cxSelect = new ArrayList(); for (GenTable genTable : genTables) { if (!StringUtils.equals(table.getTableName(), genTable.getTableName())) { CxSelect cxTable = new CxSelect(genTable.getTableName(), genTable.getTableName() + ':' + genTable.getTableComment()); List cxColumns = new ArrayList(); for (GenTableColumn tableColumn : genTable.getColumns()) { cxColumns.add(new CxSelect(tableColumn.getColumnName(), tableColumn.getColumnName() + ':' + tableColumn.getColumnComment())); } cxTable.setS(cxColumns); cxSelect.add(cxTable); } } mmap.put("table", table); mmap.put("data", JSON.toJSON(cxSelect)); return prefix + "/edit"; } /** * 修改保存代码生成业务 */ @RequiresPermissions("tool:gen:edit") @Log(title = "代码生成", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated GenTable genTable) { genTableService.validateEdit(genTable); genTableService.updateGenTable(genTable); return AjaxResult.success(); } @RequiresPermissions("tool:gen:remove") @Log(title = "代码生成", businessType = BusinessType.DELETE) @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { genTableService.deleteGenTableByIds(ids); return AjaxResult.success(); } @RequiresRoles("admin") @Log(title = "创建表", businessType = BusinessType.OTHER) @PostMapping("/createTable") @ResponseBody public AjaxResult create(String sql) { try { SqlUtil.filterKeyword(sql); List sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql); List tableNames = new ArrayList<>(); for (SQLStatement sqlStatement : sqlStatements) { if (sqlStatement instanceof MySqlCreateTableStatement) { MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement; if (genTableService.createTable(createTableStatement.toString())) { String tableName = createTableStatement.getTableName().replaceAll("`", ""); tableNames.add(tableName); } } } List tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); String operName = Convert.toStr(PermissionUtils.getPrincipalProperty("loginName")); genTableService.importGenTable(tableList, operName); return AjaxResult.success(); } catch (Exception e) { logger.error(e.getMessage(), e); return AjaxResult.error("创建表结构异常"); } } /** * 预览代码 */ @RequiresPermissions("tool:gen:preview") @GetMapping("/preview/{tableId}") @ResponseBody public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException { Map dataMap = genTableService.previewCode(tableId); return AjaxResult.success(dataMap); } /** * 生成代码(下载方式) */ @RequiresPermissions("tool:gen:code") @Log(title = "代码生成", businessType = BusinessType.GENCODE) @GetMapping("/download/{tableName}") public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException { byte[] data = genTableService.downloadCode(tableName); genCode(response, data); } /** * 生成代码(自定义路径) */ @RequiresPermissions("tool:gen:code") @Log(title = "代码生成", businessType = BusinessType.GENCODE) @GetMapping("/genCode/{tableName}") @ResponseBody public AjaxResult genCode(@PathVariable("tableName") String tableName) { if (!GenConfig.isAllowOverwrite()) { return AjaxResult.error("【系统预设】不允许生成文件覆盖到本地"); } genTableService.generatorCode(tableName); return AjaxResult.success(); } /** * 同步数据库 */ @RequiresPermissions("tool:gen:edit") @Log(title = "代码生成", businessType = BusinessType.UPDATE) @GetMapping("/synchDb/{tableName}") @ResponseBody public AjaxResult synchDb(@PathVariable("tableName") String tableName) { genTableService.synchDb(tableName); return AjaxResult.success(); } /** * 批量生成代码 */ @RequiresPermissions("tool:gen:code") @Log(title = "代码生成", businessType = BusinessType.GENCODE) @GetMapping("/batchGenCode") @ResponseBody public void batchGenCode(HttpServletResponse response, String tables) throws IOException { String[] tableNames = Convert.toStrArray(tables); byte[] data = genTableService.downloadCode(tableNames); genCode(response, data); } /** * 生成zip文件 */ private void genCode(HttpServletResponse response, byte[] data) throws IOException { response.reset(); response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\""); response.addHeader("Content-Length", "" + data.length); response.setContentType("application/octet-stream; charset=UTF-8"); IOUtils.write(data, response.getOutputStream()); } } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java ================================================ package com.ruoyi.generator.domain; import java.util.List; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import org.apache.commons.lang3.ArrayUtils; import com.ruoyi.common.constant.GenConstants; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.utils.StringUtils; /** * 业务表 gen_table * * @author ruoyi */ public class GenTable extends BaseEntity { private static final long serialVersionUID = 1L; /** 编号 */ private Long tableId; /** 表名称 */ @NotBlank(message = "表名称不能为空") private String tableName; /** 表描述 */ @NotBlank(message = "表描述不能为空") private String tableComment; /** 关联父表的表名 */ private String subTableName; /** 本表关联父表的外键名 */ private String subTableFkName; /** 实体类名称(首字母大写) */ @NotBlank(message = "实体类名称不能为空") private String className; /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ private String tplCategory; /** 生成包路径 */ @NotBlank(message = "生成包路径不能为空") private String packageName; /** 生成模块名 */ @NotBlank(message = "生成模块名不能为空") private String moduleName; /** 生成业务名 */ @NotBlank(message = "生成业务名不能为空") private String businessName; /** 生成功能名 */ @NotBlank(message = "生成功能名不能为空") private String functionName; /** 生成作者 */ @NotBlank(message = "作者不能为空") private String functionAuthor; /** 表单布局(单列 双列 三列) */ private int formColNum; /** 生成代码方式(0zip压缩包 1自定义路径) */ private String genType; /** 生成路径(不填默认项目路径) */ private String genPath; /** 主键信息 */ private GenTableColumn pkColumn; /** 子表信息 */ private GenTable subTable; /** 表列信息 */ @Valid private List columns; /** 其它生成选项 */ private String options; /** 树编码字段 */ private String treeCode; /** 树父编码字段 */ private String treeParentCode; /** 树名称字段 */ private String treeName; /** 上级菜单ID字段 */ private String parentMenuId; /** 上级菜单名称字段 */ private String parentMenuName; public Long getTableId() { return tableId; } public void setTableId(Long tableId) { this.tableId = tableId; } public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public String getTableComment() { return tableComment; } public void setTableComment(String tableComment) { this.tableComment = tableComment; } public String getSubTableName() { return subTableName; } public void setSubTableName(String subTableName) { this.subTableName = subTableName; } public String getSubTableFkName() { return subTableFkName; } public void setSubTableFkName(String subTableFkName) { this.subTableFkName = subTableFkName; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getTplCategory() { return tplCategory; } public void setTplCategory(String tplCategory) { this.tplCategory = tplCategory; } public String getPackageName() { return packageName; } public void setPackageName(String packageName) { this.packageName = packageName; } public String getModuleName() { return moduleName; } public void setModuleName(String moduleName) { this.moduleName = moduleName; } public String getBusinessName() { return businessName; } public void setBusinessName(String businessName) { this.businessName = businessName; } public String getFunctionName() { return functionName; } public void setFunctionName(String functionName) { this.functionName = functionName; } public String getFunctionAuthor() { return functionAuthor; } public void setFunctionAuthor(String functionAuthor) { this.functionAuthor = functionAuthor; } public int getFormColNum() { return formColNum; } public void setFormColNum(int formColNum) { this.formColNum = formColNum; } public String getGenType() { return genType; } public void setGenType(String genType) { this.genType = genType; } public String getGenPath() { return genPath; } public void setGenPath(String genPath) { this.genPath = genPath; } public GenTableColumn getPkColumn() { return pkColumn; } public void setPkColumn(GenTableColumn pkColumn) { this.pkColumn = pkColumn; } public GenTable getSubTable() { return subTable; } public void setSubTable(GenTable subTable) { this.subTable = subTable; } public List getColumns() { return columns; } public void setColumns(List columns) { this.columns = columns; } public String getOptions() { return options; } public void setOptions(String options) { this.options = options; } public String getTreeCode() { return treeCode; } public void setTreeCode(String treeCode) { this.treeCode = treeCode; } public String getTreeParentCode() { return treeParentCode; } public void setTreeParentCode(String treeParentCode) { this.treeParentCode = treeParentCode; } public String getTreeName() { return treeName; } public void setTreeName(String treeName) { this.treeName = treeName; } public String getParentMenuId() { return parentMenuId; } public void setParentMenuId(String parentMenuId) { this.parentMenuId = parentMenuId; } public String getParentMenuName() { return parentMenuName; } public void setParentMenuName(String parentMenuName) { this.parentMenuName = parentMenuName; } public boolean isSub() { return isSub(this.tplCategory); } public static boolean isSub(String tplCategory) { return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); } public boolean isTree() { return isTree(this.tplCategory); } public static boolean isTree(String tplCategory) { return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); } public boolean isCrud() { return isCrud(this.tplCategory); } public static boolean isCrud(String tplCategory) { return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); } public boolean isSuperColumn(String javaField) { return isSuperColumn(this.tplCategory, javaField); } public static boolean isSuperColumn(String tplCategory, String javaField) { if (isTree(tplCategory)) { return StringUtils.equalsAnyIgnoreCase(javaField, ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY)); } return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); } } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java ================================================ package com.ruoyi.generator.domain; import jakarta.validation.constraints.NotBlank; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.utils.StringUtils; /** * 代码生成业务字段表 gen_table_column * * @author ruoyi */ public class GenTableColumn extends BaseEntity { private static final long serialVersionUID = 1L; /** 编号 */ private Long columnId; /** 归属表编号 */ private Long tableId; /** 列名称 */ private String columnName; /** 列描述 */ private String columnComment; /** 列类型 */ private String columnType; /** JAVA类型 */ private String javaType; /** JAVA字段名 */ @NotBlank(message = "Java属性不能为空") private String javaField; /** 是否主键(1是) */ private String isPk; /** 是否自增(1是) */ private String isIncrement; /** 是否必填(1是) */ private String isRequired; /** 是否为插入字段(1是) */ private String isInsert; /** 是否编辑字段(1是) */ private String isEdit; /** 是否列表字段(1是) */ private String isList; /** 是否查询字段(1是) */ private String isQuery; /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ private String queryType; /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、upload上传控件、summernote富文本控件) */ private String htmlType; /** 字典类型 */ private String dictType = ""; /** 排序 */ private Integer sort; public void setColumnId(Long columnId) { this.columnId = columnId; } public Long getColumnId() { return columnId; } public void setTableId(Long tableId) { this.tableId = tableId; } public Long getTableId() { return tableId; } public void setColumnName(String columnName) { this.columnName = columnName; } public String getColumnName() { return columnName; } public void setColumnComment(String columnComment) { this.columnComment = columnComment; } public String getColumnComment() { return columnComment; } public void setColumnType(String columnType) { this.columnType = columnType; } public String getColumnType() { return columnType; } public void setJavaType(String javaType) { this.javaType = javaType; } public String getJavaType() { return javaType; } public void setJavaField(String javaField) { this.javaField = javaField; } public String getJavaField() { return javaField; } public String getCapJavaField() { return StringUtils.capitalize(javaField); } public void setIsPk(String isPk) { this.isPk = isPk; } public String getIsPk() { return isPk; } public boolean isPk() { return isPk(this.isPk); } public boolean isPk(String isPk) { return isPk != null && StringUtils.equals("1", isPk); } public String getIsIncrement() { return isIncrement; } public void setIsIncrement(String isIncrement) { this.isIncrement = isIncrement; } public boolean isIncrement() { return isIncrement(this.isIncrement); } public boolean isIncrement(String isIncrement) { return isIncrement != null && StringUtils.equals("1", isIncrement); } public void setIsRequired(String isRequired) { this.isRequired = isRequired; } public String getIsRequired() { return isRequired; } public boolean isRequired() { return isRequired(this.isRequired); } public boolean isRequired(String isRequired) { return isRequired != null && StringUtils.equals("1", isRequired); } public void setIsInsert(String isInsert) { this.isInsert = isInsert; } public String getIsInsert() { return isInsert; } public boolean isInsert() { return isInsert(this.isInsert); } public boolean isInsert(String isInsert) { return isInsert != null && StringUtils.equals("1", isInsert); } public void setIsEdit(String isEdit) { this.isEdit = isEdit; } public String getIsEdit() { return isEdit; } public boolean isEdit() { return isInsert(this.isEdit); } public boolean isEdit(String isEdit) { return isEdit != null && StringUtils.equals("1", isEdit); } public void setIsList(String isList) { this.isList = isList; } public String getIsList() { return isList; } public boolean isList() { return isList(this.isList); } public boolean isList(String isList) { return isList != null && StringUtils.equals("1", isList); } public void setIsQuery(String isQuery) { this.isQuery = isQuery; } public String getIsQuery() { return isQuery; } public boolean isQuery() { return isQuery(this.isQuery); } public boolean isQuery(String isQuery) { return isQuery != null && StringUtils.equals("1", isQuery); } public void setQueryType(String queryType) { this.queryType = queryType; } public String getQueryType() { return queryType; } public String getHtmlType() { return htmlType; } public void setHtmlType(String htmlType) { this.htmlType = htmlType; } public void setDictType(String dictType) { this.dictType = dictType; } public String getDictType() { return dictType; } public void setSort(Integer sort) { this.sort = sort; } public Integer getSort() { return sort; } public boolean isSuperColumn() { return isSuperColumn(this.javaField); } public static boolean isSuperColumn(String javaField) { return StringUtils.equalsAnyIgnoreCase(javaField, // BaseEntity "createBy", "createTime", "updateBy", "updateTime", "remark", // TreeEntity "parentName", "parentId", "orderNum", "ancestors"); } public boolean isUsableColumn() { return isUsableColumn(javaField); } public static boolean isUsableColumn(String javaField) { // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单 return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark"); } public String readConverterExp() { String remarks = StringUtils.substringBetween(this.columnComment, "(", ")"); StringBuffer sb = new StringBuffer(); if (StringUtils.isNotEmpty(remarks)) { for (String value : remarks.split(" ")) { if (StringUtils.isNotEmpty(value)) { Object startStr = value.subSequence(0, 1); String endStr = value.substring(1); sb.append("").append(startStr).append("=").append(endStr).append(","); } } return sb.deleteCharAt(sb.length() - 1).toString(); } else { return this.columnComment; } } } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java ================================================ package com.ruoyi.generator.mapper; import java.util.List; import com.ruoyi.generator.domain.GenTableColumn; /** * 业务字段 数据层 * * @author ruoyi */ public interface GenTableColumnMapper { /** * 根据表名称查询列信息 * * @param tableName 表名称 * @return 列信息 */ public List selectDbTableColumnsByName(String tableName); /** * 查询业务字段列表 * * @param genTableColumn 业务字段信息 * @return 业务字段集合 */ public List selectGenTableColumnListByTableId(GenTableColumn genTableColumn); /** * 新增业务字段 * * @param genTableColumn 业务字段信息 * @return 结果 */ public int insertGenTableColumn(GenTableColumn genTableColumn); /** * 修改业务字段 * * @param genTableColumn 业务字段信息 * @return 结果 */ public int updateGenTableColumn(GenTableColumn genTableColumn); /** * 删除业务字段 * * @param genTableColumns 列数据 * @return 结果 */ public int deleteGenTableColumns(List genTableColumns); /** * 批量删除业务字段 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteGenTableColumnByIds(Long[] ids); } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java ================================================ package com.ruoyi.generator.mapper; import java.util.List; import com.ruoyi.generator.domain.GenTable; /** * 业务 数据层 * * @author ruoyi */ public interface GenTableMapper { /** * 查询业务列表 * * @param genTable 业务信息 * @return 业务集合 */ public List selectGenTableList(GenTable genTable); /** * 查询据库列表 * * @param genTable 业务信息 * @return 数据库表集合 */ public List selectDbTableList(GenTable genTable); /** * 查询据库列表 * * @param tableNames 表名称组 * @return 数据库表集合 */ public List selectDbTableListByNames(String[] tableNames); /** * 查询所有表信息 * * @return 表信息集合 */ public List selectGenTableAll(); /** * 查询表ID业务信息 * * @param id 业务ID * @return 业务信息 */ public GenTable selectGenTableById(Long id); /** * 查询表名称业务信息 * * @param tableName 表名称 * @return 业务信息 */ public GenTable selectGenTableByName(String tableName); /** * 新增业务 * * @param genTable 业务信息 * @return 结果 */ public int insertGenTable(GenTable genTable); /** * 修改业务 * * @param genTable 业务信息 * @return 结果 */ public int updateGenTable(GenTable genTable); /** * 批量删除业务 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteGenTableByIds(Long[] ids); /** * 创建表 * * @param sql * @return 结果 */ public int createTable(String sql); } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java ================================================ package com.ruoyi.generator.service; import java.util.List; import com.ruoyi.generator.domain.GenTableColumn; /** * 业务字段 服务层 * * @author ruoyi */ public interface IGenTableColumnService { /** * 查询业务字段列表 * * @param genTableColumn 业务字段信息 * @return 业务字段集合 */ public List selectGenTableColumnListByTableId(GenTableColumn genTableColumn); /** * 新增业务字段 * * @param genTableColumn 业务字段信息 * @return 结果 */ public int insertGenTableColumn(GenTableColumn genTableColumn); /** * 修改业务字段 * * @param genTableColumn 业务字段信息 * @return 结果 */ public int updateGenTableColumn(GenTableColumn genTableColumn); /** * 删除业务字段信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteGenTableColumnByIds(String ids); } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java ================================================ package com.ruoyi.generator.service; import java.util.List; import java.util.Map; import com.ruoyi.generator.domain.GenTable; /** * 业务 服务层 * * @author ruoyi */ public interface IGenTableService { /** * 查询业务列表 * * @param genTable 业务信息 * @return 业务集合 */ public List selectGenTableList(GenTable genTable); /** * 查询据库列表 * * @param genTable 业务信息 * @return 数据库表集合 */ public List selectDbTableList(GenTable genTable); /** * 查询据库列表 * * @param tableNames 表名称组 * @return 数据库表集合 */ public List selectDbTableListByNames(String[] tableNames); /** * 查询所有表信息 * * @return 表信息集合 */ public List selectGenTableAll(); /** * 查询业务信息 * * @param id 业务ID * @return 业务信息 */ public GenTable selectGenTableById(Long id); /** * 修改业务 * * @param genTable 业务信息 * @return 结果 */ public void updateGenTable(GenTable genTable); /** * 删除业务信息 * * @param ids 需要删除的数据ID * @return 结果 */ public void deleteGenTableByIds(String ids); /** * 创建表 * * @param sql 创建表语句 * @return 结果 */ public boolean createTable(String sql); /** * 导入表结构 * * @param tableList 导入表列表 * @param operName 操作人员 */ public void importGenTable(List tableList, String operName); /** * 预览代码 * * @param tableId 表编号 * @return 预览数据列表 */ public Map previewCode(Long tableId); /** * 生成代码(下载方式) * * @param tableName 表名称 * @return 数据 */ public byte[] downloadCode(String tableName); /** * 生成代码(自定义路径) * * @param tableName 表名称 */ public void generatorCode(String tableName); /** * 同步数据库 * * @param tableName 表名称 */ public void synchDb(String tableName); /** * 批量生成代码(下载方式) * * @param tableNames 表数组 * @return 数据 */ public byte[] downloadCode(String[] tableNames); /** * 修改保存参数校验 * * @param genTable 业务信息 */ public void validateEdit(GenTable genTable); } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableColumnServiceImpl.java ================================================ package com.ruoyi.generator.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.core.text.Convert; import com.ruoyi.generator.domain.GenTableColumn; import com.ruoyi.generator.mapper.GenTableColumnMapper; import com.ruoyi.generator.service.IGenTableColumnService; /** * 业务字段 服务层实现 * * @author ruoyi */ @Service public class GenTableColumnServiceImpl implements IGenTableColumnService { @Autowired private GenTableColumnMapper genTableColumnMapper; /** * 查询业务字段列表 * * @param genTableColumn 业务字段信息 * @return 业务字段集合 */ @Override public List selectGenTableColumnListByTableId(GenTableColumn genTableColumn) { return genTableColumnMapper.selectGenTableColumnListByTableId(genTableColumn); } /** * 新增业务字段 * * @param genTableColumn 业务字段信息 * @return 结果 */ @Override public int insertGenTableColumn(GenTableColumn genTableColumn) { return genTableColumnMapper.insertGenTableColumn(genTableColumn); } /** * 修改业务字段 * * @param genTableColumn 业务字段信息 * @return 结果 */ @Override public int updateGenTableColumn(GenTableColumn genTableColumn) { return genTableColumnMapper.updateGenTableColumn(genTableColumn); } /** * 删除业务字段对象 * * @param ids 需要删除的数据ID * @return 结果 */ @Override public int deleteGenTableColumnByIds(String ids) { return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); } } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java ================================================ package com.ruoyi.generator.service.impl; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.GenConstants; import com.ruoyi.common.core.text.CharsetKit; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.generator.domain.GenTable; import com.ruoyi.generator.domain.GenTableColumn; import com.ruoyi.generator.mapper.GenTableColumnMapper; import com.ruoyi.generator.mapper.GenTableMapper; import com.ruoyi.generator.service.IGenTableService; import com.ruoyi.generator.util.GenUtils; import com.ruoyi.generator.util.VelocityInitializer; import com.ruoyi.generator.util.VelocityUtils; /** * 业务 服务层实现 * * @author ruoyi */ @Service public class GenTableServiceImpl implements IGenTableService { private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class); @Autowired private GenTableMapper genTableMapper; @Autowired private GenTableColumnMapper genTableColumnMapper; /** * 查询业务信息 * * @param id 业务ID * @return 业务信息 */ @Override public GenTable selectGenTableById(Long id) { GenTable genTable = genTableMapper.selectGenTableById(id); setTableFromOptions(genTable); return genTable; } /** * 查询业务列表 * * @param genTable 业务信息 * @return 业务集合 */ @Override public List selectGenTableList(GenTable genTable) { return genTableMapper.selectGenTableList(genTable); } /** * 查询据库列表 * * @param genTable 业务信息 * @return 数据库表集合 */ @Override public List selectDbTableList(GenTable genTable) { return genTableMapper.selectDbTableList(genTable); } /** * 查询据库列表 * * @param tableNames 表名称组 * @return 数据库表集合 */ @Override public List selectDbTableListByNames(String[] tableNames) { return genTableMapper.selectDbTableListByNames(tableNames); } /** * 查询所有表信息 * * @return 表信息集合 */ @Override public List selectGenTableAll() { return genTableMapper.selectGenTableAll(); } /** * 修改业务 * * @param genTable 业务信息 * @return 结果 */ @Override @Transactional public void updateGenTable(GenTable genTable) { String options = JSON.toJSONString(genTable.getParams()); genTable.setOptions(options); int row = genTableMapper.updateGenTable(genTable); if (row > 0) { for (GenTableColumn genTableColumn : genTable.getColumns()) { genTableColumnMapper.updateGenTableColumn(genTableColumn); } } } /** * 删除业务对象 * * @param ids 需要删除的数据ID * @return 结果 */ @Override @Transactional public void deleteGenTableByIds(String ids) { genTableMapper.deleteGenTableByIds(Convert.toLongArray(ids)); genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); } /** * 创建表 * * @param sql 创建表语句 * @return 结果 */ @Override public boolean createTable(String sql) { return genTableMapper.createTable(sql) == 0; } /** * 导入表结构 * * @param tableList 导入表列表 * @param operName 操作人员 */ @Override @Transactional public void importGenTable(List tableList, String operName) { try { for (GenTable table : tableList) { String tableName = table.getTableName(); GenUtils.initTable(table, operName); int row = genTableMapper.insertGenTable(table); if (row > 0) { // 保存列信息 List genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); for (GenTableColumn column : genTableColumns) { GenUtils.initColumnField(column, table); genTableColumnMapper.insertGenTableColumn(column); } } } } catch (Exception e) { throw new ServiceException("导入失败:" + e.getMessage()); } } /** * 预览代码 * * @param tableId 表编号 * @return 预览数据列表 */ @Override public Map previewCode(Long tableId) { Map dataMap = new LinkedHashMap<>(); // 查询表信息 GenTable table = genTableMapper.selectGenTableById(tableId); // 设置主子表信息 setSubTable(table); // 设置主键列信息 setPkColumn(table); VelocityInitializer.initVelocity(); VelocityContext context = VelocityUtils.prepareContext(table); // 获取模板列表 List templates = VelocityUtils.getTemplateList(table.getTplCategory()); for (String template : templates) { // 渲染模板 StringWriter sw = new StringWriter(); Template tpl = Velocity.getTemplate(template, Constants.UTF8); tpl.merge(context, sw); dataMap.put(template, sw.toString()); } return dataMap; } /** * 生成代码(下载方式) * * @param tableName 表名称 * @return 数据 */ @Override public byte[] downloadCode(String tableName) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ZipOutputStream zip = new ZipOutputStream(outputStream); generatorCode(tableName, zip); IOUtils.closeQuietly(zip); return outputStream.toByteArray(); } /** * 生成代码(自定义路径) * * @param tableName 表名称 */ @Override public void generatorCode(String tableName) { // 查询表信息 GenTable table = genTableMapper.selectGenTableByName(tableName); // 设置主子表信息 setSubTable(table); // 设置主键列信息 setPkColumn(table); VelocityInitializer.initVelocity(); VelocityContext context = VelocityUtils.prepareContext(table); // 获取模板列表 List templates = VelocityUtils.getTemplateList(table.getTplCategory()); for (String template : templates) { if (!StringUtils.contains(template, "sql.vm")) { // 渲染模板 StringWriter sw = new StringWriter(); Template tpl = Velocity.getTemplate(template, Constants.UTF8); tpl.merge(context, sw); try { String path = getGenPath(table, template); FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8); } catch (IOException e) { throw new ServiceException("渲染模板失败,表名:" + table.getTableName()); } } } } /** * 同步数据库 * * @param tableName 表名称 */ @Override @Transactional public void synchDb(String tableName) { GenTable table = genTableMapper.selectGenTableByName(tableName); List tableColumns = table.getColumns(); Map tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); if (StringUtils.isEmpty(dbTableColumns)) { throw new ServiceException("同步数据失败,原表结构不存在"); } List dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); dbTableColumns.forEach(column -> { GenUtils.initColumnField(column, table); if (tableColumnMap.containsKey(column.getColumnName())) { GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName()); column.setColumnId(prevColumn.getColumnId()); if (column.isList()) { // 如果是列表,继续保留查询方式/字典类型选项 column.setDictType(prevColumn.getDictType()); column.setQueryType(prevColumn.getQueryType()); } if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk() && (column.isInsert() || column.isEdit()) && ((column.isUsableColumn()) || (!column.isSuperColumn()))) { // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项 column.setIsRequired(prevColumn.getIsRequired()); column.setHtmlType(prevColumn.getHtmlType()); } genTableColumnMapper.updateGenTableColumn(column); } else { genTableColumnMapper.insertGenTableColumn(column); } }); List delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList()); if (StringUtils.isNotEmpty(delColumns)) { genTableColumnMapper.deleteGenTableColumns(delColumns); } } /** * 批量生成代码(下载方式) * * @param tableNames 表数组 * @return 数据 */ @Override public byte[] downloadCode(String[] tableNames) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ZipOutputStream zip = new ZipOutputStream(outputStream); for (String tableName : tableNames) { generatorCode(tableName, zip); } IOUtils.closeQuietly(zip); return outputStream.toByteArray(); } /** * 查询表信息并生成代码 */ private void generatorCode(String tableName, ZipOutputStream zip) { // 查询表信息 GenTable table = genTableMapper.selectGenTableByName(tableName); // 设置主子表信息 setSubTable(table); // 设置主键列信息 setPkColumn(table); VelocityInitializer.initVelocity(); VelocityContext context = VelocityUtils.prepareContext(table); // 获取模板列表 List templates = VelocityUtils.getTemplateList(table.getTplCategory()); for (String template : templates) { // 渲染模板 StringWriter sw = new StringWriter(); Template tpl = Velocity.getTemplate(template, Constants.UTF8); tpl.merge(context, sw); try { // 添加到zip zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); IOUtils.write(sw.toString(), zip, Constants.UTF8); IOUtils.closeQuietly(sw); zip.flush(); zip.closeEntry(); } catch (IOException e) { log.error("渲染模板失败,表名:" + table.getTableName(), e); } } } /** * 修改保存参数校验 * * @param genTable 业务信息 */ @Override public void validateEdit(GenTable genTable) { if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) { String options = JSON.toJSONString(genTable.getParams()); JSONObject paramsObj = JSONObject.parseObject(options); if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) { throw new ServiceException("树编码字段不能为空"); } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) { throw new ServiceException("树父编码字段不能为空"); } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) { throw new ServiceException("树名称字段不能为空"); } } else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) { if (StringUtils.isEmpty(genTable.getSubTableName())) { throw new ServiceException("关联子表的表名不能为空"); } else if (StringUtils.isEmpty(genTable.getSubTableFkName())) { throw new ServiceException("子表关联的外键名不能为空"); } } } /** * 设置主键列信息 * * @param table 业务表信息 */ public void setPkColumn(GenTable table) { for (GenTableColumn column : table.getColumns()) { if (column.isPk()) { table.setPkColumn(column); break; } } if (StringUtils.isNull(table.getPkColumn())) { table.setPkColumn(table.getColumns().get(0)); } if (GenConstants.TPL_SUB.equals(table.getTplCategory())) { for (GenTableColumn column : table.getSubTable().getColumns()) { if (column.isPk()) { table.getSubTable().setPkColumn(column); break; } } if (StringUtils.isNull(table.getSubTable().getPkColumn())) { table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); } } } /** * 设置主子表信息 * * @param table 业务表信息 */ public void setSubTable(GenTable table) { String subTableName = table.getSubTableName(); if (StringUtils.isNotEmpty(subTableName)) { table.setSubTable(genTableMapper.selectGenTableByName(subTableName)); } } /** * 设置代码生成其他选项值 * * @param genTable 设置后的生成对象 */ public void setTableFromOptions(GenTable genTable) { JSONObject paramsObj = JSONObject.parseObject(genTable.getOptions()); if (StringUtils.isNotNull(paramsObj)) { String treeCode = paramsObj.getString(GenConstants.TREE_CODE); String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); String treeName = paramsObj.getString(GenConstants.TREE_NAME); String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); genTable.setTreeCode(treeCode); genTable.setTreeParentCode(treeParentCode); genTable.setTreeName(treeName); genTable.setParentMenuId(parentMenuId); genTable.setParentMenuName(parentMenuName); } } /** * 获取代码生成地址 * * @param table 业务表信息 * @param template 模板文件路径 * @return 生成地址 */ public static String getGenPath(GenTable table, String template) { String genPath = table.getGenPath(); if (StringUtils.equals(genPath, "/")) { return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table); } return genPath + File.separator + VelocityUtils.getFileName(template, table); } } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java ================================================ package com.ruoyi.generator.util; import java.util.Arrays; import org.apache.commons.lang3.RegExUtils; import com.ruoyi.common.constant.GenConstants; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.generator.config.GenConfig; import com.ruoyi.generator.domain.GenTable; import com.ruoyi.generator.domain.GenTableColumn; /** * 代码生成器 工具类 * * @author ruoyi */ public class GenUtils { /** * 初始化表信息 */ public static void initTable(GenTable genTable, String operName) { genTable.setClassName(convertClassName(genTable.getTableName())); genTable.setPackageName(GenConfig.getPackageName()); genTable.setModuleName(getModuleName(GenConfig.getPackageName())); genTable.setBusinessName(getBusinessName(genTable.getTableName())); genTable.setFunctionName(replaceText(genTable.getTableComment())); genTable.setFunctionAuthor(GenConfig.getAuthor()); genTable.setCreateBy(operName); } /** * 初始化列属性字段 */ public static void initColumnField(GenTableColumn column, GenTable table) { String dataType = getDbType(column.getColumnType()); String columnName = column.getColumnName(); column.setTableId(table.getTableId()); column.setCreateBy(table.getCreateBy()); // 设置java字段名 column.setJavaField(StringUtils.toCamelCase(columnName)); // 设置默认类型 column.setJavaType(GenConstants.TYPE_STRING); column.setQueryType(GenConstants.QUERY_EQ); if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) { // 字符串长度超过500设置为文本域 Integer columnLength = getColumnLength(column.getColumnType()); String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; column.setHtmlType(htmlType); } else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) { column.setJavaType(GenConstants.TYPE_DATE); column.setHtmlType(GenConstants.HTML_DATETIME); } else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) { column.setHtmlType(GenConstants.HTML_INPUT); // 如果是浮点型 统一用BigDecimal String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) { column.setJavaType(GenConstants.TYPE_BIGDECIMAL); } // 如果是整形 else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) { column.setJavaType(GenConstants.TYPE_INTEGER); } // 长整形 else { column.setJavaType(GenConstants.TYPE_LONG); } } // 插入字段(默认所有字段都需要插入) column.setIsInsert(GenConstants.REQUIRE); // 编辑字段 if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) { column.setIsEdit(GenConstants.REQUIRE); } // 列表字段 if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) { column.setIsList(GenConstants.REQUIRE); } // 查询字段 if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) { column.setIsQuery(GenConstants.REQUIRE); } // 查询字段类型 if (StringUtils.endsWithIgnoreCase(columnName, "name")) { column.setQueryType(GenConstants.QUERY_LIKE); } // 状态字段设置单选框 if (StringUtils.endsWithIgnoreCase(columnName, "status")) { column.setHtmlType(GenConstants.HTML_RADIO); } // 类型&性别字段设置下拉框 else if (StringUtils.endsWithIgnoreCase(columnName, "type") || StringUtils.endsWithIgnoreCase(columnName, "sex")) { column.setHtmlType(GenConstants.HTML_SELECT); } // 文件字段设置上传控件 else if (StringUtils.endsWithIgnoreCase(columnName, "file")) { column.setHtmlType(GenConstants.HTML_UPLOAD); } // 内容字段设置富文本控件 else if (StringUtils.endsWithIgnoreCase(columnName, "content")) { column.setHtmlType(GenConstants.HTML_SUMMERNOTE); } } /** * 校验数组是否包含指定值 * * @param arr 数组 * @param targetValue 值 * @return 是否包含 */ public static boolean arraysContains(String[] arr, String targetValue) { return Arrays.asList(arr).contains(targetValue); } /** * 获取模块名 * * @param packageName 包名 * @return 模块名 */ public static String getModuleName(String packageName) { int lastIndex = packageName.lastIndexOf("."); int nameLength = packageName.length(); return StringUtils.substring(packageName, lastIndex + 1, nameLength); } /** * 获取业务名 * * @param tableName 表名 * @return 业务名 */ public static String getBusinessName(String tableName) { int lastIndex = tableName.lastIndexOf("_"); int nameLength = tableName.length(); return StringUtils.substring(tableName, lastIndex + 1, nameLength); } /** * 表名转换成Java类名 * * @param tableName 表名称 * @return 类名 */ public static String convertClassName(String tableName) { boolean autoRemovePre = GenConfig.getAutoRemovePre(); String tablePrefix = GenConfig.getTablePrefix(); if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) { String[] searchList = StringUtils.split(tablePrefix, ","); tableName = replaceFirst(tableName, searchList); } return StringUtils.convertToCamelCase(tableName); } /** * 批量替换前缀 * * @param replacementm 替换值 * @param searchList 替换列表 * @return */ public static String replaceFirst(String replacementm, String[] searchList) { String text = replacementm; for (String searchString : searchList) { if (replacementm.startsWith(searchString)) { text = replacementm.replaceFirst(searchString, ""); break; } } return text; } /** * 关键字替换 * * @param text 需要被替换的名字 * @return 替换后的名字 */ public static String replaceText(String text) { return RegExUtils.replaceAll(text, "(?:表|若依)", ""); } /** * 获取数据库类型字段 * * @param columnType 列类型 * @return 截取后的列类型 */ public static String getDbType(String columnType) { if (StringUtils.indexOf(columnType, "(") > 0) { return StringUtils.substringBefore(columnType, "("); } else { return columnType; } } /** * 获取字段长度 * * @param columnType 列类型 * @return 截取后的列类型 */ public static Integer getColumnLength(String columnType) { if (StringUtils.indexOf(columnType, "(") > 0) { String length = StringUtils.substringBetween(columnType, "(", ")"); return Integer.valueOf(length); } else { return 0; } } } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java ================================================ package com.ruoyi.generator.util; import java.util.Properties; import org.apache.velocity.app.Velocity; import com.ruoyi.common.constant.Constants; /** * VelocityEngine工厂 * * @author ruoyi */ public class VelocityInitializer { /** * 初始化vm方法 */ public static void initVelocity() { Properties p = new Properties(); try { // 加载classpath目录下的vm文件 p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); // 定义字符集 p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); // 初始化Velocity引擎,指定配置Properties Velocity.init(p); } catch (Exception e) { throw new RuntimeException(e); } } } ================================================ FILE: ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java ================================================ package com.ruoyi.generator.util; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import org.apache.velocity.VelocityContext; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.constant.GenConstants; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.generator.config.GenConfig; import com.ruoyi.generator.domain.GenTable; import com.ruoyi.generator.domain.GenTableColumn; public class VelocityUtils { /** 项目空间路径 */ private static final String PROJECT_PATH = "main/java"; /** mybatis空间路径 */ private static final String MYBATIS_PATH = "main/resources/mapper"; /** html空间路径 */ private static final String TEMPLATES_PATH = "main/resources/templates"; /** 默认上级菜单,系统工具 */ private static final String DEFAULT_PARENT_MENU_ID = "3"; /** * 设置模板变量信息 * * @return 模板列表 */ public static VelocityContext prepareContext(GenTable genTable) { String moduleName = genTable.getModuleName(); String businessName = genTable.getBusinessName(); String packageName = genTable.getPackageName(); String tplCategory = genTable.getTplCategory(); String functionName = genTable.getFunctionName(); VelocityContext velocityContext = new VelocityContext(); velocityContext.put("tplCategory", genTable.getTplCategory()); velocityContext.put("tableName", genTable.getTableName()); velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); velocityContext.put("ClassName", genTable.getClassName()); velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); velocityContext.put("moduleName", genTable.getModuleName()); velocityContext.put("businessName", genTable.getBusinessName()); velocityContext.put("basePackage", getPackagePrefix(packageName)); velocityContext.put("packageName", packageName); velocityContext.put("author", genTable.getFunctionAuthor()); velocityContext.put("colXsNum", getColXsNum(genTable.getFormColNum())); velocityContext.put("colSmNum", getColSmNum(genTable.getFormColNum())); velocityContext.put("datetime", DateUtils.getDate()); velocityContext.put("pkColumn", genTable.getPkColumn()); velocityContext.put("importList", getImportList(genTable)); velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); velocityContext.put("columns", genTable.getColumns()); velocityContext.put("table", genTable); setMenuVelocityContext(velocityContext, genTable); if (GenConstants.TPL_TREE.equals(tplCategory)) { setTreeVelocityContext(velocityContext, genTable); } if (GenConstants.TPL_SUB.equals(tplCategory)) { setSubVelocityContext(velocityContext, genTable); } return velocityContext; } public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) { String options = genTable.getOptions(); JSONObject paramsObj = JSONObject.parseObject(options); String parentMenuId = getParentMenuId(paramsObj); context.put("parentMenuId", parentMenuId); } public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) { String options = genTable.getOptions(); JSONObject paramsObj = JSONObject.parseObject(options); String treeCode = getTreecode(paramsObj); String treeParentCode = getTreeParentCode(paramsObj); String treeName = getTreeName(paramsObj); context.put("treeCode", treeCode); context.put("treeParentCode", treeParentCode); context.put("treeName", treeName); context.put("expandColumn", getExpandColumn(genTable)); if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) { context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); } if (paramsObj.containsKey(GenConstants.TREE_NAME)) { context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); } } public static void setSubVelocityContext(VelocityContext context, GenTable genTable) { GenTable subTable = genTable.getSubTable(); String subTableName = genTable.getSubTableName(); String subTableFkName = genTable.getSubTableFkName(); String subClassName = genTable.getSubTable().getClassName(); String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); context.put("subTable", subTable); context.put("subTableName", subTableName); context.put("subTableFkName", subTableFkName); context.put("subTableFkClassName", subTableFkClassName); context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); context.put("subClassName", subClassName); context.put("subclassName", StringUtils.uncapitalize(subClassName)); context.put("subImportList", getImportList(genTable.getSubTable())); } /** * 获取模板信息 * * @return 模板列表 */ public static List getTemplateList(String tplCategory) { List templates = new ArrayList(); templates.add("vm/java/domain.java.vm"); templates.add("vm/java/mapper.java.vm"); templates.add("vm/java/service.java.vm"); templates.add("vm/java/serviceImpl.java.vm"); templates.add("vm/java/controller.java.vm"); templates.add("vm/xml/mapper.xml.vm"); if (GenConstants.TPL_CRUD.equals(tplCategory)) { templates.add("vm/html/list.html.vm"); } else if (GenConstants.TPL_TREE.equals(tplCategory)) { templates.add("vm/html/tree.html.vm"); templates.add("vm/html/list-tree.html.vm"); } else if (GenConstants.TPL_SUB.equals(tplCategory)) { templates.add("vm/html/list.html.vm"); templates.add("vm/java/sub-domain.java.vm"); } templates.add("vm/html/add.html.vm"); templates.add("vm/html/edit.html.vm"); templates.add("vm/sql/sql.vm"); return templates; } /** * 获取文件名 */ public static String getFileName(String template, GenTable genTable) { // 文件名称 String fileName = ""; // 包路径 String packageName = genTable.getPackageName(); // 模块名 String moduleName = genTable.getModuleName(); // 大写类名 String className = genTable.getClassName(); // 业务名称 String businessName = genTable.getBusinessName(); String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); String mybatisPath = MYBATIS_PATH + "/" + moduleName; String htmlPath = TEMPLATES_PATH + "/" + moduleName + "/" + businessName; if (template.contains("domain.java.vm")) { fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); } if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) { fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); } else if (template.contains("mapper.java.vm")) { fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); } else if (template.contains("service.java.vm")) { fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); } else if (template.contains("serviceImpl.java.vm")) { fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); } else if (template.contains("controller.java.vm")) { fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); } else if (template.contains("mapper.xml.vm")) { fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); } else if (template.contains("list.html.vm")) { fileName = StringUtils.format("{}/{}.html", htmlPath, businessName); } else if (template.contains("list-tree.html.vm")) { fileName = StringUtils.format("{}/{}.html", htmlPath, businessName); } else if (template.contains("tree.html.vm")) { fileName = StringUtils.format("{}/tree.html", htmlPath); } else if (template.contains("add.html.vm")) { fileName = StringUtils.format("{}/add.html", htmlPath); } else if (template.contains("edit.html.vm")) { fileName = StringUtils.format("{}/edit.html", htmlPath); } else if (template.contains("sql.vm")) { fileName = businessName + "Menu.sql"; } return fileName; } /** * 获取项目文件路径 * * @return 路径 */ public static String getProjectPath() { String packageName = GenConfig.getPackageName(); StringBuffer projectPath = new StringBuffer(); projectPath.append("main/java/"); projectPath.append(packageName.replace(".", "/")); projectPath.append("/"); return projectPath.toString(); } /** * 获取包前缀 * * @param packageName 包名称 * @return 包前缀名称 */ public static String getPackagePrefix(String packageName) { int lastIndex = packageName.lastIndexOf("."); return StringUtils.substring(packageName, 0, lastIndex); } /** * 根据列类型获取导入包 * * @param genTable 业务表对象 * @return 返回需要导入的包列表 */ public static HashSet getImportList(GenTable genTable) { List columns = genTable.getColumns(); GenTable subGenTable = genTable.getSubTable(); HashSet importList = new HashSet(); if (StringUtils.isNotNull(subGenTable)) { importList.add("java.util.List"); } for (GenTableColumn column : columns) { if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) { importList.add("java.util.Date"); importList.add("com.fasterxml.jackson.annotation.JsonFormat"); } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) { importList.add("java.math.BigDecimal"); } } return importList; } /** * 获取权限前缀 * * @param moduleName 模块名称 * @param businessName 业务名称 * @return 返回权限前缀 */ public static String getPermissionPrefix(String moduleName, String businessName) { return StringUtils.format("{}:{}", moduleName, businessName); } /** * 获取上级菜单ID字段 * * @param paramsObj 生成其他选项 * @return 上级菜单ID字段 */ public static String getParentMenuId(JSONObject paramsObj) { if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) { return paramsObj.getString(GenConstants.PARENT_MENU_ID); } return DEFAULT_PARENT_MENU_ID; } /** * 获取树编码 * * @param paramsObj 生成其他选项 * @return 树编码 */ public static String getTreecode(JSONObject paramsObj) { if (paramsObj.containsKey(GenConstants.TREE_CODE)) { return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); } return StringUtils.EMPTY; } /** * 获取树父编码 * * @param paramsObj 生成其他选项 * @return 树父编码 */ public static String getTreeParentCode(JSONObject paramsObj) { if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) { return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); } return StringUtils.EMPTY; } /** * 获取树名称 * * @param paramsObj 生成其他选项 * @return 树名称 */ public static String getTreeName(JSONObject paramsObj) { if (paramsObj.containsKey(GenConstants.TREE_NAME)) { return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); } return StringUtils.EMPTY; } /** * 获取需要在哪一列上面显示展开按钮 * * @param genTable 业务表对象 * @return 展开按钮列序号 */ public static int getExpandColumn(GenTable genTable) { String options = genTable.getOptions(); JSONObject paramsObj = JSONObject.parseObject(options); String treeName = paramsObj.getString(GenConstants.TREE_NAME); int num = 0; for (GenTableColumn column : genTable.getColumns()) { if (column.isList()) { num++; String columnName = column.getColumnName(); if (columnName.equals(treeName)) { break; } } } return num; } /** * 获取表单排列网格 * * @param formColNum 表单布局方式 * @return 排列类样式 */ public static String getColXsNum(int formColNum) { String colXsNum = "col-xs-12"; if (formColNum == 2) { return "col-xs-6"; } else if (formColNum == 3) { return "col-xs-4"; } return colXsNum; } /** * 获取表单label网格 * * @param formColNum 表单布局方式 * @return 网格类样式 */ public static String getColSmNum(int formColNum) { String colSmNum = "col-sm-3"; if (formColNum == 2 || formColNum == 3) { return "col-sm-4"; } return colSmNum; } } ================================================ FILE: ruoyi-generator/src/main/resources/generator.yml ================================================ # 代码生成 gen: # 作者 author: ruoyi # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool packageName: com.ruoyi.system # 自动去除表前缀,默认是false autoRemovePre: false # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) tablePrefix: sys_ # 是否允许生成文件覆盖到本地(自定义路径),默认不允许 allowOverwrite: false ================================================ FILE: ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml ================================================ select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column insert into gen_table_column ( table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time )values( #{tableId}, #{columnName}, #{columnComment}, #{columnType}, #{javaType}, #{javaField}, #{isPk}, #{isIncrement}, #{isRequired}, #{isInsert}, #{isEdit}, #{isList}, #{isQuery}, #{queryType}, #{htmlType}, #{dictType}, #{sort}, #{createBy}, sysdate() ) update gen_table_column column_comment = #{columnComment}, java_type = #{javaType}, java_field = #{javaField}, is_insert = #{isInsert}, is_edit = #{isEdit}, is_list = #{isList}, is_query = #{isQuery}, is_required = #{isRequired}, query_type = #{queryType}, html_type = #{htmlType}, dict_type = #{dictType}, sort = #{sort}, update_by = #{updateBy}, update_time = sysdate() where column_id = #{columnId} delete from gen_table_column where table_id in #{tableId} delete from gen_table_column where column_id in #{item.columnId} ================================================ FILE: ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml ================================================ select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, package_name, module_name, business_name, function_name, function_author, form_col_num, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table insert into gen_table ( table_name, table_comment, class_name, tpl_category, package_name, module_name, business_name, function_name, function_author, form_col_num, gen_type, gen_path, remark, create_by, create_time )values( #{tableName}, #{tableComment}, #{className}, #{tplCategory}, #{packageName}, #{moduleName}, #{businessName}, #{functionName}, #{functionAuthor}, #{formColNum}, #{genType}, #{genPath}, #{remark}, #{createBy}, sysdate() ) ${sql} update gen_table table_name = #{tableName}, table_comment = #{tableComment}, sub_table_name = #{subTableName}, sub_table_fk_name = #{subTableFkName}, class_name = #{className}, function_author = #{functionAuthor}, form_col_num = #{formColNum}, gen_type = #{genType}, gen_path = #{genPath}, tpl_category = #{tplCategory}, package_name = #{packageName}, module_name = #{moduleName}, business_name = #{businessName}, function_name = #{functionName}, options = #{options}, update_by = #{updateBy}, remark = #{remark}, update_time = sysdate() where table_id = #{tableId} delete from gen_table where table_id in #{tableId} ================================================ FILE: ruoyi-generator/src/main/resources/templates/tool/gen/createTable.html ================================================

                    ================================================ FILE: ruoyi-generator/src/main/resources/templates/tool/gen/edit.html ================================================
                    ================================================ FILE: ruoyi-generator/src/main/resources/templates/tool/gen/gen.html ================================================
                    ================================================ FILE: ruoyi-generator/src/main/resources/templates/tool/gen/importTable.html ================================================
                    ================================================ FILE: ruoyi-generator/src/main/resources/vm/html/add.html.vm ================================================ #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "datetime") #break #end #end #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "upload") #break #end #end #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "summernote") #break #end #end
                    #if($table.sub)

                    ${functionName}信息

                    #end #foreach($column in $columns) #set($field=$column.javaField) #if($column.insert && !$column.pk) #if(($column.usableColumn) || (!$column.superColumn)) #set($parentheseIndex=$column.columnComment.indexOf("(")) #if($parentheseIndex != -1) #set($comment=$column.columnComment.substring(0, $parentheseIndex)) #else #set($comment=$column.columnComment) #end #set($dictType=$column.dictType) #if("" != $treeParentCode && $column.javaField == $treeParentCode)
                    #set($BusinessName=$businessName.substring(0,1).toUpperCase() + ${businessName.substring(1)}) #set($treeId = "${className}?.${treeCode}")
                    #elseif($column.htmlType == "input")
                    #elseif($column.htmlType == "upload")
                    #elseif($column.htmlType == "summernote")
                    #elseif($column.htmlType == "select" && "" != $dictType)
                    #elseif($column.htmlType == "select" && $dictType)
                    代码生成请选择字典属性
                    #elseif($column.htmlType == "checkbox" && "" != $dictType)
                    #elseif($column.htmlType == "checkbox" && $dictType)
                    代码生成请选择字典属性
                    #elseif($column.htmlType == "radio" && "" != $dictType)
                    #elseif($column.htmlType == "radio" && $dictType)
                    代码生成请选择字典属性
                    #elseif($column.htmlType == "datetime")
                    #elseif($column.htmlType == "textarea")
                    #end #end #end #end #if($table.sub)

                    ${subTable.functionName}信息

                    #end
                    #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "datetime") #break #end #end #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "upload") #break #end #end #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "summernote") #break #end #end ================================================ FILE: ruoyi-generator/src/main/resources/vm/html/edit.html.vm ================================================ #foreach($column in $columns) #if($column.edit && !$column.superColumn && !$column.pk && $column.htmlType == "datetime") #break #end #end #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "upload") #break #end #end #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "summernote") #break #end #end
                    #if($table.sub)

                    ${functionName}信息

                    #end #foreach($column in $columns) #if($column.edit && !$column.pk) #if(($column.usableColumn) || (!$column.superColumn)) #set($parentheseIndex=$column.columnComment.indexOf("(")) #if($parentheseIndex != -1) #set($comment=$column.columnComment.substring(0, $parentheseIndex)) #else #set($comment=$column.columnComment) #end #set($field=$column.javaField) #set($dictType=$column.dictType) #if("" != $treeParentCode && $column.javaField == $treeParentCode)
                    #set($BusinessName=$businessName.substring(0,1).toUpperCase() + ${businessName.substring(1)})
                    #elseif($column.htmlType == "input")
                    #elseif($column.htmlType == "upload")
                    #elseif($column.htmlType == "summernote")
                    #elseif($column.htmlType == "select" && "" != $dictType)
                    #elseif($column.htmlType == "select" && $dictType)
                    代码生成请选择字典属性
                    #elseif($column.htmlType == "checkbox" && "" != $dictType)
                    #elseif($column.htmlType == "checkbox" && $dictType)
                    代码生成请选择字典属性
                    #elseif($column.htmlType == "radio" && "" != $dictType)
                    #elseif($column.htmlType == "radio" && $dictType)
                    代码生成请选择字典属性
                    #elseif($column.htmlType == "datetime")
                    #elseif($column.htmlType == "textarea")
                    #end #end #end #end #if($table.sub)

                    ${subTable.functionName}信息

                    #end #foreach($column in $columns) #if($column.edit && !$column.superColumn && !$column.pk && $column.htmlType == "datetime") #break #end #end #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "upload") #break #end #end #foreach($column in $columns) #if($column.insert && !$column.superColumn && !$column.pk && $column.htmlType == "summernote") #break #end #end ================================================ FILE: ruoyi-generator/src/main/resources/vm/html/list-tree.html.vm ================================================
                      #foreach($column in $columns) #if($column.query) #set($dictType=$column.dictType) #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set($parentheseIndex=$column.columnComment.indexOf("(")) #if($parentheseIndex != -1) #set($comment=$column.columnComment.substring(0, $parentheseIndex)) #else #set($comment=$column.columnComment) #end #if($column.htmlType == "input")
                    • #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
                    • #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
                    • #elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
                    • #elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                    • -
                    • #end #end #end
                    •  搜索  重置
                    ================================================ FILE: ruoyi-generator/src/main/resources/vm/html/list.html.vm ================================================
                      #foreach($column in $columns) #if($column.query) #set($dictType=$column.dictType) #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set($parentheseIndex=$column.columnComment.indexOf("(")) #if($parentheseIndex != -1) #set($comment=$column.columnComment.substring(0, $parentheseIndex)) #else #set($comment=$column.columnComment) #end #if($column.htmlType == "input")
                    • #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
                    • #elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
                    • #elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
                    • #elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
                    • -
                    • #end #end #end
                    •  搜索  重置
                    ================================================ FILE: ruoyi-generator/src/main/resources/vm/html/tree.html.vm ================================================ #set($treeId = "${className}?." + $treeCode) #set($treeName = "${className}?." + $treeName)
                    ================================================ FILE: ruoyi-generator/src/main/resources/vm/java/controller.java.vm ================================================ package ${packageName}.controller; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.enums.BusinessType; import ${packageName}.domain.${ClassName}; import ${packageName}.service.I${ClassName}Service; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.poi.ExcelUtil; #if($table.crud || $table.sub) import com.ruoyi.common.core.page.TableDataInfo; #elseif($table.tree) import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.core.domain.Ztree; #end /** * ${functionName}Controller * * @author ${author} * @date ${datetime} */ @Controller @RequestMapping("/${moduleName}/${businessName}") public class ${ClassName}Controller extends BaseController { private String prefix = "${moduleName}/${businessName}"; @Autowired private I${ClassName}Service ${className}Service; @RequiresPermissions("${permissionPrefix}:view") @GetMapping() public String ${businessName}() { return prefix + "/${businessName}"; } #if($table.crud || $table.sub) /** * 查询${functionName}列表 */ @RequiresPermissions("${permissionPrefix}:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(${ClassName} ${className}) { startPage(); List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); return getDataTable(list); } #elseif($table.tree) /** * 查询${functionName}树列表 */ @RequiresPermissions("${permissionPrefix}:list") @PostMapping("/list") @ResponseBody public List<${ClassName}> list(${ClassName} ${className}) { List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); return list; } #end /** * 导出${functionName}列表 */ @RequiresPermissions("${permissionPrefix}:export") @Log(title = "${functionName}", businessType = BusinessType.EXPORT) @PostMapping("/export") @ResponseBody public AjaxResult export(${ClassName} ${className}) { List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); return util.exportExcel(list, "${functionName}数据"); } #if($table.crud || $table.sub) /** * 新增${functionName} */ @RequiresPermissions("${permissionPrefix}:add") @GetMapping("/add") public String add() { return prefix + "/add"; } #elseif($table.tree) /** * 新增${functionName} */ @GetMapping(value = { "/add/{${pkColumn.javaField}}", "/add/" }) public String add(@PathVariable(value = "${pkColumn.javaField}", required = false) Long ${pkColumn.javaField}, ModelMap mmap) { if (StringUtils.isNotNull(${pkColumn.javaField})) { mmap.put("${className}", ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); } return prefix + "/add"; } #end /** * 新增保存${functionName} */ @RequiresPermissions("${permissionPrefix}:add") @Log(title = "${functionName}", businessType = BusinessType.INSERT) @PostMapping("/add") @ResponseBody public AjaxResult addSave(${ClassName} ${className}) { return toAjax(${className}Service.insert${ClassName}(${className})); } /** * 修改${functionName} */ @RequiresPermissions("${permissionPrefix}:edit") @GetMapping("/edit/{${pkColumn.javaField}}") public String edit(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}, ModelMap mmap) { ${ClassName} ${className} = ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); mmap.put("${className}", ${className}); return prefix + "/edit"; } /** * 修改保存${functionName} */ @RequiresPermissions("${permissionPrefix}:edit") @Log(title = "${functionName}", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(${ClassName} ${className}) { return toAjax(${className}Service.update${ClassName}(${className})); } #if($table.crud || $table.sub) /** * 删除${functionName} */ @RequiresPermissions("${permissionPrefix}:remove") @Log(title = "${functionName}", businessType = BusinessType.DELETE) @PostMapping( "/remove") @ResponseBody public AjaxResult remove(String ids) { return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(ids)); } #elseif($table.tree) /** * 删除 */ @RequiresPermissions("${permissionPrefix}:remove") @Log(title = "${functionName}", businessType = BusinessType.DELETE) @GetMapping("/remove/{${pkColumn.javaField}}") @ResponseBody public AjaxResult remove(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) { return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); } #end #if($table.tree) /** * 选择${functionName}树 */ #set($BusinessName=$businessName.substring(0,1).toUpperCase() + ${businessName.substring(1)}) @GetMapping(value = { "/select${BusinessName}Tree/{${pkColumn.javaField}}", "/select${BusinessName}Tree/" }) public String select${BusinessName}Tree(@PathVariable(value = "${pkColumn.javaField}", required = false) Long ${pkColumn.javaField}, ModelMap mmap) { if (StringUtils.isNotNull(${pkColumn.javaField})) { mmap.put("${className}", ${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); } return prefix + "/tree"; } /** * 加载${functionName}树列表 */ @GetMapping("/treeData") @ResponseBody public List treeData() { List ztrees = ${className}Service.select${ClassName}Tree(); return ztrees; } #end } ================================================ FILE: ruoyi-generator/src/main/resources/vm/java/domain.java.vm ================================================ package ${packageName}.domain; #foreach ($import in $importList) import ${import}; #end import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; #if($table.crud || $table.sub) import com.ruoyi.common.core.domain.BaseEntity; #elseif($table.tree) import com.ruoyi.common.core.domain.TreeEntity; #end /** * ${functionName}对象 ${tableName} * * @author ${author} * @date ${datetime} */ #if($table.crud || $table.sub) #set($Entity="BaseEntity") #elseif($table.tree) #set($Entity="TreeEntity") #end public class ${ClassName} extends ${Entity} { private static final long serialVersionUID = 1L; #foreach ($column in $columns) #if(!$table.isSuperColumn($column.javaField)) /** $column.columnComment */ #if($column.list) #set($parentheseIndex=$column.columnComment.indexOf("(")) #if($parentheseIndex != -1) #set($comment=$column.columnComment.substring(0, $parentheseIndex)) #else #set($comment=$column.columnComment) #end #if($parentheseIndex != -1) @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") #elseif($column.javaType == 'Date') @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") #else @Excel(name = "${comment}") #end #end private $column.javaType $column.javaField; #end #end #if($table.sub) /** $table.subTable.functionName信息 */ private List<${subClassName}> ${subclassName}List; #end #foreach ($column in $columns) #if(!$table.isSuperColumn($column.javaField)) #if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) #set($AttrName=$column.javaField) #else #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #end public void set${AttrName}($column.javaType $column.javaField) { this.$column.javaField = $column.javaField; } public $column.javaType get${AttrName}() { return $column.javaField; } #end #end #if($table.sub) public List<${subClassName}> get${subClassName}List() { return ${subclassName}List; } public void set${subClassName}List(List<${subClassName}> ${subclassName}List) { this.${subclassName}List = ${subclassName}List; } #end @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) #foreach ($column in $columns) #if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) #set($AttrName=$column.javaField) #else #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #end .append("${column.javaField}", get${AttrName}()) #end #if($table.sub) .append("${subclassName}List", get${subClassName}List()) #end .toString(); } } ================================================ FILE: ruoyi-generator/src/main/resources/vm/java/mapper.java.vm ================================================ package ${packageName}.mapper; import java.util.List; import ${packageName}.domain.${ClassName}; #if($table.sub) import ${packageName}.domain.${subClassName}; #end /** * ${functionName}Mapper接口 * * @author ${author} * @date ${datetime} */ public interface ${ClassName}Mapper { /** * 查询${functionName} * * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 查询${functionName}列表 * * @param ${className} ${functionName} * @return ${functionName}集合 */ public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); /** * 新增${functionName} * * @param ${className} ${functionName} * @return 结果 */ public int insert${ClassName}(${ClassName} ${className}); /** * 修改${functionName} * * @param ${className} ${functionName} * @return 结果 */ public int update${ClassName}(${ClassName} ${className}); /** * 删除${functionName} * * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 批量删除${functionName} * * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ public int delete${ClassName}By${pkColumn.capJavaField}s(String[] ${pkColumn.javaField}s); #if($table.sub) /** * 批量删除${subTable.functionName} * * @param ${pkColumn.javaField}s 需要删除的数据主键集合 * @return 结果 */ public int delete${subClassName}By${subTableFkClassName}s(String[] ${pkColumn.javaField}s); /** * 批量新增${subTable.functionName} * * @param ${subclassName}List ${subTable.functionName}列表 * @return 结果 */ public int batch${subClassName}(List<${subClassName}> ${subclassName}List); /** * 通过${functionName}主键删除${subTable.functionName}信息 * * @param ${pkColumn.javaField} ${functionName}ID * @return 结果 */ public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); #end } ================================================ FILE: ruoyi-generator/src/main/resources/vm/java/service.java.vm ================================================ package ${packageName}.service; import java.util.List; import ${packageName}.domain.${ClassName}; #if($table.tree) import com.ruoyi.common.core.domain.Ztree; #end /** * ${functionName}Service接口 * * @author ${author} * @date ${datetime} */ public interface I${ClassName}Service { /** * 查询${functionName} * * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); /** * 查询${functionName}列表 * * @param ${className} ${functionName} * @return ${functionName}集合 */ public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); /** * 新增${functionName} * * @param ${className} ${functionName} * @return 结果 */ public int insert${ClassName}(${ClassName} ${className}); /** * 修改${functionName} * * @param ${className} ${functionName} * @return 结果 */ public int update${ClassName}(${ClassName} ${className}); /** * 批量删除${functionName} * * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 * @return 结果 */ public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s); /** * 删除${functionName}信息 * * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); #if($table.tree) /** * 查询${functionName}树列表 * * @return 所有${functionName}信息 */ public List select${ClassName}Tree(); #end } ================================================ FILE: ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm ================================================ package ${packageName}.service.impl; import java.util.List; #if($table.tree) import java.util.ArrayList; import com.ruoyi.common.core.domain.Ztree; #end #foreach ($column in $columns) #if($column.javaField == 'createTime' || $column.javaField == 'updateTime') import com.ruoyi.common.utils.DateUtils; #break #end #end import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; #if($table.sub) import java.util.ArrayList; import com.ruoyi.common.utils.StringUtils; import org.springframework.transaction.annotation.Transactional; import ${packageName}.domain.${subClassName}; #end import ${packageName}.mapper.${ClassName}Mapper; import ${packageName}.domain.${ClassName}; import ${packageName}.service.I${ClassName}Service; import com.ruoyi.common.core.text.Convert; /** * ${functionName}Service业务层处理 * * @author ${author} * @date ${datetime} */ @Service public class ${ClassName}ServiceImpl implements I${ClassName}Service { @Autowired private ${ClassName}Mapper ${className}Mapper; /** * 查询${functionName} * * @param ${pkColumn.javaField} ${functionName}主键 * @return ${functionName} */ @Override public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); } /** * 查询${functionName}列表 * * @param ${className} ${functionName} * @return ${functionName} */ @Override public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) { return ${className}Mapper.select${ClassName}List(${className}); } /** * 新增${functionName} * * @param ${className} ${functionName} * @return 结果 */ #if($table.sub) @Transactional #end @Override public int insert${ClassName}(${ClassName} ${className}) { #foreach ($column in $columns) #if($column.javaField == 'createTime') ${className}.setCreateTime(DateUtils.getNowDate()); #end #end #if($table.sub) int rows = ${className}Mapper.insert${ClassName}(${className}); insert${subClassName}(${className}); return rows; #else return ${className}Mapper.insert${ClassName}(${className}); #end } /** * 修改${functionName} * * @param ${className} ${functionName} * @return 结果 */ #if($table.sub) @Transactional #end @Override public int update${ClassName}(${ClassName} ${className}) { #foreach ($column in $columns) #if($column.javaField == 'updateTime') ${className}.setUpdateTime(DateUtils.getNowDate()); #end #end #if($table.sub) ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); insert${subClassName}(${className}); #end return ${className}Mapper.update${ClassName}(${className}); } /** * 批量删除${functionName} * * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 * @return 结果 */ #if($table.sub) @Transactional #end @Override public int delete${ClassName}By${pkColumn.capJavaField}s(String ${pkColumn.javaField}s) { #if($table.sub) ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(Convert.toStrArray(${pkColumn.javaField}s)); #end return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(Convert.toStrArray(${pkColumn.javaField}s)); } /** * 删除${functionName}信息 * * @param ${pkColumn.javaField} ${functionName}主键 * @return 结果 */ #if($table.sub) @Transactional #end @Override public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) { #if($table.sub) ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); #end return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); } #if($table.tree) /** * 查询${functionName}树列表 * * @return 所有${functionName}信息 */ @Override public List select${ClassName}Tree() { List<${ClassName}> ${className}List = ${className}Mapper.select${ClassName}List(new ${ClassName}()); List ztrees = new ArrayList(); for (${ClassName} ${className} : ${className}List) { Ztree ztree = new Ztree(); #if($treeCode.length() > 2 && $treeCode.substring(1,2).matches("[A-Z]")) #set($TreeCode=$treeCode) #else #set($TreeCode=$treeCode.substring(0,1).toUpperCase() + ${treeCode.substring(1)}) #end #if($treeParentCode.length() > 2 && $treeParentCode.substring(1,2).matches("[A-Z]")) #set($TreeParentCode=$treeParentCode) #else #set($TreeParentCode=$treeParentCode.substring(0,1).toUpperCase() + ${treeParentCode.substring(1)}) #end #if($treeName.length() > 2 && $treeName.substring(1,2).matches("[A-Z]")) #set($TreeName=$treeName) #else #set($TreeName=$treeName.substring(0,1).toUpperCase() + ${treeName.substring(1)}) #end ztree.setId(${className}.get${TreeCode}()); ztree.setpId(${className}.get${TreeParentCode}()); ztree.setName(${className}.get${TreeName}()); ztree.setTitle(${className}.get${TreeName}()); ztrees.add(ztree); } return ztrees; } #end #if($table.sub) /** * 新增${subTable.functionName}信息 * * @param ${className} ${functionName}对象 */ public void insert${subClassName}(${ClassName} ${className}) { List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); if (StringUtils.isNotNull(${subclassName}List)) { List<${subClassName}> list = new ArrayList<${subClassName}>(); for (${subClassName} ${subclassName} : ${subclassName}List) { ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); list.add(${subclassName}); } if (list.size() > 0) { ${className}Mapper.batch${subClassName}(list); } } } #end } ================================================ FILE: ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm ================================================ package ${packageName}.domain; #foreach ($import in $subImportList) import ${import}; #end import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.core.domain.BaseEntity; /** * ${subTable.functionName}对象 ${subTableName} * * @author ${author} * @date ${datetime} */ public class ${subClassName} extends BaseEntity { private static final long serialVersionUID = 1L; #foreach ($column in $subTable.columns) #if(!$table.isSuperColumn($column.javaField)) /** $column.columnComment */ #if($column.list) #set($parentheseIndex=$column.columnComment.indexOf("(")) #if($parentheseIndex != -1) #set($comment=$column.columnComment.substring(0, $parentheseIndex)) #else #set($comment=$column.columnComment) #end #if($parentheseIndex != -1) @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") #elseif($column.javaType == 'Date') @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") #else @Excel(name = "${comment}") #end #end private $column.javaType $column.javaField; #end #end #foreach ($column in $subTable.columns) #if(!$table.isSuperColumn($column.javaField)) #if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) #set($AttrName=$column.javaField) #else #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #end public void set${AttrName}($column.javaType $column.javaField) { this.$column.javaField = $column.javaField; } public $column.javaType get${AttrName}() { return $column.javaField; } #end #end @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) #foreach ($column in $subTable.columns) #if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) #set($AttrName=$column.javaField) #else #set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #end .append("${column.javaField}", get${AttrName}()) #end .toString(); } } ================================================ FILE: ruoyi-generator/src/main/resources/vm/sql/sql.vm ================================================ -- 菜单 SQL insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) values('${functionName}', '${parentMenuId}', '1', '/${moduleName}/${businessName}', 'C', '0', '${permissionPrefix}:view', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); -- 按钮父菜单ID SELECT @parentId := LAST_INSERT_ID(); -- 按钮 SQL insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) values('${functionName}查询', @parentId, '1', '#', 'F', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) values('${functionName}新增', @parentId, '2', '#', 'F', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) values('${functionName}修改', @parentId, '3', '#', 'F', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) values('${functionName}删除', @parentId, '4', '#', 'F', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu (menu_name, parent_id, order_num, url, menu_type, visible, perms, icon, create_by, create_time, update_by, update_time, remark) values('${functionName}导出', @parentId, '5', '#', 'F', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); ================================================ FILE: ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm ================================================ #foreach ($column in $columns) #end #if($table.tree) #end #if($table.sub) #foreach ($column in $subTable.columns) #end #end select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} #if($table.sub) #end insert into ${tableName} #foreach($column in $columns) #if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) $column.columnName, #end #end #foreach($column in $columns) #if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) #{$column.javaField}, #end #end update ${tableName} #foreach($column in $columns) #if($column.columnName != $pkColumn.columnName) $column.columnName = #{$column.javaField}, #end #end where ${pkColumn.columnName} = #{${pkColumn.javaField}} delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} delete from ${tableName} where ${pkColumn.columnName} in #{${pkColumn.javaField}} #if($table.sub) delete from ${subTableName} where ${subTableFkName} in #{${subTableFkclassName}} delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) #end ================================================ FILE: ruoyi-quartz/pom.xml ================================================ ruoyi com.ruoyi 4.8.2 4.0.0 ruoyi-quartz quartz定时任务 org.springframework.boot spring-boot-starter-quartz com.ruoyi ruoyi-common ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/config/ScheduleConfig.java ================================================ //package com.ruoyi.quartz.config; // //import org.springframework.context.annotation.Bean; //import org.springframework.context.annotation.Configuration; //import org.springframework.scheduling.quartz.SchedulerFactoryBean; //import javax.sql.DataSource; //import java.util.Properties; // ///** // * 定时任务配置(单机部署建议默认走内存,如需集群需要创建qrtz数据库表/打开类注释) // * // * @author ruoyi // */ //@Configuration //public class ScheduleConfig //{ // @Bean // public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) // { // SchedulerFactoryBean factory = new SchedulerFactoryBean(); // factory.setDataSource(dataSource); // // // quartz参数 // Properties prop = new Properties(); // prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler"); // prop.put("org.quartz.scheduler.instanceId", "AUTO"); // // 线程池配置 // prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); // prop.put("org.quartz.threadPool.threadCount", "20"); // prop.put("org.quartz.threadPool.threadPriority", "5"); // // JobStore配置 // prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); // // 集群配置 // prop.put("org.quartz.jobStore.isClustered", "true"); // prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); // prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10"); // prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); // // // sqlserver 启用 // // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); // prop.put("org.quartz.jobStore.misfireThreshold", "12000"); // prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); // factory.setQuartzProperties(prop); // // factory.setSchedulerName("RuoyiScheduler"); // // 延时启动 // factory.setStartupDelay(1); // factory.setApplicationContextSchedulerContextKey("applicationContextKey"); // // 可选,QuartzScheduler // // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 // factory.setOverwriteExistingJobs(true); // // 设置自动启动,默认为true // factory.setAutoStartup(true); // // return factory; // } //} ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobController.java ================================================ package com.ruoyi.quartz.controller; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.service.ISysJobService; import com.ruoyi.quartz.util.CronUtils; import com.ruoyi.quartz.util.ScheduleUtils; /** * 调度任务信息操作处理 * * @author ruoyi */ @Controller @RequestMapping("/monitor/job") public class SysJobController extends BaseController { private String prefix = "monitor/job"; @Autowired private ISysJobService jobService; @RequiresPermissions("monitor:job:view") @GetMapping() public String job() { return prefix + "/job"; } @RequiresPermissions("monitor:job:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysJob job) { startPage(); List list = jobService.selectJobList(job); return getDataTable(list); } @Log(title = "定时任务", businessType = BusinessType.EXPORT) @RequiresPermissions("monitor:job:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysJob job) { List list = jobService.selectJobList(job); ExcelUtil util = new ExcelUtil(SysJob.class); return util.exportExcel(list, "定时任务"); } @Log(title = "定时任务", businessType = BusinessType.DELETE) @RequiresPermissions("monitor:job:remove") @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) throws SchedulerException { jobService.deleteJobByIds(ids); return success(); } @RequiresPermissions("monitor:job:detail") @GetMapping("/detail/{jobId}") public String detail(@PathVariable("jobId") Long jobId, ModelMap mmap) { mmap.put("name", "job"); mmap.put("job", jobService.selectJobById(jobId)); return prefix + "/detail"; } /** * 任务调度状态修改 */ @Log(title = "定时任务", businessType = BusinessType.UPDATE) @RequiresPermissions("monitor:job:changeStatus") @PostMapping("/changeStatus") @ResponseBody public AjaxResult changeStatus(SysJob job) throws SchedulerException { SysJob newJob = jobService.selectJobById(job.getJobId()); newJob.setStatus(job.getStatus()); return toAjax(jobService.changeStatus(newJob)); } /** * 任务调度立即执行一次 */ @Log(title = "定时任务", businessType = BusinessType.UPDATE) @RequiresPermissions("monitor:job:changeStatus") @PostMapping("/run") @ResponseBody public AjaxResult run(SysJob job) throws SchedulerException { boolean result = jobService.run(job); return result ? success() : error("任务不存在或已过期!"); } /** * 新增调度 */ @RequiresPermissions("monitor:job:add") @GetMapping("/add") public String add() { return prefix + "/add"; } /** * 新增保存调度 */ @Log(title = "定时任务", businessType = BusinessType.INSERT) @RequiresPermissions("monitor:job:add") @PostMapping("/add") @ResponseBody public AjaxResult addSave(@Validated SysJob job) throws SchedulerException, TaskException { if (!CronUtils.isValid(job.getCronExpression())) { return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) { return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) { return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) { return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) { return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); } else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) { return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); } job.setCreateBy(getLoginName()); return toAjax(jobService.insertJob(job)); } /** * 修改调度 */ @RequiresPermissions("monitor:job:edit") @GetMapping("/edit/{jobId}") public String edit(@PathVariable("jobId") Long jobId, ModelMap mmap) { mmap.put("job", jobService.selectJobById(jobId)); return prefix + "/edit"; } /** * 修改保存调度 */ @Log(title = "定时任务", businessType = BusinessType.UPDATE) @RequiresPermissions("monitor:job:edit") @PostMapping("/edit") @ResponseBody public AjaxResult editSave(@Validated SysJob job) throws SchedulerException, TaskException { if (!CronUtils.isValid(job.getCronExpression())) { return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) { return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) { return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap'调用"); } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) { return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) { return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); } else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) { return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); } return toAjax(jobService.updateJob(job)); } /** * 校验cron表达式是否有效 */ @PostMapping("/checkCronExpressionIsValid") @ResponseBody public boolean checkCronExpressionIsValid(SysJob job) { return jobService.checkCronExpressionIsValid(job.getCronExpression()); } /** * Cron表达式在线生成 */ @GetMapping("/cron") public String cron() { return prefix + "/cron"; } /** * 查询cron表达式近10次的执行时间 */ @GetMapping("/queryCronExpression") @ResponseBody public AjaxResult queryCronExpression(@RequestParam(value = "cronExpression", required = false) String cronExpression) { if (jobService.checkCronExpressionIsValid(cronExpression)) { List dateList = CronUtils.getRecentTriggerTime(cronExpression); return success(dateList); } else { return error("表达式无效"); } } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/controller/SysJobLogController.java ================================================ package com.ruoyi.quartz.controller; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.domain.SysJobLog; import com.ruoyi.quartz.service.ISysJobLogService; import com.ruoyi.quartz.service.ISysJobService; /** * 调度日志操作处理 * * @author ruoyi */ @Controller @RequestMapping("/monitor/jobLog") public class SysJobLogController extends BaseController { private String prefix = "monitor/job"; @Autowired private ISysJobService jobService; @Autowired private ISysJobLogService jobLogService; @RequiresPermissions("monitor:job:view") @GetMapping() public String jobLog(@RequestParam(value = "jobId", required = false) Long jobId, ModelMap mmap) { if (StringUtils.isNotNull(jobId)) { SysJob job = jobService.selectJobById(jobId); mmap.put("job", job); } return prefix + "/jobLog"; } @RequiresPermissions("monitor:job:list") @PostMapping("/list") @ResponseBody public TableDataInfo list(SysJobLog jobLog) { startPage(); List list = jobLogService.selectJobLogList(jobLog); return getDataTable(list); } @Log(title = "调度日志", businessType = BusinessType.EXPORT) @RequiresPermissions("monitor:job:export") @PostMapping("/export") @ResponseBody public AjaxResult export(SysJobLog jobLog) { List list = jobLogService.selectJobLogList(jobLog); ExcelUtil util = new ExcelUtil(SysJobLog.class); return util.exportExcel(list, "调度日志"); } @Log(title = "调度日志", businessType = BusinessType.DELETE) @RequiresPermissions("monitor:job:remove") @PostMapping("/remove") @ResponseBody public AjaxResult remove(String ids) { return toAjax(jobLogService.deleteJobLogByIds(ids)); } @RequiresPermissions("monitor:job:detail") @GetMapping("/detail/{jobLogId}") public String detail(@PathVariable("jobLogId") Long jobLogId, ModelMap mmap) { mmap.put("name", "jobLog"); mmap.put("jobLog", jobLogService.selectJobLogById(jobLogId)); return prefix + "/detail"; } @Log(title = "调度日志", businessType = BusinessType.CLEAN) @RequiresPermissions("monitor:job:remove") @PostMapping("/clean") @ResponseBody public AjaxResult clean() { jobLogService.cleanJobLog(); return success(); } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJob.java ================================================ package com.ruoyi.quartz.domain; import java.io.Serializable; import java.util.Date; import jakarta.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.quartz.util.CronUtils; /** * 定时任务调度表 sys_job * * @author ruoyi */ public class SysJob extends BaseEntity implements Serializable { private static final long serialVersionUID = 1L; /** 任务ID */ @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) private Long jobId; /** 任务名称 */ @Excel(name = "任务名称") private String jobName; /** 任务组名 */ @Excel(name = "任务组名") private String jobGroup; /** 调用目标字符串 */ @Excel(name = "调用目标字符串") private String invokeTarget; /** cron执行表达式 */ @Excel(name = "执行表达式 ") private String cronExpression; /** cron计划策略 */ @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; /** 是否并发执行(0允许 1禁止) */ @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") private String concurrent; /** 任务状态(0正常 1暂停) */ @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") private String status; public Long getJobId() { return jobId; } public void setJobId(Long jobId) { this.jobId = jobId; } @NotBlank(message = "任务名称不能为空") @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") public String getJobName() { return jobName; } public void setJobName(String jobName) { this.jobName = jobName; } public String getJobGroup() { return jobGroup; } public void setJobGroup(String jobGroup) { this.jobGroup = jobGroup; } @NotBlank(message = "调用目标字符串不能为空") @Size(min = 0, max = 1000, message = "调用目标字符串长度不能超过500个字符") public String getInvokeTarget() { return invokeTarget; } public void setInvokeTarget(String invokeTarget) { this.invokeTarget = invokeTarget; } @NotBlank(message = "Cron执行表达式不能为空") @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") public String getCronExpression() { return cronExpression; } public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } public Date getNextValidTime() { if (StringUtils.isNotEmpty(cronExpression)) { return CronUtils.getNextExecution(cronExpression); } return null; } public String getMisfirePolicy() { return misfirePolicy; } public void setMisfirePolicy(String misfirePolicy) { this.misfirePolicy = misfirePolicy; } public String getConcurrent() { return concurrent; } public void setConcurrent(String concurrent) { this.concurrent = concurrent; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("jobId", getJobId()) .append("jobName", getJobName()) .append("jobGroup", getJobGroup()) .append("cronExpression", getCronExpression()) .append("nextValidTime", getNextValidTime()) .append("misfirePolicy", getMisfirePolicy()) .append("concurrent", getConcurrent()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/domain/SysJobLog.java ================================================ package com.ruoyi.quartz.domain; import java.util.Date; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.core.domain.BaseEntity; /** * 定时任务调度日志表 sys_job_log * * @author ruoyi */ public class SysJobLog extends BaseEntity { private static final long serialVersionUID = 1L; /** ID */ @Excel(name = "日志序号") private Long jobLogId; /** 任务名称 */ @Excel(name = "任务名称") private String jobName; /** 任务组名 */ @Excel(name = "任务组名") private String jobGroup; /** 调用目标字符串 */ @Excel(name = "调用目标字符串") private String invokeTarget; /** 日志信息 */ @Excel(name = "日志信息") private String jobMessage; /** 执行状态(0正常 1失败) */ @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败") private String status; /** 异常信息 */ @Excel(name = "异常信息") private String exceptionInfo; /** 开始时间 */ private Date startTime; /** 结束时间 */ private Date endTime; public Long getJobLogId() { return jobLogId; } public void setJobLogId(Long jobLogId) { this.jobLogId = jobLogId; } public String getJobName() { return jobName; } public void setJobName(String jobName) { this.jobName = jobName; } public String getJobGroup() { return jobGroup; } public void setJobGroup(String jobGroup) { this.jobGroup = jobGroup; } public String getInvokeTarget() { return invokeTarget; } public void setInvokeTarget(String invokeTarget) { this.invokeTarget = invokeTarget; } public String getJobMessage() { return jobMessage; } public void setJobMessage(String jobMessage) { this.jobMessage = jobMessage; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getExceptionInfo() { return exceptionInfo; } public void setExceptionInfo(String exceptionInfo) { this.exceptionInfo = exceptionInfo; } public Date getStartTime() { return startTime; } public void setStartTime(Date startTime) { this.startTime = startTime; } public Date getEndTime() { return endTime; } public void setEndTime(Date endTime) { this.endTime = endTime; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("jobLogId", getJobLogId()) .append("jobName", getJobName()) .append("jobGroup", getJobGroup()) .append("jobMessage", getJobMessage()) .append("status", getStatus()) .append("exceptionInfo", getExceptionInfo()) .append("startTime", getStartTime()) .append("endTime", getEndTime()) .toString(); } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobLogMapper.java ================================================ package com.ruoyi.quartz.mapper; import com.ruoyi.quartz.domain.SysJobLog; import java.util.List; /** * 调度任务日志信息 数据层 * * @author ruoyi */ public interface SysJobLogMapper { /** * 获取quartz调度器日志的计划任务 * * @param jobLog 调度日志信息 * @return 调度任务日志集合 */ public List selectJobLogList(SysJobLog jobLog); /** * 查询所有调度任务日志 * * @return 调度任务日志列表 */ public List selectJobLogAll(); /** * 通过调度任务日志ID查询调度信息 * * @param jobLogId 调度任务日志ID * @return 调度任务日志对象信息 */ public SysJobLog selectJobLogById(Long jobLogId); /** * 新增任务日志 * * @param jobLog 调度日志信息 * @return 结果 */ public int insertJobLog(SysJobLog jobLog); /** * 批量删除调度日志信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteJobLogByIds(String[] ids); /** * 删除任务日志 * * @param jobId 调度日志ID * @return 结果 */ public int deleteJobLogById(Long jobId); /** * 清空任务日志 */ public void cleanJobLog(); } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/mapper/SysJobMapper.java ================================================ package com.ruoyi.quartz.mapper; import com.ruoyi.quartz.domain.SysJob; import java.util.List; /** * 调度任务信息 数据层 * * @author ruoyi */ public interface SysJobMapper { /** * 查询调度任务日志集合 * * @param job 调度信息 * @return 操作日志集合 */ public List selectJobList(SysJob job); /** * 查询所有调度任务 * * @return 调度任务列表 */ public List selectJobAll(); /** * 通过调度ID查询调度任务信息 * * @param jobId 调度ID * @return 角色对象信息 */ public SysJob selectJobById(Long jobId); /** * 通过调度ID删除调度任务信息 * * @param jobId 调度ID * @return 结果 */ public int deleteJobById(Long jobId); /** * 批量删除调度任务信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteJobByIds(Long[] ids); /** * 修改调度任务信息 * * @param job 调度任务信息 * @return 结果 */ public int updateJob(SysJob job); /** * 新增调度任务信息 * * @param job 调度任务信息 * @return 结果 */ public int insertJob(SysJob job); } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobLogService.java ================================================ package com.ruoyi.quartz.service; import java.util.List; import com.ruoyi.quartz.domain.SysJobLog; /** * 定时任务调度日志信息信息 服务层 * * @author ruoyi */ public interface ISysJobLogService { /** * 获取quartz调度器日志的计划任务 * * @param jobLog 调度日志信息 * @return 调度任务日志集合 */ public List selectJobLogList(SysJobLog jobLog); /** * 通过调度任务日志ID查询调度信息 * * @param jobLogId 调度任务日志ID * @return 调度任务日志对象信息 */ public SysJobLog selectJobLogById(Long jobLogId); /** * 新增任务日志 * * @param jobLog 调度日志信息 */ public void addJobLog(SysJobLog jobLog); /** * 批量删除调度日志信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteJobLogByIds(String ids); /** * 删除任务日志 * * @param jobId 调度日志ID * @return 结果 */ public int deleteJobLogById(Long jobId); /** * 清空任务日志 */ public void cleanJobLog(); } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/ISysJobService.java ================================================ package com.ruoyi.quartz.service; import java.util.List; import org.quartz.SchedulerException; import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.quartz.domain.SysJob; /** * 定时任务调度信息信息 服务层 * * @author ruoyi */ public interface ISysJobService { /** * 获取quartz调度器的计划任务 * * @param job 调度信息 * @return 调度任务集合 */ public List selectJobList(SysJob job); /** * 通过调度任务ID查询调度信息 * * @param jobId 调度任务ID * @return 调度任务对象信息 */ public SysJob selectJobById(Long jobId); /** * 暂停任务 * * @param job 调度信息 * @return 结果 */ public int pauseJob(SysJob job) throws SchedulerException; /** * 恢复任务 * * @param job 调度信息 * @return 结果 */ public int resumeJob(SysJob job) throws SchedulerException; /** * 删除任务后,所对应的trigger也将被删除 * * @param job 调度信息 * @return 结果 */ public int deleteJob(SysJob job) throws SchedulerException; /** * 批量删除调度信息 * * @param ids 需要删除的数据ID * @return 结果 */ public void deleteJobByIds(String ids) throws SchedulerException; /** * 任务调度状态修改 * * @param job 调度信息 * @return 结果 */ public int changeStatus(SysJob job) throws SchedulerException; /** * 立即运行任务 * * @param job 调度信息 * @return 结果 */ public boolean run(SysJob job) throws SchedulerException; /** * 新增任务 * * @param job 调度信息 * @return 结果 */ public int insertJob(SysJob job) throws SchedulerException, TaskException; /** * 更新任务 * * @param job 调度信息 * @return 结果 */ public int updateJob(SysJob job) throws SchedulerException, TaskException; /** * 校验cron表达式是否有效 * * @param cronExpression 表达式 * @return 结果 */ public boolean checkCronExpressionIsValid(String cronExpression); } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobLogServiceImpl.java ================================================ package com.ruoyi.quartz.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.core.text.Convert; import com.ruoyi.quartz.domain.SysJobLog; import com.ruoyi.quartz.mapper.SysJobLogMapper; import com.ruoyi.quartz.service.ISysJobLogService; /** * 定时任务调度日志信息 服务层 * * @author ruoyi */ @Service public class SysJobLogServiceImpl implements ISysJobLogService { @Autowired private SysJobLogMapper jobLogMapper; /** * 获取quartz调度器日志的计划任务 * * @param jobLog 调度日志信息 * @return 调度任务日志集合 */ @Override public List selectJobLogList(SysJobLog jobLog) { return jobLogMapper.selectJobLogList(jobLog); } /** * 通过调度任务日志ID查询调度信息 * * @param jobLogId 调度任务日志ID * @return 调度任务日志对象信息 */ @Override public SysJobLog selectJobLogById(Long jobLogId) { return jobLogMapper.selectJobLogById(jobLogId); } /** * 新增任务日志 * * @param jobLog 调度日志信息 */ @Override public void addJobLog(SysJobLog jobLog) { jobLogMapper.insertJobLog(jobLog); } /** * 批量删除调度日志信息 * * @param ids 需要删除的数据ID * @return 结果 */ @Override public int deleteJobLogByIds(String ids) { return jobLogMapper.deleteJobLogByIds(Convert.toStrArray(ids)); } /** * 删除任务日志 * * @param jobId 调度日志ID */ @Override public int deleteJobLogById(Long jobId) { return jobLogMapper.deleteJobLogById(jobId); } /** * 清空任务日志 */ @Override public void cleanJobLog() { jobLogMapper.cleanJobLog(); } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/service/impl/SysJobServiceImpl.java ================================================ package com.ruoyi.quartz.service.impl; import java.util.List; import jakarta.annotation.PostConstruct; import org.quartz.JobDataMap; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.mapper.SysJobMapper; import com.ruoyi.quartz.service.ISysJobService; import com.ruoyi.quartz.util.CronUtils; import com.ruoyi.quartz.util.ScheduleUtils; /** * 定时任务调度信息 服务层 * * @author ruoyi */ @Service public class SysJobServiceImpl implements ISysJobService { @Autowired private Scheduler scheduler; @Autowired private SysJobMapper jobMapper; /** * 项目启动时,初始化定时器 * 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) */ @PostConstruct public void init() throws SchedulerException, TaskException { scheduler.clear(); List jobList = jobMapper.selectJobAll(); for (SysJob job : jobList) { ScheduleUtils.createScheduleJob(scheduler, job); } } /** * 获取quartz调度器的计划任务列表 * * @param job 调度信息 * @return */ @Override public List selectJobList(SysJob job) { return jobMapper.selectJobList(job); } /** * 通过调度任务ID查询调度信息 * * @param jobId 调度任务ID * @return 调度任务对象信息 */ @Override public SysJob selectJobById(Long jobId) { return jobMapper.selectJobById(jobId); } /** * 暂停任务 * * @param job 调度信息 */ @Override @Transactional(rollbackFor = Exception.class) public int pauseJob(SysJob job) throws SchedulerException { Long jobId = job.getJobId(); String jobGroup = job.getJobGroup(); job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); int rows = jobMapper.updateJob(job); if (rows > 0) { scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); } return rows; } /** * 恢复任务 * * @param job 调度信息 */ @Override @Transactional(rollbackFor = Exception.class) public int resumeJob(SysJob job) throws SchedulerException { Long jobId = job.getJobId(); String jobGroup = job.getJobGroup(); job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); int rows = jobMapper.updateJob(job); if (rows > 0) { scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); } return rows; } /** * 删除任务后,所对应的trigger也将被删除 * * @param job 调度信息 */ @Override @Transactional(rollbackFor = Exception.class) public int deleteJob(SysJob job) throws SchedulerException { Long jobId = job.getJobId(); String jobGroup = job.getJobGroup(); int rows = jobMapper.deleteJobById(jobId); if (rows > 0) { scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); } return rows; } /** * 批量删除调度信息 * * @param ids 需要删除的数据ID * @return 结果 */ @Override @Transactional(rollbackFor = Exception.class) public void deleteJobByIds(String ids) throws SchedulerException { Long[] jobIds = Convert.toLongArray(ids); for (Long jobId : jobIds) { SysJob job = jobMapper.selectJobById(jobId); deleteJob(job); } } /** * 任务调度状态修改 * * @param job 调度信息 */ @Override @Transactional(rollbackFor = Exception.class) public int changeStatus(SysJob job) throws SchedulerException { int rows = 0; String status = job.getStatus(); if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) { rows = resumeJob(job); } else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) { rows = pauseJob(job); } return rows; } /** * 立即运行任务 * * @param job 调度信息 */ @Override @Transactional(rollbackFor = Exception.class) public boolean run(SysJob job) throws SchedulerException { boolean result = false; Long jobId = job.getJobId(); SysJob tmpObj = selectJobById(job.getJobId()); // 参数 JobDataMap dataMap = new JobDataMap(); dataMap.put(ScheduleConstants.TASK_PROPERTIES, tmpObj); JobKey jobKey = ScheduleUtils.getJobKey(jobId, tmpObj.getJobGroup()); if (scheduler.checkExists(jobKey)) { result = true; scheduler.triggerJob(jobKey, dataMap); } return result; } /** * 新增任务 * * @param job 调度信息 调度信息 */ @Override @Transactional(rollbackFor = Exception.class) public int insertJob(SysJob job) throws SchedulerException, TaskException { job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); int rows = jobMapper.insertJob(job); if (rows > 0) { ScheduleUtils.createScheduleJob(scheduler, job); } return rows; } /** * 更新任务的时间表达式 * * @param job 调度信息 */ @Override @Transactional(rollbackFor = Exception.class) public int updateJob(SysJob job) throws SchedulerException, TaskException { SysJob properties = selectJobById(job.getJobId()); int rows = jobMapper.updateJob(job); if (rows > 0) { updateSchedulerJob(job, properties.getJobGroup()); } return rows; } /** * 更新任务 * * @param job 任务对象 * @param jobGroup 任务组名 */ public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException { Long jobId = job.getJobId(); // 判断是否存在 JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); if (scheduler.checkExists(jobKey)) { // 防止创建时存在数据问题 先移除,然后在执行创建操作 scheduler.deleteJob(jobKey); } ScheduleUtils.createScheduleJob(scheduler, job); } /** * 校验cron表达式是否有效 * * @param cronExpression 表达式 * @return 结果 */ @Override public boolean checkCronExpressionIsValid(String cronExpression) { return CronUtils.isValid(cronExpression); } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java ================================================ package com.ruoyi.quartz.task; import org.springframework.stereotype.Component; import com.ruoyi.common.utils.StringUtils; /** * 定时任务调度测试 * * @author ruoyi */ @Component("ryTask") public class RyTask { public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) { System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i)); } public void ryParams(String params) { System.out.println("执行有参方法:" + params); } public void ryNoParams() { System.out.println("执行无参方法"); } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/AbstractQuartzJob.java ================================================ package com.ruoyi.quartz.util; import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.utils.ExceptionUtil; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.quartz.domain.SysJob; import com.ruoyi.quartz.domain.SysJobLog; import com.ruoyi.quartz.service.ISysJobLogService; /** * 抽象quartz调用 * * @author ruoyi */ public abstract class AbstractQuartzJob implements Job { private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); /** * 线程本地变量 */ private static ThreadLocal threadLocal = new ThreadLocal<>(); @Override public void execute(JobExecutionContext context) { SysJob sysJob = new SysJob(); BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); try { before(context, sysJob); if (sysJob != null) { doExecute(context, sysJob); } after(context, sysJob, null); } catch (Exception e) { log.error("任务执行异常 - :", e); after(context, sysJob, e); } } /** * 执行前 * * @param context 工作执行上下文对象 * @param sysJob 系统计划任务 */ protected void before(JobExecutionContext context, SysJob sysJob) { threadLocal.set(new Date()); } /** * 执行后 * * @param context 工作执行上下文对象 * @param sysJob 系统计划任务 */ protected void after(JobExecutionContext context, SysJob sysJob, Exception e) { Date startTime = threadLocal.get(); threadLocal.remove(); final SysJobLog sysJobLog = new SysJobLog(); sysJobLog.setJobName(sysJob.getJobName()); sysJobLog.setJobGroup(sysJob.getJobGroup()); sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); sysJobLog.setStartTime(startTime); sysJobLog.setEndTime(new Date()); long runMs = sysJobLog.getEndTime().getTime() - sysJobLog.getStartTime().getTime(); sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); if (e != null) { sysJobLog.setStatus(Constants.FAIL); String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); sysJobLog.setExceptionInfo(errorMsg); } else { sysJobLog.setStatus(Constants.SUCCESS); } // 写入数据库当中 SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); } /** * 执行方法,由子类重载 * * @param context 工作执行上下文对象 * @param sysJob 系统计划任务 * @throws Exception 执行过程中的异常 */ protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/CronUtils.java ================================================ package com.ruoyi.quartz.util; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.quartz.CronExpression; import org.quartz.TriggerUtils; import org.quartz.impl.triggers.CronTriggerImpl; import com.ruoyi.common.utils.DateUtils; /** * cron表达式工具类 * * @author ruoyi * */ public class CronUtils { /** * 返回一个布尔值代表一个给定的Cron表达式的有效性 * * @param cronExpression Cron表达式 * @return boolean 表达式是否有效 */ public static boolean isValid(String cronExpression) { return CronExpression.isValidExpression(cronExpression); } /** * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 * * @param cronExpression Cron表达式 * @return String 无效时返回表达式错误描述,如果有效返回null */ public static String getInvalidMessage(String cronExpression) { try { new CronExpression(cronExpression); return null; } catch (ParseException pe) { return pe.getMessage(); } } /** * 返回下一个执行时间根据给定的Cron表达式 * * @param cronExpression Cron表达式 * @return Date 下次Cron表达式执行时间 */ public static Date getNextExecution(String cronExpression) { try { CronExpression cron = new CronExpression(cronExpression); return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); } catch (ParseException e) { throw new IllegalArgumentException(e.getMessage()); } } /** * 通过表达式获取近10次的执行时间 * * @param cron 表达式 * @return 时间列表 */ public static List getRecentTriggerTime(String cron) { List list = new ArrayList(); try { CronTriggerImpl cronTriggerImpl = new CronTriggerImpl(); cronTriggerImpl.setCronExpression(cron); List dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 10); for (Date date : dates) { list.add(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date)); } } catch (ParseException e) { return null; } return list; } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/JobInvokeUtil.java ================================================ package com.ruoyi.quartz.util; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.List; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.quartz.domain.SysJob; /** * 任务执行工具 * * @author ruoyi */ public class JobInvokeUtil { /** * 执行方法 * * @param sysJob 系统任务 */ public static void invokeMethod(SysJob sysJob) throws Exception { String invokeTarget = sysJob.getInvokeTarget(); String beanName = getBeanName(invokeTarget); String methodName = getMethodName(invokeTarget); List methodParams = getMethodParams(invokeTarget); if (!isValidClassName(beanName)) { Object bean = SpringUtils.getBean(beanName); invokeMethod(bean, methodName, methodParams); } else { Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance(); invokeMethod(bean, methodName, methodParams); } } /** * 调用任务方法 * * @param bean 目标对象 * @param methodName 方法名称 * @param methodParams 方法参数 */ private static void invokeMethod(Object bean, String methodName, List methodParams) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) { Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams)); method.invoke(bean, getMethodParamsValue(methodParams)); } else { Method method = bean.getClass().getMethod(methodName); method.invoke(bean); } } /** * 校验是否为为class包名 * * @param invokeTarget 名称 * @return true是 false否 */ public static boolean isValidClassName(String invokeTarget) { return StringUtils.countMatches(invokeTarget, ".") > 1; } /** * 获取bean名称 * * @param invokeTarget 目标字符串 * @return bean名称 */ public static String getBeanName(String invokeTarget) { String beanName = StringUtils.substringBefore(invokeTarget, "("); return StringUtils.substringBeforeLast(beanName, "."); } /** * 获取bean方法 * * @param invokeTarget 目标字符串 * @return method方法 */ public static String getMethodName(String invokeTarget) { String methodName = StringUtils.substringBefore(invokeTarget, "("); return StringUtils.substringAfterLast(methodName, "."); } /** * 获取method方法参数相关列表 * * @param invokeTarget 目标字符串 * @return method方法相关参数列表 */ public static List getMethodParams(String invokeTarget) { String methodStr = StringUtils.substringBetweenLast(invokeTarget, "(", ")"); if (StringUtils.isEmpty(methodStr)) { return null; } String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); List classs = new LinkedList<>(); for (int i = 0; i < methodParams.length; i++) { String str = StringUtils.trimToEmpty(methodParams[i]); // String字符串类型,以'或"开头 if (StringUtils.startsWithAny(str, "'", "\"")) { classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); } // boolean布尔类型,等于true或者false else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) { classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); } // long长整形,以L结尾 else if (StringUtils.endsWith(str, "L")) { classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); } // double浮点类型,以D结尾 else if (StringUtils.endsWith(str, "D")) { classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); } // 其他类型归类为整形 else { classs.add(new Object[] { Integer.valueOf(str), Integer.class }); } } return classs; } /** * 获取参数类型 * * @param methodParams 参数相关列表 * @return 参数类型列表 */ public static Class[] getMethodParamsType(List methodParams) { Class[] classs = new Class[methodParams.size()]; int index = 0; for (Object[] os : methodParams) { classs[index] = (Class) os[1]; index++; } return classs; } /** * 获取参数值 * * @param methodParams 参数相关列表 * @return 参数值列表 */ public static Object[] getMethodParamsValue(List methodParams) { Object[] classs = new Object[methodParams.size()]; int index = 0; for (Object[] os : methodParams) { classs[index] = (Object) os[0]; index++; } return classs; } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzDisallowConcurrentExecution.java ================================================ package com.ruoyi.quartz.util; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import com.ruoyi.quartz.domain.SysJob; /** * 定时任务处理(禁止并发执行) * * @author ruoyi * */ @DisallowConcurrentExecution public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob { @Override protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception { JobInvokeUtil.invokeMethod(sysJob); } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/QuartzJobExecution.java ================================================ package com.ruoyi.quartz.util; import org.quartz.JobExecutionContext; import com.ruoyi.quartz.domain.SysJob; /** * 定时任务处理(允许并发执行) * * @author ruoyi * */ public class QuartzJobExecution extends AbstractQuartzJob { @Override protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception { JobInvokeUtil.invokeMethod(sysJob); } } ================================================ FILE: ruoyi-quartz/src/main/java/com/ruoyi/quartz/util/ScheduleUtils.java ================================================ package com.ruoyi.quartz.util; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.Job; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.TriggerBuilder; import org.quartz.TriggerKey; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.ScheduleConstants; import com.ruoyi.common.exception.job.TaskException; import com.ruoyi.common.exception.job.TaskException.Code; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.quartz.domain.SysJob; /** * 定时任务工具类 * * @author ruoyi * */ public class ScheduleUtils { /** * 得到quartz任务类 * * @param sysJob 执行计划 * @return 具体执行任务类 */ private static Class getQuartzJobClass(SysJob sysJob) { boolean isConcurrent = "0".equals(sysJob.getConcurrent()); return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; } /** * 构建任务触发对象 */ public static TriggerKey getTriggerKey(Long jobId, String jobGroup) { return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); } /** * 构建任务键对象 */ public static JobKey getJobKey(Long jobId, String jobGroup) { return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); } /** * 创建定时任务 */ public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException { Class jobClass = getQuartzJobClass(job); // 构建job信息 Long jobId = job.getJobId(); String jobGroup = job.getJobGroup(); JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build(); // 表达式调度构建器 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); // 按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) .withSchedule(cronScheduleBuilder).build(); // 放入参数,运行时的方法可以获取 jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); // 判断是否存在 if (scheduler.checkExists(getJobKey(jobId, jobGroup))) { // 防止创建时存在数据问题 先移除,然后在执行创建操作 scheduler.deleteJob(getJobKey(jobId, jobGroup)); } // 判断任务是否过期 if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) { // 执行调度任务 scheduler.scheduleJob(jobDetail, trigger); } // 暂停任务 if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) { scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); } } /** * 设置定时任务策略 */ public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) throws TaskException { switch (job.getMisfirePolicy()) { case ScheduleConstants.MISFIRE_DEFAULT: return cb; case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: return cb.withMisfireHandlingInstructionIgnoreMisfires(); case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: return cb.withMisfireHandlingInstructionFireAndProceed(); case ScheduleConstants.MISFIRE_DO_NOTHING: return cb.withMisfireHandlingInstructionDoNothing(); default: throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); } } /** * 检查包名是否为白名单配置 * * @param invokeTarget 目标字符串 * @return 结果 */ public static boolean whiteList(String invokeTarget) { String packageName = StringUtils.substringBefore(invokeTarget, "("); int count = StringUtils.countMatches(packageName, "."); if (count > 1) { return StringUtils.startsWithAny(invokeTarget, Constants.JOB_WHITELIST_STR); } Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); String beanPackageName = obj.getClass().getPackage().getName(); return StringUtils.startsWithAny(beanPackageName, Constants.JOB_WHITELIST_STR) && !StringUtils.startsWithAny(beanPackageName, Constants.JOB_ERROR_STR); } } ================================================ FILE: ruoyi-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml ================================================ select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, start_time, end_time, create_time from sys_job_log delete from sys_job_log where job_log_id = #{jobLogId} delete from sys_job_log where job_log_id in #{jobLogId} truncate table sys_job_log insert into sys_job_log( job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, start_time, end_time, create_time )values( #{jobLogId}, #{jobName}, #{jobGroup}, #{invokeTarget}, #{jobMessage}, #{status}, #{exceptionInfo}, #{startTime}, #{endTime}, sysdate() ) ================================================ FILE: ruoyi-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml ================================================ select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark from sys_job delete from sys_job where job_id = #{jobId} delete from sys_job where job_id in #{jobId} update sys_job job_name = #{jobName}, job_group = #{jobGroup}, invoke_target = #{invokeTarget}, cron_expression = #{cronExpression}, misfire_policy = #{misfirePolicy}, concurrent = #{concurrent}, status = #{status}, remark = #{remark}, update_by = #{updateBy}, update_time = sysdate() where job_id = #{jobId} insert into sys_job( job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, remark, create_by, create_time )values( #{jobId}, #{jobName}, #{jobGroup}, #{invokeTarget}, #{cronExpression}, #{misfirePolicy}, #{concurrent}, #{status}, #{remark}, #{createBy}, sysdate() ) ================================================ FILE: ruoyi-quartz/src/main/resources/templates/monitor/job/add.html ================================================
                    Bean调用示例:ryTask.ryParams('ry') Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry') 参数说明:支持字符串,布尔类型,长整型,浮点型,整型
                    ================================================ FILE: ruoyi-quartz/src/main/resources/templates/monitor/job/cron.html ================================================ Cron表达式在线生成

                    表达式

                    分钟 小时 星期
                    表达式字段
                    Cron 表达式

                    ================================================ FILE: ruoyi-quartz/src/main/resources/templates/monitor/job/detail.html ================================================
                    定时任务执行日志详情

                    基础信息


                    正常 失败

                    执行时间线


                    执行方法


                    
                    	                        

                    日志详情


                    
                    	                        
                    定时任务详情

                    任务配置


                    正常 暂停

                    调度信息


                    默认策略 立即执行 执行一次 放弃执行
                    允许 禁止

                    执行方法


                    元信息


                    ================================================ FILE: ruoyi-quartz/src/main/resources/templates/monitor/job/edit.html ================================================
                    Bean调用示例:ryTask.ryParams('ry') Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry') 参数说明:支持字符串,布尔类型,长整型,浮点型,整型
                    ================================================ FILE: ruoyi-quartz/src/main/resources/templates/monitor/job/job.html ================================================
                    • 任务名称:
                    • 任务分组:
                    • 任务状态:
                    •  搜索  重置
                    ================================================ FILE: ruoyi-quartz/src/main/resources/templates/monitor/job/jobLog.html ================================================
                    • 任务名称:
                    • 任务分组:
                    • 执行状态:
                    • -
                    •  搜索  重置
                    ================================================ FILE: ruoyi-system/pom.xml ================================================ ruoyi com.ruoyi 4.8.2 4.0.0 ruoyi-system system系统模块 com.ruoyi ruoyi-common ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysConfig.java ================================================ package com.ruoyi.system.domain; import jakarta.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.domain.BaseEntity; /** * 参数配置表 sys_config * * @author ruoyi */ public class SysConfig extends BaseEntity { private static final long serialVersionUID = 1L; /** 参数主键 */ @Excel(name = "参数主键", cellType = ColumnType.NUMERIC) private Long configId; /** 参数名称 */ @Excel(name = "参数名称") private String configName; /** 参数键名 */ @Excel(name = "参数键名") private String configKey; /** 参数键值 */ @Excel(name = "参数键值") private String configValue; /** 系统内置(Y是 N否) */ @Excel(name = "系统内置", readConverterExp = "Y=是,N=否") private String configType; public Long getConfigId() { return configId; } public void setConfigId(Long configId) { this.configId = configId; } @NotBlank(message = "参数名称不能为空") @Size(min = 0, max = 100, message = "参数名称不能超过100个字符") public String getConfigName() { return configName; } public void setConfigName(String configName) { this.configName = configName; } @NotBlank(message = "参数键名长度不能为空") @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符") public String getConfigKey() { return configKey; } public void setConfigKey(String configKey) { this.configKey = configKey; } @NotBlank(message = "参数键值不能为空") @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符") public String getConfigValue() { return configValue; } public void setConfigValue(String configValue) { this.configValue = configValue; } public String getConfigType() { return configType; } public void setConfigType(String configType) { this.configType = configType; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("configId", getConfigId()) .append("configName", getConfigName()) .append("configKey", getConfigKey()) .append("configValue", getConfigValue()) .append("configType", getConfigType()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysLogininfor.java ================================================ package com.ruoyi.system.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.util.Date; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.domain.BaseEntity; /** * 系统访问记录表 sys_logininfor * * @author ruoyi */ public class SysLogininfor extends BaseEntity { private static final long serialVersionUID = 1L; /** ID */ @Excel(name = "序号", cellType = ColumnType.NUMERIC) private Long infoId; /** 用户账号 */ @Excel(name = "用户账号") private String loginName; /** 登录状态 0成功 1失败 */ @Excel(name = "登录状态", readConverterExp = "0=成功,1=失败") private String status; /** 登录IP地址 */ @Excel(name = "登录地址") private String ipaddr; /** 登录地点 */ @Excel(name = "登录地点") private String loginLocation; /** 浏览器类型 */ @Excel(name = "浏览器") private String browser; /** 操作系统 */ @Excel(name = "操作系统") private String os; /** 提示消息 */ @Excel(name = "提示消息") private String msg; /** 访问时间 */ @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date loginTime; public Long getInfoId() { return infoId; } public void setInfoId(Long infoId) { this.infoId = infoId; } public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getIpaddr() { return ipaddr; } public void setIpaddr(String ipaddr) { this.ipaddr = ipaddr; } public String getLoginLocation() { return loginLocation; } public void setLoginLocation(String loginLocation) { this.loginLocation = loginLocation; } public String getBrowser() { return browser; } public void setBrowser(String browser) { this.browser = browser; } public String getOs() { return os; } public void setOs(String os) { this.os = os; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Date getLoginTime() { return loginTime; } public void setLoginTime(Date loginTime) { this.loginTime = loginTime; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("infoId", getInfoId()) .append("loginName", getLoginName()) .append("ipaddr", getIpaddr()) .append("loginLocation", getLoginLocation()) .append("browser", getBrowser()) .append("os", getOs()) .append("status", getStatus()) .append("msg", getMsg()) .append("loginTime", getLoginTime()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNotice.java ================================================ package com.ruoyi.system.domain; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.fasterxml.jackson.annotation.JsonProperty; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.xss.Xss; /** * 通知公告表 sys_notice * * @author ruoyi */ public class SysNotice extends BaseEntity { private static final long serialVersionUID = 1L; /** 公告ID */ private Long noticeId; /** 公告标题 */ private String noticeTitle; /** 公告类型(1通知 2公告) */ private String noticeType; /** 公告内容 */ private String noticeContent; /** 公告状态(0正常 1关闭) */ private String status; /** 是否已读 */ @JsonProperty("isRead") private boolean isRead; public Long getNoticeId() { return noticeId; } public void setNoticeId(Long noticeId) { this.noticeId = noticeId; } public void setNoticeTitle(String noticeTitle) { this.noticeTitle = noticeTitle; } @Xss(message = "公告标题不能包含脚本字符") @NotBlank(message = "公告标题不能为空") @Size(min = 0, max = 50, message = "公告标题不能超过50个字符") public String getNoticeTitle() { return noticeTitle; } public void setNoticeType(String noticeType) { this.noticeType = noticeType; } public String getNoticeType() { return noticeType; } public void setNoticeContent(String noticeContent) { this.noticeContent = noticeContent; } public String getNoticeContent() { return noticeContent; } public void setStatus(String status) { this.status = status; } public String getStatus() { return status; } public boolean getIsRead() { return isRead; } public void setIsRead(boolean isRead) { this.isRead = isRead; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("noticeId", getNoticeId()) .append("noticeTitle", getNoticeTitle()) .append("noticeType", getNoticeType()) .append("noticeContent", getNoticeContent()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysNoticeRead.java ================================================ package com.ruoyi.system.domain; import java.util.Date; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * 公告已读记录表 sys_notice_read * * @author ruoyi */ public class SysNoticeRead { /** 主键 */ private Long readId; /** 公告ID */ private Long noticeId; /** 用户ID */ private Long userId; /** 阅读时间 */ private Date readTime; public Long getReadId() { return readId; } public void setReadId(Long readId) { this.readId = readId; } public Long getNoticeId() { return noticeId; } public void setNoticeId(Long noticeId) { this.noticeId = noticeId; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Date getReadTime() { return readTime; } public void setReadTime(Date readTime) { this.readTime = readTime; } @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) .append("readId", getReadId()) .append("noticeId", getNoticeId()) .append("userId", getUserId()) .append("readTime", getReadTime()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java ================================================ package com.ruoyi.system.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.util.Date; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.domain.BaseEntity; /** * 操作日志记录表 oper_log * * @author ruoyi */ public class SysOperLog extends BaseEntity { private static final long serialVersionUID = 1L; /** 日志主键 */ @Excel(name = "操作序号", cellType = ColumnType.NUMERIC) private Long operId; /** 操作模块 */ @Excel(name = "操作模块") private String title; /** 业务类型(0其它 1新增 2修改 3删除) */ @Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据") private Integer businessType; /** 业务类型数组 */ private Integer[] businessTypes; /** 请求方法 */ @Excel(name = "请求方法") private String method; /** 请求方式 */ @Excel(name = "请求方式") private String requestMethod; /** 操作类别(0其它 1后台用户 2手机端用户) */ @Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户") private Integer operatorType; /** 操作人员 */ @Excel(name = "操作人员") private String operName; /** 部门名称 */ @Excel(name = "部门名称") private String deptName; /** 请求url */ @Excel(name = "请求地址") private String operUrl; /** 操作地址 */ @Excel(name = "操作地址") private String operIp; /** 操作地点 */ @Excel(name = "操作地点") private String operLocation; /** 请求参数 */ @Excel(name = "请求参数") private String operParam; /** 返回参数 */ @Excel(name = "返回参数") private String jsonResult; /** 操作状态(0正常 1异常) */ @Excel(name = "状态", readConverterExp = "0=正常,1=异常") private Integer status; /** 错误消息 */ @Excel(name = "错误消息") private String errorMsg; /** 操作时间 */ @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date operTime; /** 消耗时间 */ @Excel(name = "消耗时间", suffix = "毫秒") private Long costTime; public Long getOperId() { return operId; } public void setOperId(Long operId) { this.operId = operId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Integer getBusinessType() { return businessType; } public void setBusinessType(Integer businessType) { this.businessType = businessType; } public Integer[] getBusinessTypes() { return businessTypes; } public void setBusinessTypes(Integer[] businessTypes) { this.businessTypes = businessTypes; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getRequestMethod() { return requestMethod; } public void setRequestMethod(String requestMethod) { this.requestMethod = requestMethod; } public Integer getOperatorType() { return operatorType; } public void setOperatorType(Integer operatorType) { this.operatorType = operatorType; } public String getOperName() { return operName; } public void setOperName(String operName) { this.operName = operName; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public String getOperUrl() { return operUrl; } public void setOperUrl(String operUrl) { this.operUrl = operUrl; } public String getOperIp() { return operIp; } public void setOperIp(String operIp) { this.operIp = operIp; } public String getOperLocation() { return operLocation; } public void setOperLocation(String operLocation) { this.operLocation = operLocation; } public String getOperParam() { return operParam; } public void setOperParam(String operParam) { this.operParam = operParam; } public String getJsonResult() { return jsonResult; } public void setJsonResult(String jsonResult) { this.jsonResult = jsonResult; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public Date getOperTime() { return operTime; } public void setOperTime(Date operTime) { this.operTime = operTime; } public Long getCostTime() { return costTime; } public void setCostTime(Long costTime) { this.costTime = costTime; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("operId", getOperId()) .append("title", getTitle()) .append("businessType", getBusinessType()) .append("businessTypes", getBusinessTypes()) .append("method", getMethod()) .append("requestMethod", getRequestMethod()) .append("operatorType", getOperatorType()) .append("operName", getOperName()) .append("deptName", getDeptName()) .append("operUrl", getOperUrl()) .append("operIp", getOperIp()) .append("operLocation", getOperLocation()) .append("operParam", getOperParam()) .append("status", getStatus()) .append("errorMsg", getErrorMsg()) .append("operTime", getOperTime()) .append("costTime", getCostTime()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysPost.java ================================================ package com.ruoyi.system.domain; import jakarta.validation.constraints.*; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import com.ruoyi.common.core.domain.BaseEntity; /** * 岗位表 sys_post * * @author ruoyi */ public class SysPost extends BaseEntity { private static final long serialVersionUID = 1L; /** 岗位序号 */ @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC) private Long postId; /** 岗位编码 */ @Excel(name = "岗位编码") private String postCode; /** 岗位名称 */ @Excel(name = "岗位名称") private String postName; /** 岗位排序 */ @Excel(name = "岗位排序", cellType = ColumnType.NUMERIC) private String postSort; /** 状态(0正常 1停用) */ @Excel(name = "状态", readConverterExp = "0=正常,1=停用") private String status; /** 用户是否存在此岗位标识 默认不存在 */ private boolean flag = false; public Long getPostId() { return postId; } public void setPostId(Long postId) { this.postId = postId; } @NotBlank(message = "岗位编码不能为空") @Size(min = 0, max = 64, message = "岗位编码长度不能超过64个字符") public String getPostCode() { return postCode; } public void setPostCode(String postCode) { this.postCode = postCode; } @NotBlank(message = "岗位名称不能为空") @Size(min = 0, max = 50, message = "岗位名称长度不能超过50个字符") public String getPostName() { return postName; } public void setPostName(String postName) { this.postName = postName; } @NotBlank(message = "显示顺序不能为空") public String getPostSort() { return postSort; } public void setPostSort(String postSort) { this.postSort = postSort; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("postId", getPostId()) .append("postCode", getPostCode()) .append("postName", getPostName()) .append("postSort", getPostSort()) .append("status", getStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleDept.java ================================================ package com.ruoyi.system.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * 角色和部门关联 sys_role_dept * * @author ruoyi */ public class SysRoleDept { /** 角色ID */ private Long roleId; /** 部门ID */ private Long deptId; public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } public Long getDeptId() { return deptId; } public void setDeptId(Long deptId) { this.deptId = deptId; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("roleId", getRoleId()) .append("deptId", getDeptId()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysRoleMenu.java ================================================ package com.ruoyi.system.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * 角色和菜单关联 sys_role_menu * * @author ruoyi */ public class SysRoleMenu { /** 角色ID */ private Long roleId; /** 菜单ID */ private Long menuId; public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } public Long getMenuId() { return menuId; } public void setMenuId(Long menuId) { this.menuId = menuId; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("roleId", getRoleId()) .append("menuId", getMenuId()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserOnline.java ================================================ package com.ruoyi.system.domain; import java.util.Date; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.core.domain.BaseEntity; import com.ruoyi.common.core.session.OnlineSession; import com.ruoyi.common.enums.OnlineStatus; /** * 当前在线会话 sys_user_online * * @author ruoyi */ public class SysUserOnline extends BaseEntity { private static final long serialVersionUID = 1L; /** 用户会话id */ private String sessionId; /** 部门名称 */ private String deptName; /** 登录名称 */ private String loginName; /** 登录IP地址 */ private String ipaddr; /** 登录地址 */ private String loginLocation; /** 浏览器类型 */ private String browser; /** 操作系统 */ private String os; /** session创建时间 */ private Date startTimestamp; /** session最后访问时间 */ private Date lastAccessTime; /** 超时时间,单位为毫秒 */ private Long expireTime; /** 在线状态 */ private OnlineStatus status = OnlineStatus.on_line; /** 备份的当前用户会话 */ private OnlineSession session; /** 序列化的Session数据\uff0c用于服务重吏后恢复会话 */ private byte[] sessionData; public String getSessionId() { return sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public String getIpaddr() { return ipaddr; } public void setIpaddr(String ipaddr) { this.ipaddr = ipaddr; } public String getLoginLocation() { return loginLocation; } public void setLoginLocation(String loginLocation) { this.loginLocation = loginLocation; } public String getBrowser() { return browser; } public void setBrowser(String browser) { this.browser = browser; } public String getOs() { return os; } public void setOs(String os) { this.os = os; } public Date getStartTimestamp() { return startTimestamp; } public void setStartTimestamp(Date startTimestamp) { this.startTimestamp = startTimestamp; } public Date getLastAccessTime() { return lastAccessTime; } public void setLastAccessTime(Date lastAccessTime) { this.lastAccessTime = lastAccessTime; } public Long getExpireTime() { return expireTime; } public void setExpireTime(Long expireTime) { this.expireTime = expireTime; } public OnlineStatus getStatus() { return status; } public void setStatus(OnlineStatus status) { this.status = status; } public OnlineSession getSession() { return session; } public void setSession(OnlineSession session) { this.session = session; } public byte[] getSessionData() { return sessionData; } public void setSessionData(byte[] sessionData) { this.sessionData = sessionData; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("sessionId", getSessionId()) .append("loginName", getLoginName()) .append("deptName", getDeptName()) .append("ipaddr", getIpaddr()) .append("loginLocation", getLoginLocation()) .append("browser", getBrowser()) .append("os", getOs()) .append("status", getStatus()) .append("startTimestamp", getStartTimestamp()) .append("lastAccessTime", getLastAccessTime()) .append("expireTime", getExpireTime()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserPost.java ================================================ package com.ruoyi.system.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * 用户和岗位关联 sys_user_post * * @author ruoyi */ public class SysUserPost { /** 用户ID */ private Long userId; /** 岗位ID */ private Long postId; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Long getPostId() { return postId; } public void setPostId(Long postId) { this.postId = postId; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("userId", getUserId()) .append("postId", getPostId()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserRole.java ================================================ package com.ruoyi.system.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * 用户和角色关联 sys_user_role * * @author ruoyi */ public class SysUserRole { /** 用户ID */ private Long userId; /** 角色ID */ private Long roleId; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("userId", getUserId()) .append("roleId", getRoleId()) .toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysConfigMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysConfig; /** * 参数配置 数据层 * * @author ruoyi */ public interface SysConfigMapper { /** * 查询参数配置信息 * * @param config 参数配置信息 * @return 参数配置信息 */ public SysConfig selectConfig(SysConfig config); /** * 通过ID查询配置 * * @param configId 参数ID * @return 参数配置信息 */ public SysConfig selectConfigById(Long configId); /** * 查询参数配置列表 * * @param config 参数配置信息 * @return 参数配置集合 */ public List selectConfigList(SysConfig config); /** * 根据键名查询参数配置信息 * * @param configKey 参数键名 * @return 参数配置信息 */ public SysConfig checkConfigKeyUnique(String configKey); /** * 新增参数配置 * * @param config 参数配置信息 * @return 结果 */ public int insertConfig(SysConfig config); /** * 修改参数配置 * * @param config 参数配置信息 * @return 结果 */ public int updateConfig(SysConfig config); /** * 删除参数配置 * * @param configId 参数主键 * @return 结果 */ public int deleteConfigById(Long configId); /** * 批量删除参数配置 * * @param configIds 需要删除的数据ID * @return 结果 */ public int deleteConfigByIds(String[] configIds); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.ruoyi.common.core.domain.entity.SysDept; /** * 部门管理 数据层 * * @author ruoyi */ public interface SysDeptMapper { /** * 查询下级部门数量 * * @param dept 部门信息 * @return 结果 */ public int selectDeptCount(SysDept dept); /** * 查询部门是否存在用户 * * @param deptId 部门ID * @return 结果 */ public int checkDeptExistUser(Long deptId); /** * 查询部门管理数据 * * @param dept 部门信息 * @return 部门信息集合 */ public List selectDeptList(SysDept dept); /** * 删除部门管理信息 * * @param deptId 部门ID * @return 结果 */ public int deleteDeptById(Long deptId); /** * 新增部门信息 * * @param dept 部门信息 * @return 结果 */ public int insertDept(SysDept dept); /** * 修改部门信息 * * @param dept 部门信息 * @return 结果 */ public int updateDept(SysDept dept); /** * 修改子元素关系 * * @param depts 子元素 * @return 结果 */ public int updateDeptChildren(@Param("depts") List depts); /** * 根据部门ID查询信息 * * @param deptId 部门ID * @return 部门信息 */ public SysDept selectDeptById(Long deptId); /** * 校验部门名称是否唯一 * * @param deptName 部门名称 * @param parentId 父部门ID * @return 结果 */ public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId); /** * 根据角色ID查询部门 * * @param roleId 角色ID * @return 部门列表 */ public List selectRoleDeptTree(Long roleId); /** * 修改所在部门正常状态 * * @param deptIds 部门ID组 */ public void updateDeptStatusNormal(Long[] deptIds); /** * 根据ID查询所有子部门 * * @param deptId 部门ID * @return 部门列表 */ public List selectChildrenDeptById(Long deptId); /** * 根据ID查询所有子部门(正常状态) * * @param deptId 部门ID * @return 子部门数 */ public int selectNormalChildrenDeptById(Long deptId); /** * 保存部门排序 * * @param dept 部门信息(含deptId和orderNum) */ public void updateDeptSort(SysDept dept); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictDataMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.ruoyi.common.core.domain.entity.SysDictData; /** * 字典表 数据层 * * @author ruoyi */ public interface SysDictDataMapper { /** * 根据条件分页查询字典数据 * * @param dictData 字典数据信息 * @return 字典数据集合信息 */ public List selectDictDataList(SysDictData dictData); /** * 根据字典类型查询字典数据 * * @param dictType 字典类型 * @return 字典数据集合信息 */ public List selectDictDataByType(String dictType); /** * 根据字典类型和字典键值查询字典数据信息 * * @param dictType 字典类型 * @param dictValue 字典键值 * @return 字典标签 */ public String selectDictLabel(@Param("dictType") String dictType, @Param("dictValue") String dictValue); /** * 根据字典数据ID查询信息 * * @param dictCode 字典数据ID * @return 字典数据 */ public SysDictData selectDictDataById(Long dictCode); /** * 查询字典数据 * * @param dictType 字典类型 * @return 字典数据 */ public int countDictDataByType(String dictType); /** * 通过字典ID删除字典数据信息 * * @param dictCode 字典数据ID * @return 结果 */ public int deleteDictDataById(Long dictCode); /** * 批量删除字典数据 * * @param ids 需要删除的数据 * @return 结果 */ public int deleteDictDataByIds(String[] ids); /** * 新增字典数据信息 * * @param dictData 字典数据信息 * @return 结果 */ public int insertDictData(SysDictData dictData); /** * 修改字典数据信息 * * @param dictData 字典数据信息 * @return 结果 */ public int updateDictData(SysDictData dictData); /** * 同步修改字典类型 * * @param oldDictType 旧字典类型 * @param newDictType 新旧字典类型 * @return 结果 */ public int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDictTypeMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.common.core.domain.entity.SysDictType; /** * 字典表 数据层 * * @author ruoyi */ public interface SysDictTypeMapper { /** * 根据条件分页查询字典类型 * * @param dictType 字典类型信息 * @return 字典类型集合信息 */ public List selectDictTypeList(SysDictType dictType); /** * 根据所有字典类型 * * @return 字典类型集合信息 */ public List selectDictTypeAll(); /** * 根据字典类型ID查询信息 * * @param dictId 字典类型ID * @return 字典类型 */ public SysDictType selectDictTypeById(Long dictId); /** * 根据字典类型查询信息 * * @param dictType 字典类型 * @return 字典类型 */ public SysDictType selectDictTypeByType(String dictType); /** * 通过字典ID删除字典信息 * * @param dictId 字典ID * @return 结果 */ public int deleteDictTypeById(Long dictId); /** * 批量删除字典类型 * * @param ids 需要删除的数据 * @return 结果 */ public int deleteDictTypeByIds(Long[] ids); /** * 新增字典类型信息 * * @param dictType 字典类型信息 * @return 结果 */ public int insertDictType(SysDictType dictType); /** * 修改字典类型信息 * * @param dictType 字典类型信息 * @return 结果 */ public int updateDictType(SysDictType dictType); /** * 校验字典类型称是否唯一 * * @param dictType 字典类型 * @return 结果 */ public SysDictType checkDictTypeUnique(String dictType); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysLogininforMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysLogininfor; /** * 系统访问日志情况信息 数据层 * * @author ruoyi */ public interface SysLogininforMapper { /** * 新增系统登录日志 * * @param logininfor 访问日志对象 */ public void insertLogininfor(SysLogininfor logininfor); /** * 查询系统登录日志集合 * * @param logininfor 访问日志对象 * @return 登录记录集合 */ public List selectLogininforList(SysLogininfor logininfor); /** * 批量删除系统登录日志 * * @param ids 需要删除的数据 * @return 结果 */ public int deleteLogininforByIds(String[] ids); /** * 清空系统登录日志 * * @return 结果 */ public int cleanLogininfor(); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.ruoyi.common.core.domain.entity.SysMenu; /** * 菜单表 数据层 * * @author ruoyi */ public interface SysMenuMapper { /** * 查询系统所有菜单(含按钮) * * @return 菜单列表 */ public List selectMenuAll(); /** * 根据用户ID查询菜单 * * @param userId 用户ID * @return 菜单列表 */ public List selectMenuAllByUserId(Long userId); /** * 查询系统正常显示菜单(不含按钮) * * @return 菜单列表 */ public List selectMenuNormalAll(); /** * 根据用户ID查询菜单 * * @param userId 用户ID * @return 菜单列表 */ public List selectMenusByUserId(Long userId); /** * 根据用户ID查询权限 * * @param userId 用户ID * @return 权限列表 */ public List selectPermsByUserId(Long userId); /** * 根据角色ID查询权限 * * @param roleId 角色ID * @return 权限列表 */ public List selectPermsByRoleId(Long roleId); /** * 根据角色ID查询菜单 * * @param roleId 角色ID * @return 菜单列表 */ public List selectMenuTree(Long roleId); /** * 查询系统菜单列表 * * @param menu 菜单信息 * @return 菜单列表 */ public List selectMenuList(SysMenu menu); /** * 查询系统菜单列表 * * @param menu 菜单信息 * @return 菜单列表 */ public List selectMenuListByUserId(SysMenu menu); /** * 删除菜单管理信息 * * @param menuId 菜单ID * @return 结果 */ public int deleteMenuById(Long menuId); /** * 根据菜单ID查询信息 * * @param menuId 菜单ID * @return 菜单信息 */ public SysMenu selectMenuById(Long menuId); /** * 查询菜单数量 * * @param parentId 菜单父ID * @return 结果 */ public int selectCountMenuByParentId(Long parentId); /** * 新增菜单信息 * * @param menu 菜单信息 * @return 结果 */ public int insertMenu(SysMenu menu); /** * 修改菜单信息 * * @param menu 菜单信息 * @return 结果 */ public int updateMenu(SysMenu menu); /** * 保存菜单排序 * * @param menu 菜单信息 */ public void updateMenuSort(SysMenu menu); /** * 校验菜单名称是否唯一 * * @param menuName 菜单名称 * @param parentId 父菜单ID * @return 结果 */ public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysNotice; /** * 公告 数据层 * * @author ruoyi */ public interface SysNoticeMapper { /** * 查询公告信息 * * @param noticeId 公告ID * @return 公告信息 */ public SysNotice selectNoticeById(Long noticeId); /** * 查询公告列表 * * @param notice 公告信息 * @return 公告集合 */ public List selectNoticeList(SysNotice notice); /** * 新增公告 * * @param notice 公告信息 * @return 结果 */ public int insertNotice(SysNotice notice); /** * 修改公告 * * @param notice 公告信息 * @return 结果 */ public int updateNotice(SysNotice notice); /** * 批量删除公告 * * @param noticeIds 需要删除的数据ID * @return 结果 */ public int deleteNoticeByIds(String[] noticeIds); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysNoticeReadMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.ruoyi.system.domain.SysNoticeRead; import com.ruoyi.system.domain.SysNotice; /** * 公告已读记录 数据层 * * @author ruoyi */ public interface SysNoticeReadMapper { /** * 新增已读记录(忽略重复) * * @param noticeRead 已读记录 * @return 结果 */ public int insertNoticeRead(SysNoticeRead noticeRead); /** * 查询某用户未读公告数量 * * @param userId 用户ID * @return 未读数量 */ public int selectUnreadCount(@Param("userId") Long userId); /** * 查询某用户是否已读某公告 * * @param noticeId 公告ID * @param userId 用户ID * @return 已读记录数(0未读 1已读) */ public int selectIsRead(@Param("noticeId") Long noticeId, @Param("userId") Long userId); /** * 批量标记已读 * * @param userId 用户ID * @param noticeIds 公告ID数组 * @return 结果 */ public int insertNoticeReadBatch(@Param("userId") Long userId, @Param("noticeIds") Long[] noticeIds); /** * 查询带已读状态的公告列表(SQL层限制条数,一次查询完成) * * @param userId 用户ID * @param limit 最多返回条数 * @return 带 isRead 标记的公告列表 */ public List selectNoticeListWithReadStatus(@Param("userId") Long userId, @Param("limit") int limit); /** * 公告删除时清理对应已读记录 * * @param noticeIds 公告ID数组 * @return 结果 */ public int deleteByNoticeIds(@Param("noticeIds") String[] noticeIds); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOperLogMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysOperLog; /** * 操作日志 数据层 * * @author ruoyi */ public interface SysOperLogMapper { /** * 新增操作日志 * * @param operLog 操作日志对象 */ public void insertOperlog(SysOperLog operLog); /** * 查询系统操作日志集合 * * @param operLog 操作日志对象 * @return 操作日志集合 */ public List selectOperLogList(SysOperLog operLog); /** * 批量删除系统操作日志 * * @param ids 需要删除的数据 * @return 结果 */ public int deleteOperLogByIds(String[] ids); /** * 查询操作日志详细 * * @param operId 操作ID * @return 操作日志对象 */ public SysOperLog selectOperLogById(Long operId); /** * 清空操作日志 */ public void cleanOperLog(); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysPostMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysPost; /** * 岗位信息 数据层 * * @author ruoyi */ public interface SysPostMapper { /** * 查询岗位数据集合 * * @param post 岗位信息 * @return 岗位数据集合 */ public List selectPostList(SysPost post); /** * 查询所有岗位 * * @return 岗位列表 */ public List selectPostAll(); /** * 根据用户ID查询岗位 * * @param userId 用户ID * @return 岗位列表 */ public List selectPostsByUserId(Long userId); /** * 通过岗位ID查询岗位信息 * * @param postId 岗位ID * @return 角色对象信息 */ public SysPost selectPostById(Long postId); /** * 批量删除岗位信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deletePostByIds(Long[] ids); /** * 修改岗位信息 * * @param post 岗位信息 * @return 结果 */ public int updatePost(SysPost post); /** * 新增岗位信息 * * @param post 岗位信息 * @return 结果 */ public int insertPost(SysPost post); /** * 校验岗位名称 * * @param postName 岗位名称 * @return 结果 */ public SysPost checkPostNameUnique(String postName); /** * 校验岗位编码 * * @param postCode 岗位编码 * @return 结果 */ public SysPost checkPostCodeUnique(String postCode); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleDeptMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysRoleDept; /** * 角色与部门关联表 数据层 * * @author ruoyi */ public interface SysRoleDeptMapper { /** * 通过角色ID删除角色和部门关联 * * @param roleId 角色ID * @return 结果 */ public int deleteRoleDeptByRoleId(Long roleId); /** * 批量删除角色部门关联信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteRoleDept(Long[] ids); /** * 查询部门使用数量 * * @param deptId 部门ID * @return 结果 */ public int selectCountRoleDeptByDeptId(Long deptId); /** * 批量新增角色部门信息 * * @param roleDeptList 角色部门列表 * @return 结果 */ public int batchRoleDept(List roleDeptList); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.common.core.domain.entity.SysRole; /** * 角色表 数据层 * * @author ruoyi */ public interface SysRoleMapper { /** * 根据条件分页查询角色数据 * * @param role 角色信息 * @return 角色数据集合信息 */ public List selectRoleList(SysRole role); /** * 根据用户ID查询角色 * * @param userId 用户ID * @return 角色列表 */ public List selectRolesByUserId(Long userId); /** * 通过角色ID查询角色 * * @param roleId 角色ID * @return 角色对象信息 */ public SysRole selectRoleById(Long roleId); /** * 通过角色ID删除角色 * * @param roleId 角色ID * @return 结果 */ public int deleteRoleById(Long roleId); /** * 批量角色用户信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteRoleByIds(Long[] ids); /** * 修改角色信息 * * @param role 角色信息 * @return 结果 */ public int updateRole(SysRole role); /** * 新增角色信息 * * @param role 角色信息 * @return 结果 */ public int insertRole(SysRole role); /** * 校验角色名称是否唯一 * * @param roleName 角色名称 * @return 角色信息 */ public SysRole checkRoleNameUnique(String roleName); /** * 校验角色权限是否唯一 * * @param roleKey 角色权限 * @return 角色信息 */ public SysRole checkRoleKeyUnique(String roleKey); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysRoleMenuMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysRoleMenu; /** * 角色与菜单关联表 数据层 * * @author ruoyi */ public interface SysRoleMenuMapper { /** * 通过角色ID删除角色和菜单关联 * * @param roleId 角色ID * @return 结果 */ public int deleteRoleMenuByRoleId(Long roleId); /** * 批量删除角色菜单关联信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteRoleMenu(Long[] ids); /** * 查询菜单使用数量 * * @param menuId 菜单ID * @return 结果 */ public int selectCountRoleMenuByMenuId(Long menuId); /** * 批量新增角色菜单信息 * * @param roleMenuList 角色菜单列表 * @return 结果 */ public int batchRoleMenu(List roleMenuList); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.Date; import java.util.List; import org.apache.ibatis.annotations.Param; import com.ruoyi.common.core.domain.entity.SysUser; /** * 用户表 数据层 * * @author ruoyi */ public interface SysUserMapper { /** * 根据条件分页查询用户列表 * * @param sysUser 用户信息 * @return 用户信息集合信息 */ public List selectUserList(SysUser sysUser); /** * 根据条件分页查询已配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ public List selectAllocatedList(SysUser user); /** * 根据条件分页查询未分配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ public List selectUnallocatedList(SysUser user); /** * 通过用户名查询用户 * * @param userName 用户名 * @return 用户对象信息 */ public SysUser selectUserByLoginName(String userName); /** * 通过手机号码查询用户 * * @param phoneNumber 手机号码 * @return 用户对象信息 */ public SysUser selectUserByPhoneNumber(String phoneNumber); /** * 通过邮箱查询用户 * * @param email 邮箱 * @return 用户对象信息 */ public SysUser selectUserByEmail(String email); /** * 通过用户ID查询用户 * * @param userId 用户ID * @return 用户对象信息 */ public SysUser selectUserById(Long userId); /** * 通过用户ID删除用户 * * @param userId 用户ID * @return 结果 */ public int deleteUserById(Long userId); /** * 批量删除用户信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteUserByIds(Long[] ids); /** * 修改用户信息 * * @param user 用户信息 * @return 结果 */ public int updateUser(SysUser user); /** * 修改用户头像 * * @param userId 用户ID * @param avatar 头像地址 * @return 结果 */ public int updateUserAvatar(@Param("userId") Long userId, @Param("avatar") String avatar); /** * 修改用户状态 * * @param userId 用户ID * @param status 状态 * @return 结果 */ public int updateUserStatus(@Param("userId") Long userId, @Param("status") String status); /** * 重置用户密码 * * @param userId 用户ID * @param password 密码 * @param salt 盐 * @return 结果 */ public int resetUserPwd(@Param("userId") Long userId, @Param("password") String password, @Param("salt") String salt); /** * 更新用户登录信息(IP和登录时间) * * @param userId 用户ID * @param loginIp 登录IP地址 * @param loginDate 登录时间 * @return 结果 */ public int updateLoginInfo(@Param("userId") Long userId, @Param("loginIp") String loginIp, @Param("loginDate") Date loginDate); /** * 新增用户信息 * * @param user 用户信息 * @return 结果 */ public int insertUser(SysUser user); /** * 校验用户名称是否唯一 * * @param loginName 登录名称 * @return 结果 */ public SysUser checkLoginNameUnique(String loginName); /** * 校验手机号码是否唯一 * * @param phonenumber 手机号码 * @return 结果 */ public SysUser checkPhoneUnique(String phonenumber); /** * 校验email是否唯一 * * @param email 用户邮箱 * @return 结果 */ public SysUser checkEmailUnique(String email); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysUserOnline; /** * 在线用户 数据层 * * @author ruoyi */ public interface SysUserOnlineMapper { /** * 通过会话序号查询信息 * * @param sessionId 会话ID * @return 在线用户信息 */ public SysUserOnline selectOnlineById(String sessionId); /** * 通过会话序号删除信息 * * @param sessionId 会话ID * @return 在线用户信息 */ public int deleteOnlineById(String sessionId); /** * 保存会话信息 * * @param online 会话信息 * @return 结果 */ public int saveOnline(SysUserOnline online); /** * 查询会话集合 * * @param userOnline 会话参数 * @return 会话集合 */ public List selectUserOnlineList(SysUserOnline userOnline); /** * 查询过期会话集合 * * @param lastAccessTime 过期时间 * @return 会话集合 */ public List selectOnlineByExpired(String lastAccessTime); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserPostMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import com.ruoyi.system.domain.SysUserPost; /** * 用户与岗位关联表 数据层 * * @author ruoyi */ public interface SysUserPostMapper { /** * 通过用户ID删除用户和岗位关联 * * @param userId 用户ID * @return 结果 */ public int deleteUserPostByUserId(Long userId); /** * 通过岗位ID查询岗位使用数量 * * @param postId 岗位ID * @return 结果 */ public int countUserPostById(Long postId); /** * 批量删除用户和岗位关联 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteUserPost(Long[] ids); /** * 批量新增用户岗位信息 * * @param userPostList 用户岗位列表 * @return 结果 */ public int batchUserPost(List userPostList); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java ================================================ package com.ruoyi.system.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.ruoyi.system.domain.SysUserRole; /** * 用户与角色关联表 数据层 * * @author ruoyi */ public interface SysUserRoleMapper { /** * 通过用户ID查询用户和角色关联 * * @param userId 用户ID * @return 用户和角色关联列表 */ public List selectUserRoleByUserId(Long userId); /** * 通过用户ID删除用户和角色关联 * * @param userId 用户ID * @return 结果 */ public int deleteUserRoleByUserId(Long userId); /** * 批量删除用户和角色关联 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteUserRole(Long[] ids); /** * 通过角色ID查询角色使用数量 * * @param roleId 角色ID * @return 结果 */ public int countUserRoleByRoleId(Long roleId); /** * 批量新增用户角色信息 * * @param userRoleList 用户角色列表 * @return 结果 */ public int batchUserRole(List userRoleList); /** * 删除用户和角色关联信息 * * @param userRole 用户和角色关联信息 * @return 结果 */ public int deleteUserRoleInfo(SysUserRole userRole); /** * 批量取消授权用户角色 * * @param roleId 角色ID * @param userIds 需要删除的用户数据ID * @return 结果 */ public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.system.domain.SysConfig; /** * 参数配置 服务层 * * @author ruoyi */ public interface ISysConfigService { /** * 查询参数配置信息 * * @param configId 参数配置ID * @return 参数配置信息 */ public SysConfig selectConfigById(Long configId); /** * 根据键名查询参数配置信息 * * @param configKey 参数键名 * @return 参数键值 */ public String selectConfigByKey(String configKey); /** * 查询参数配置列表 * * @param config 参数配置信息 * @return 参数配置集合 */ public List selectConfigList(SysConfig config); /** * 新增参数配置 * * @param config 参数配置信息 * @return 结果 */ public int insertConfig(SysConfig config); /** * 修改参数配置 * * @param config 参数配置信息 * @return 结果 */ public int updateConfig(SysConfig config); /** * 批量删除参数配置信息 * * @param ids 需要删除的数据ID */ public void deleteConfigByIds(String ids); /** * 加载参数缓存数据 */ public void loadingConfigCache(); /** * 清空参数缓存数据 */ public void clearConfigCache(); /** * 重置参数缓存数据 */ public void resetConfigCache(); /** * 校验参数键名是否唯一 * * @param config 参数信息 * @return 结果 */ public boolean checkConfigKeyUnique(SysConfig config); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDeptService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysRole; /** * 部门管理 服务层 * * @author ruoyi */ public interface ISysDeptService { /** * 查询部门管理数据 * * @param dept 部门信息 * @return 部门信息集合 */ public List selectDeptList(SysDept dept); /** * 查询部门管理树 * * @param dept 部门信息 * @return 所有部门信息 */ public List selectDeptTree(SysDept dept); /** * 查询部门管理树(排除下级) * * @param dept 部门信息 * @return 所有部门信息 */ public List selectDeptTreeExcludeChild(SysDept dept); /** * 根据角色ID查询菜单 * * @param role 角色对象 * @return 菜单列表 */ public List roleDeptTreeData(SysRole role); /** * 根据父部门ID查询下级部门数量 * * @param parentId 父部门ID * @return 结果 */ public int selectDeptCount(Long parentId); /** * 查询部门是否存在用户 * * @param deptId 部门ID * @return 结果 true 存在 false 不存在 */ public boolean checkDeptExistUser(Long deptId); /** * 删除部门管理信息 * * @param deptId 部门ID * @return 结果 */ public int deleteDeptById(Long deptId); /** * 新增保存部门信息 * * @param dept 部门信息 * @return 结果 */ public int insertDept(SysDept dept); /** * 修改保存部门信息 * * @param dept 部门信息 * @return 结果 */ public int updateDept(SysDept dept); /** * 根据部门ID查询信息 * * @param deptId 部门ID * @return 部门信息 */ public SysDept selectDeptById(Long deptId); /** * 根据ID查询所有子部门(正常状态) * * @param deptId 部门ID * @return 子部门数 */ public int selectNormalChildrenDeptById(Long deptId); /** * 校验部门名称是否唯一 * * @param dept 部门信息 * @return 结果 */ public boolean checkDeptNameUnique(SysDept dept); /** * 校验部门是否有数据权限 * * @param deptId 部门id */ public void checkDeptDataScope(Long deptId); /** * 保存部门排序 * * @param deptIds 部门ID数组 * @param orderNums 排序数组 */ public void updateDeptSort(String[] deptIds, String[] orderNums); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictDataService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.common.core.domain.entity.SysDictData; /** * 字典 业务层 * * @author ruoyi */ public interface ISysDictDataService { /** * 根据条件分页查询字典数据 * * @param dictData 字典数据信息 * @return 字典数据集合信息 */ public List selectDictDataList(SysDictData dictData); /** * 根据字典类型和字典键值查询字典数据信息 * * @param dictType 字典类型 * @param dictValue 字典键值 * @return 字典标签 */ public String selectDictLabel(String dictType, String dictValue); /** * 根据字典数据ID查询信息 * * @param dictCode 字典数据ID * @return 字典数据 */ public SysDictData selectDictDataById(Long dictCode); /** * 批量删除字典数据 * * @param ids 需要删除的数据 */ public void deleteDictDataByIds(String ids); /** * 新增保存字典数据信息 * * @param dictData 字典数据信息 * @return 结果 */ public int insertDictData(SysDictData dictData); /** * 修改保存字典数据信息 * * @param dictData 字典数据信息 * @return 结果 */ public int updateDictData(SysDictData dictData); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.domain.entity.SysDictType; /** * 字典 业务层 * * @author ruoyi */ public interface ISysDictTypeService { /** * 根据条件分页查询字典类型 * * @param dictType 字典类型信息 * @return 字典类型集合信息 */ public List selectDictTypeList(SysDictType dictType); /** * 根据所有字典类型 * * @return 字典类型集合信息 */ public List selectDictTypeAll(); /** * 根据字典类型查询字典数据 * * @param dictType 字典类型 * @return 字典数据集合信息 */ public List selectDictDataByType(String dictType); /** * 根据字典类型ID查询信息 * * @param dictId 字典类型ID * @return 字典类型 */ public SysDictType selectDictTypeById(Long dictId); /** * 根据字典类型查询信息 * * @param dictType 字典类型 * @return 字典类型 */ public SysDictType selectDictTypeByType(String dictType); /** * 批量删除字典类型 * * @param ids 需要删除的数据 */ public void deleteDictTypeByIds(String ids); /** * 加载字典缓存数据 */ public void loadingDictCache(); /** * 清空字典缓存数据 */ public void clearDictCache(); /** * 重置字典缓存数据 */ public void resetDictCache(); /** * 新增保存字典类型信息 * * @param dictType 字典类型信息 * @return 结果 */ public int insertDictType(SysDictType dictType); /** * 修改保存字典类型信息 * * @param dictType 字典类型信息 * @return 结果 */ public int updateDictType(SysDictType dictType); /** * 校验字典类型称是否唯一 * * @param dictType 字典类型 * @return 结果 */ public boolean checkDictTypeUnique(SysDictType dictType); /** * 查询字典类型树 * * @param dictType 字典类型 * @return 所有字典类型 */ public List selectDictTree(SysDictType dictType); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysLogininforService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.system.domain.SysLogininfor; /** * 系统访问日志情况信息 服务层 * * @author ruoyi */ public interface ISysLogininforService { /** * 新增系统登录日志 * * @param logininfor 访问日志对象 */ public void insertLogininfor(SysLogininfor logininfor); /** * 查询系统登录日志集合 * * @param logininfor 访问日志对象 * @return 登录记录集合 */ public List selectLogininforList(SysLogininfor logininfor); /** * 批量删除系统登录日志 * * @param ids 需要删除的数据 * @return 结果 */ public int deleteLogininforByIds(String ids); /** * 清空系统登录日志 */ public void cleanLogininfor(); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java ================================================ package com.ruoyi.system.service; import java.util.List; import java.util.Map; import java.util.Set; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysMenu; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; /** * 菜单 业务层 * * @author ruoyi */ public interface ISysMenuService { /** * 根据用户ID查询菜单 * * @param user 用户信息 * @return 菜单列表 */ public List selectMenusByUser(SysUser user); /** * 查询系统菜单列表 * * @param menu 菜单信息 * @param userId 用户ID * @return 菜单列表 */ public List selectMenuList(SysMenu menu, Long userId); /** * 查询菜单集合 * * @param userId 用户ID * @return 所有菜单信息 */ public List selectMenuAll(Long userId); /** * 根据用户ID查询权限 * * @param userId 用户ID * @return 权限列表 */ public Set selectPermsByUserId(Long userId); /** * 根据角色ID查询权限 * * @param roleId 角色ID * @return 权限列表 */ public Set selectPermsByRoleId(Long roleId); /** * 根据角色ID查询菜单 * * @param role 角色对象 * @param userId 用户ID * @return 菜单列表 */ public List roleMenuTreeData(SysRole role, Long userId); /** * 查询所有菜单信息 * * @param userId 用户ID * @return 菜单列表 */ public List menuTreeData(Long userId); /** * 查询系统所有权限 * * @param userId 用户ID * @return 权限列表 */ public Map selectPermsAll(Long userId); /** * 删除菜单管理信息 * * @param menuId 菜单ID * @return 结果 */ public int deleteMenuById(Long menuId); /** * 根据菜单ID查询信息 * * @param menuId 菜单ID * @return 菜单信息 */ public SysMenu selectMenuById(Long menuId); /** * 查询菜单数量 * * @param parentId 菜单父ID * @return 结果 */ public int selectCountMenuByParentId(Long parentId); /** * 查询菜单使用数量 * * @param menuId 菜单ID * @return 结果 */ public int selectCountRoleMenuByMenuId(Long menuId); /** * 新增保存菜单信息 * * @param menu 菜单信息 * @return 结果 */ public int insertMenu(SysMenu menu); /** * 修改保存菜单信息 * * @param menu 菜单信息 * @return 结果 */ public int updateMenu(SysMenu menu); /** * 保存菜单排序 * * @param menuIds 菜单ID * @param orderNums 排序ID */ public void updateMenuSort(String[] menuIds, String[] orderNums); /** * 校验菜单名称是否唯一 * * @param menu 菜单信息 * @return 结果 */ public boolean checkMenuNameUnique(SysMenu menu); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeReadService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.system.domain.SysNotice; /** * 公告已读记录 服务层 * * @author ruoyi */ public interface ISysNoticeReadService { /** * 标记已读(幂等,重复调用不报错) * * @param noticeId 公告ID * @param userId 用户ID */ public void markRead(Long noticeId, Long userId); /** * 查询某用户未读公告数量 * * @param userId 用户ID * @return 未读数量 */ public int selectUnreadCount(Long userId); /** * 查询公告列表并标记当前用户已读状态(用于首页展示) * * @param userId 用户ID * @param limit 最多返回条数 * @return 带 isRead 标记的公告列表 */ public List selectNoticeListWithReadStatus(Long userId, int limit); /** * 批量标记已读 * * @param userId 用户ID * @param noticeIds 公告ID数组 */ public void markReadBatch(Long userId, Long[] noticeIds); /** * 删除公告时清理对应已读记录 * * @param ids 公告ID字符串(逗号分隔) */ public void deleteByNoticeIds(String ids); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysNoticeService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.system.domain.SysNotice; /** * 公告 服务层 * * @author ruoyi */ public interface ISysNoticeService { /** * 查询公告信息 * * @param noticeId 公告ID * @return 公告信息 */ public SysNotice selectNoticeById(Long noticeId); /** * 查询公告列表 * * @param notice 公告信息 * @return 公告集合 */ public List selectNoticeList(SysNotice notice); /** * 新增公告 * * @param notice 公告信息 * @return 结果 */ public int insertNotice(SysNotice notice); /** * 修改公告 * * @param notice 公告信息 * @return 结果 */ public int updateNotice(SysNotice notice); /** * 删除公告信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteNoticeByIds(String ids); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.system.domain.SysOperLog; /** * 操作日志 服务层 * * @author ruoyi */ public interface ISysOperLogService { /** * 新增操作日志 * * @param operLog 操作日志对象 */ public void insertOperlog(SysOperLog operLog); /** * 查询系统操作日志集合 * * @param operLog 操作日志对象 * @return 操作日志集合 */ public List selectOperLogList(SysOperLog operLog); /** * 批量删除系统操作日志 * * @param ids 需要删除的数据 * @return 结果 */ public int deleteOperLogByIds(String ids); /** * 查询操作日志详细 * * @param operId 操作ID * @return 操作日志对象 */ public SysOperLog selectOperLogById(Long operId); /** * 清空操作日志 */ public void cleanOperLog(); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysPostService.java ================================================ package com.ruoyi.system.service; import java.util.List; import com.ruoyi.system.domain.SysPost; /** * 岗位信息 服务层 * * @author ruoyi */ public interface ISysPostService { /** * 查询岗位信息集合 * * @param post 岗位信息 * @return 岗位信息集合 */ public List selectPostList(SysPost post); /** * 查询所有岗位 * * @return 岗位列表 */ public List selectPostAll(); /** * 根据用户ID查询岗位 * * @param userId 用户ID * @return 岗位列表 */ public List selectPostsByUserId(Long userId); /** * 通过岗位ID查询岗位信息 * * @param postId 岗位ID * @return 角色对象信息 */ public SysPost selectPostById(Long postId); /** * 批量删除岗位信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deletePostByIds(String ids); /** * 新增保存岗位信息 * * @param post 岗位信息 * @return 结果 */ public int insertPost(SysPost post); /** * 修改保存岗位信息 * * @param post 岗位信息 * @return 结果 */ public int updatePost(SysPost post); /** * 通过岗位ID查询岗位使用数量 * * @param postId 岗位ID * @return 结果 */ public int countUserPostById(Long postId); /** * 校验岗位名称 * * @param post 岗位信息 * @return 结果 */ public boolean checkPostNameUnique(SysPost post); /** * 校验岗位编码 * * @param post 岗位信息 * @return 结果 */ public boolean checkPostCodeUnique(SysPost post); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java ================================================ package com.ruoyi.system.service; import java.util.List; import java.util.Set; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.system.domain.SysUserRole; /** * 角色业务层 * * @author ruoyi */ public interface ISysRoleService { /** * 根据条件分页查询角色数据 * * @param role 角色信息 * @return 角色数据集合信息 */ public List selectRoleList(SysRole role); /** * 根据用户ID查询角色列表 * * @param userId 用户ID * @return 权限列表 */ public Set selectRoleKeys(Long userId); /** * 根据用户ID查询角色权限 * * @param userId 用户ID * @return 角色列表 */ public List selectRolesByUserId(Long userId); /** * 查询所有角色 * * @return 角色列表 */ public List selectRoleAll(); /** * 通过角色ID查询角色 * * @param roleId 角色ID * @return 角色对象信息 */ public SysRole selectRoleById(Long roleId); /** * 通过角色ID删除角色 * * @param roleId 角色ID * @return 结果 */ public boolean deleteRoleById(Long roleId); /** * 批量删除角色用户信息 * * @param ids 需要删除的数据ID * @return 结果 * @throws Exception 异常 */ public int deleteRoleByIds(String ids); /** * 新增保存角色信息 * * @param role 角色信息 * @return 结果 */ public int insertRole(SysRole role); /** * 修改保存角色信息 * * @param role 角色信息 * @return 结果 */ public int updateRole(SysRole role); /** * 修改数据权限信息 * * @param role 角色信息 * @return 结果 */ public int authDataScope(SysRole role); /** * 校验角色名称是否唯一 * * @param role 角色信息 * @return 结果 */ public boolean checkRoleNameUnique(SysRole role); /** * 校验角色权限是否唯一 * * @param role 角色信息 * @return 结果 */ public boolean checkRoleKeyUnique(SysRole role); /** * 校验角色是否允许操作 * * @param role 角色信息 */ public void checkRoleAllowed(SysRole role); /** * 校验角色是否有数据权限 * * @param roleIds 角色id */ public void checkRoleDataScope(Long... roleIds); /** * 通过角色ID查询角色使用数量 * * @param roleId 角色ID * @return 结果 */ public int countUserRoleByRoleId(Long roleId); /** * 角色状态修改 * * @param role 角色信息 * @return 结果 */ public int changeStatus(SysRole role); /** * 取消授权用户角色 * * @param userRole 用户和角色关联信息 * @return 结果 */ public int deleteAuthUser(SysUserRole userRole); /** * 批量取消授权用户角色 * * @param roleId 角色ID * @param userIds 需要删除的用户数据ID * @return 结果 */ public int deleteAuthUsers(Long roleId, String userIds); /** * 批量选择授权用户角色 * * @param roleId 角色ID * @param userIds 需要删除的用户数据ID * @return 结果 */ public int insertAuthUsers(Long roleId, String userIds); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java ================================================ package com.ruoyi.system.service; import java.util.Date; import java.util.List; import com.ruoyi.system.domain.SysUserOnline; /** * 在线用户 服务层 * * @author ruoyi */ public interface ISysUserOnlineService { /** * 通过会话序号查询信息 * * @param sessionId 会话ID * @return 在线用户信息 */ public SysUserOnline selectOnlineById(String sessionId); /** * 通过会话序号删除信息 * * @param sessionId 会话ID * @return 在线用户信息 */ public void deleteOnlineById(String sessionId); /** * 通过会话序号删除信息 * * @param sessions 会话ID集合 * @return 在线用户信息 */ public void batchDeleteOnline(List sessions); /** * 保存会话信息 * * @param online 会话信息 */ public void saveOnline(SysUserOnline online); /** * 查询会话集合 * * @param userOnline 分页参数 * @return 会话集合 */ public List selectUserOnlineList(SysUserOnline userOnline); /** * 强退用户 * * @param sessionId 会话ID */ public void forceLogout(String sessionId); /** * 清理用户缓存 * * @param loginName 登录名称 * @param sessionId 会话ID */ public void removeUserCache(String loginName, String sessionId); /** * 查询会话集合 * * @param expiredDate 有效期 * @return 会话集合 */ public List selectOnlineByExpired(Date expiredDate); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java ================================================ package com.ruoyi.system.service; import java.util.Date; import java.util.List; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.system.domain.SysUserRole; /** * 用户 业务层 * * @author ruoyi */ public interface ISysUserService { /** * 根据条件分页查询用户列表 * * @param user 用户信息 * @return 用户信息集合信息 */ public List selectUserList(SysUser user); /** * 根据条件分页查询已分配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ public List selectAllocatedList(SysUser user); /** * 根据条件分页查询未分配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ public List selectUnallocatedList(SysUser user); /** * 通过用户名查询用户 * * @param userName 用户名 * @return 用户对象信息 */ public SysUser selectUserByLoginName(String userName); /** * 通过手机号码查询用户 * * @param phoneNumber 手机号码 * @return 用户对象信息 */ public SysUser selectUserByPhoneNumber(String phoneNumber); /** * 通过邮箱查询用户 * * @param email 邮箱 * @return 用户对象信息 */ public SysUser selectUserByEmail(String email); /** * 通过用户ID查询用户 * * @param userId 用户ID * @return 用户对象信息 */ public SysUser selectUserById(Long userId); /** * 通过用户ID查询用户和角色关联 * * @param userId 用户ID * @return 用户和角色关联列表 */ public List selectUserRoleByUserId(Long userId); /** * 通过用户ID删除用户 * * @param userId 用户ID * @return 结果 */ public int deleteUserById(Long userId); /** * 批量删除用户信息 * * @param ids 需要删除的数据ID * @return 结果 * @throws Exception 异常 */ public int deleteUserByIds(String ids); /** * 保存用户信息 * * @param user 用户信息 * @return 结果 */ public int insertUser(SysUser user); /** * 注册用户信息 * * @param user 用户信息 * @return 结果 */ public boolean registerUser(SysUser user); /** * 保存用户信息 * * @param user 用户信息 * @return 结果 */ public int updateUser(SysUser user); /** * 修改用户详细信息 * * @param user 用户信息 * @return 结果 */ public int updateUserInfo(SysUser user); /** * 修改用户头像 * * @param userId 用户ID * @param avatar 头像地址 * @return 结果 */ public boolean updateUserAvatar(Long userId, String avatar); /** * 更新用户登录信息(IP和登录时间) * * @param userId 用户ID * @param loginIp 登录IP地址 * @param loginDate 登录时间 * @return 结果 */ public void updateLoginInfo(Long userId, String loginIp, Date loginDate); /** * 用户授权角色 * * @param userId 用户ID * @param roleIds 角色组 */ public void insertUserAuth(Long userId, Long[] roleIds); /** * 修改用户密码信息 * * @param user 用户信息 * @return 结果 */ public int resetUserPwd(SysUser user); /** * 校验用户名称是否唯一 * * @param user 用户信息 * @return 结果 */ public boolean checkLoginNameUnique(SysUser user); /** * 校验手机号码是否唯一 * * @param user 用户信息 * @return 结果 */ public boolean checkPhoneUnique(SysUser user); /** * 校验email是否唯一 * * @param user 用户信息 * @return 结果 */ public boolean checkEmailUnique(SysUser user); /** * 校验用户是否允许操作 * * @param user 用户信息 */ public void checkUserAllowed(SysUser user); /** * 校验用户是否有数据权限 * * @param userId 用户id */ public void checkUserDataScope(Long userId); /** * 根据用户ID查询用户所属角色组 * * @param userId 用户ID * @return 结果 */ public String selectUserRoleGroup(Long userId); /** * 根据用户ID查询用户所属岗位组 * * @param userId 用户ID * @return 结果 */ public String selectUserPostGroup(Long userId); /** * 导入用户数据 * * @param userList 用户数据列表 * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 * @param operName 操作用户 * @return 结果 */ public String importUser(List userList, Boolean isUpdateSupport, String operName); /** * 用户状态修改 * * @param user 用户信息 * @return 结果 */ public int changeStatus(SysUser user); } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.List; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.CacheUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.service.ISysConfigService; /** * 参数配置 服务层实现 * * @author ruoyi */ @Service public class SysConfigServiceImpl implements ISysConfigService { @Autowired private SysConfigMapper configMapper; /** * 项目启动时,初始化参数到缓存 */ @PostConstruct public void init() { loadingConfigCache(); } /** * 查询参数配置信息 * * @param configId 参数配置ID * @return 参数配置信息 */ @Override public SysConfig selectConfigById(Long configId) { SysConfig config = new SysConfig(); config.setConfigId(configId); return configMapper.selectConfig(config); } /** * 根据键名查询参数配置信息 * * @param configKey 参数key * @return 参数键值 */ @Override public String selectConfigByKey(String configKey) { String configValue = Convert.toStr(CacheUtils.get(getCacheName(), getCacheKey(configKey))); if (StringUtils.isNotEmpty(configValue)) { return configValue; } SysConfig config = new SysConfig(); config.setConfigKey(configKey); SysConfig retConfig = configMapper.selectConfig(config); if (StringUtils.isNotNull(retConfig)) { CacheUtils.put(getCacheName(), getCacheKey(configKey), retConfig.getConfigValue()); return retConfig.getConfigValue(); } return StringUtils.EMPTY; } /** * 查询参数配置列表 * * @param config 参数配置信息 * @return 参数配置集合 */ @Override public List selectConfigList(SysConfig config) { return configMapper.selectConfigList(config); } /** * 新增参数配置 * * @param config 参数配置信息 * @return 结果 */ @Override public int insertConfig(SysConfig config) { int row = configMapper.insertConfig(config); if (row > 0) { CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); } return row; } /** * 修改参数配置 * * @param config 参数配置信息 * @return 结果 */ @Override public int updateConfig(SysConfig config) { SysConfig temp = configMapper.selectConfigById(config.getConfigId()); if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) { CacheUtils.remove(getCacheName(), getCacheKey(temp.getConfigKey())); } int row = configMapper.updateConfig(config); if (row > 0) { CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); } return row; } /** * 批量删除参数配置对象 * * @param ids 需要删除的数据ID */ @Override public void deleteConfigByIds(String ids) { Long[] configIds = Convert.toLongArray(ids); for (Long configId : configIds) { SysConfig config = selectConfigById(configId); if (StringUtils.equals(UserConstants.YES, config.getConfigType())) { throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); } configMapper.deleteConfigById(configId); CacheUtils.remove(getCacheName(), getCacheKey(config.getConfigKey())); } } /** * 加载参数缓存数据 */ @Override public void loadingConfigCache() { List configsList = configMapper.selectConfigList(new SysConfig()); for (SysConfig config : configsList) { CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); } } /** * 清空参数缓存数据 */ @Override public void clearConfigCache() { CacheUtils.removeAll(getCacheName()); } /** * 重置参数缓存数据 */ @Override public void resetConfigCache() { clearConfigCache(); loadingConfigCache(); } /** * 校验参数键名是否唯一 * * @param config 参数配置信息 * @return 结果 */ @Override public boolean checkConfigKeyUnique(SysConfig config) { Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId(); SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey()); if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 获取cache name * * @return 缓存名 */ private String getCacheName() { return Constants.SYS_CONFIG_CACHE; } /** * 设置cache key * * @param configKey 参数键 * @return 缓存键key */ private String getCacheKey(String configKey) { return Constants.SYS_CONFIG_KEY + configKey; } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.mapper.SysDeptMapper; import com.ruoyi.system.service.ISysDeptService; /** * 部门管理 服务实现 * * @author ruoyi */ @Service public class SysDeptServiceImpl implements ISysDeptService { @Autowired private SysDeptMapper deptMapper; /** * 查询部门管理数据 * * @param dept 部门信息 * @return 部门信息集合 */ @Override @DataScope(deptAlias = "d") public List selectDeptList(SysDept dept) { return deptMapper.selectDeptList(dept); } /** * 查询部门管理树 * * @param dept 部门信息 * @return 所有部门信息 */ @Override @DataScope(deptAlias = "d") public List selectDeptTree(SysDept dept) { List deptList = deptMapper.selectDeptList(dept); List ztrees = initZtree(deptList); return ztrees; } /** * 查询部门管理树(排除下级) * * @param deptId 部门ID * @return 所有部门信息 */ @Override @DataScope(deptAlias = "d") public List selectDeptTreeExcludeChild(SysDept dept) { Long excludeId = dept.getExcludeId(); List depts = deptMapper.selectDeptList(dept); if (excludeId.intValue() > 0) { depts.removeIf(d -> d.getDeptId().intValue() == excludeId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), excludeId + "")); } List ztrees = initZtree(depts); return ztrees; } /** * 根据角色ID查询部门(数据权限) * * @param role 角色对象 * @return 部门列表(数据权限) */ @Override public List roleDeptTreeData(SysRole role) { Long roleId = role.getRoleId(); List ztrees = new ArrayList(); List deptList = SpringUtils.getAopProxy(this).selectDeptList(new SysDept()); if (StringUtils.isNotNull(roleId)) { List roleDeptList = deptMapper.selectRoleDeptTree(roleId); ztrees = initZtree(deptList, roleDeptList); } else { ztrees = initZtree(deptList); } return ztrees; } /** * 对象转部门树 * * @param deptList 部门列表 * @return 树结构列表 */ public List initZtree(List deptList) { return initZtree(deptList, null); } /** * 对象转部门树 * * @param deptList 部门列表 * @param roleDeptList 角色已存在菜单列表 * @return 树结构列表 */ public List initZtree(List deptList, List roleDeptList) { List ztrees = new ArrayList(); boolean isCheck = StringUtils.isNotNull(roleDeptList); for (SysDept dept : deptList) { if (UserConstants.DEPT_NORMAL.equals(dept.getStatus())) { Ztree ztree = new Ztree(); ztree.setId(dept.getDeptId()); ztree.setpId(dept.getParentId()); ztree.setName(dept.getDeptName()); ztree.setTitle(dept.getDeptName()); if (isCheck) { ztree.setChecked(roleDeptList.contains(dept.getDeptId() + dept.getDeptName())); } ztrees.add(ztree); } } return ztrees; } /** * 根据父部门ID查询下级部门数量 * * @param parentId 部门ID * @return 结果 */ @Override public int selectDeptCount(Long parentId) { SysDept dept = new SysDept(); dept.setParentId(parentId); return deptMapper.selectDeptCount(dept); } /** * 查询部门是否存在用户 * * @param deptId 部门ID * @return 结果 true 存在 false 不存在 */ @Override public boolean checkDeptExistUser(Long deptId) { int result = deptMapper.checkDeptExistUser(deptId); return result > 0; } /** * 删除部门管理信息 * * @param deptId 部门ID * @return 结果 */ @Override public int deleteDeptById(Long deptId) { return deptMapper.deleteDeptById(deptId); } /** * 新增保存部门信息 * * @param dept 部门信息 * @return 结果 */ @Override public int insertDept(SysDept dept) { SysDept info = deptMapper.selectDeptById(dept.getParentId()); // 如果父节点不为"正常"状态,则不允许新增子节点 if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) { throw new ServiceException("部门停用,不允许新增"); } dept.setAncestors(info.getAncestors() + "," + dept.getParentId()); return deptMapper.insertDept(dept); } /** * 修改保存部门信息 * * @param dept 部门信息 * @return 结果 */ @Override @Transactional public int updateDept(SysDept dept) { SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId()); SysDept oldDept = selectDeptById(dept.getDeptId()); if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept)) { String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId(); String oldAncestors = oldDept.getAncestors(); dept.setAncestors(newAncestors); updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); } int result = deptMapper.updateDept(dept); if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors()) && !StringUtils.equals("0", dept.getAncestors())) { // 如果该部门是启用状态,则启用该部门的所有上级部门 updateParentDeptStatusNormal(dept); } return result; } /** * 修改该部门的父级部门状态 * * @param dept 当前部门 */ private void updateParentDeptStatusNormal(SysDept dept) { String ancestors = dept.getAncestors(); Long[] deptIds = Convert.toLongArray(ancestors); deptMapper.updateDeptStatusNormal(deptIds); } /** * 修改子元素关系 * * @param deptId 被修改的部门ID * @param newAncestors 新的父ID集合 * @param oldAncestors 旧的父ID集合 */ public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) { List children = deptMapper.selectChildrenDeptById(deptId); for (SysDept child : children) { child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); } if (children.size() > 0) { deptMapper.updateDeptChildren(children); } } /** * 根据部门ID查询信息 * * @param deptId 部门ID * @return 部门信息 */ @Override public SysDept selectDeptById(Long deptId) { return deptMapper.selectDeptById(deptId); } /** * 根据ID查询所有子部门(正常状态) * * @param deptId 部门ID * @return 子部门数 */ @Override public int selectNormalChildrenDeptById(Long deptId) { return deptMapper.selectNormalChildrenDeptById(deptId); } /** * 校验部门名称是否唯一 * * @param dept 部门信息 * @return 结果 */ @Override public boolean checkDeptNameUnique(SysDept dept) { Long deptId = StringUtils.isNull(dept.getDeptId()) ? -1L : dept.getDeptId(); SysDept info = deptMapper.checkDeptNameUnique(dept.getDeptName(), dept.getParentId()); if (StringUtils.isNotNull(info) && info.getDeptId().longValue() != deptId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 校验部门是否有数据权限 * * @param deptId 部门id */ @Override public void checkDeptDataScope(Long deptId) { if (!ShiroUtils.isAdmin() && StringUtils.isNotNull(deptId)) { SysDept dept = new SysDept(); dept.setDeptId(deptId); List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); if (StringUtils.isEmpty(depts)) { throw new ServiceException("没有权限访问部门数据!"); } } } /** * 保存部门排序 * * @param deptIds 部门ID数组 * @param orderNums 排序数组 */ @Override @Transactional public void updateDeptSort(String[] deptIds, String[] orderNums) { try { for (int i = 0; i < deptIds.length; i++) { SysDept dept = new SysDept(); dept.setDeptId(Convert.toLong(deptIds[i])); dept.setOrderNum(Convert.toInt(orderNums[i])); deptMapper.updateDeptSort(dept); } } catch (Exception e) { throw new ServiceException("保存排序异常,请联系管理员"); } } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictDataServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.DictUtils; import com.ruoyi.system.mapper.SysDictDataMapper; import com.ruoyi.system.service.ISysDictDataService; /** * 字典 业务层处理 * * @author ruoyi */ @Service public class SysDictDataServiceImpl implements ISysDictDataService { @Autowired private SysDictDataMapper dictDataMapper; /** * 根据条件分页查询字典数据 * * @param dictData 字典数据信息 * @return 字典数据集合信息 */ @Override public List selectDictDataList(SysDictData dictData) { return dictDataMapper.selectDictDataList(dictData); } /** * 根据字典类型和字典键值查询字典数据信息 * * @param dictType 字典类型 * @param dictValue 字典键值 * @return 字典标签 */ @Override public String selectDictLabel(String dictType, String dictValue) { return dictDataMapper.selectDictLabel(dictType, dictValue); } /** * 根据字典数据ID查询信息 * * @param dictCode 字典数据ID * @return 字典数据 */ @Override public SysDictData selectDictDataById(Long dictCode) { return dictDataMapper.selectDictDataById(dictCode); } /** * 批量删除字典数据 * * @param ids 需要删除的数据 */ @Override public void deleteDictDataByIds(String ids) { Long[] dictCodes = Convert.toLongArray(ids); for (Long dictCode : dictCodes) { SysDictData data = selectDictDataById(dictCode); dictDataMapper.deleteDictDataById(dictCode); List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); DictUtils.setDictCache(data.getDictType(), dictDatas); } } /** * 新增保存字典数据信息 * * @param data 字典数据信息 * @return 结果 */ @Override public int insertDictData(SysDictData data) { int row = dictDataMapper.insertDictData(data); if (row > 0) { List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); DictUtils.setDictCache(data.getDictType(), dictDatas); } return row; } /** * 修改保存字典数据信息 * * @param data 字典数据信息 * @return 结果 */ @Override public int updateDictData(SysDictData data) { int row = dictDataMapper.updateDictData(data); if (row > 0) { List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); DictUtils.setDictCache(data.getDictType(), dictDatas); } return row; } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.domain.entity.SysDictType; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.DictUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.mapper.SysDictDataMapper; import com.ruoyi.system.mapper.SysDictTypeMapper; import com.ruoyi.system.service.ISysDictTypeService; /** * 字典 业务层处理 * * @author ruoyi */ @Service public class SysDictTypeServiceImpl implements ISysDictTypeService { @Autowired private SysDictTypeMapper dictTypeMapper; @Autowired private SysDictDataMapper dictDataMapper; /** * 项目启动时,初始化字典到缓存 */ @PostConstruct public void init() { loadingDictCache(); } /** * 根据条件分页查询字典类型 * * @param dictType 字典类型信息 * @return 字典类型集合信息 */ @Override public List selectDictTypeList(SysDictType dictType) { return dictTypeMapper.selectDictTypeList(dictType); } /** * 根据所有字典类型 * * @return 字典类型集合信息 */ @Override public List selectDictTypeAll() { return dictTypeMapper.selectDictTypeAll(); } /** * 根据字典类型查询字典数据 * * @param dictType 字典类型 * @return 字典数据集合信息 */ @Override public List selectDictDataByType(String dictType) { List dictDatas = DictUtils.getDictCache(dictType); if (StringUtils.isNotEmpty(dictDatas)) { return dictDatas; } dictDatas = dictDataMapper.selectDictDataByType(dictType); if (StringUtils.isNotEmpty(dictDatas)) { DictUtils.setDictCache(dictType, dictDatas); return dictDatas; } return null; } /** * 根据字典类型ID查询信息 * * @param dictId 字典类型ID * @return 字典类型 */ @Override public SysDictType selectDictTypeById(Long dictId) { return dictTypeMapper.selectDictTypeById(dictId); } /** * 根据字典类型查询信息 * * @param dictType 字典类型 * @return 字典类型 */ @Override public SysDictType selectDictTypeByType(String dictType) { return dictTypeMapper.selectDictTypeByType(dictType); } /** * 批量删除字典类型 * * @param ids 需要删除的数据 */ @Override public void deleteDictTypeByIds(String ids) { Long[] dictIds = Convert.toLongArray(ids); for (Long dictId : dictIds) { SysDictType dictType = selectDictTypeById(dictId); if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0) { throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName())); } dictTypeMapper.deleteDictTypeById(dictId); DictUtils.removeDictCache(dictType.getDictType()); } } /** * 加载字典缓存数据 */ @Override public void loadingDictCache() { SysDictData dictData = new SysDictData(); dictData.setStatus("0"); Map> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType)); for (Map.Entry> entry : dictDataMap.entrySet()) { DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList())); } } /** * 清空字典缓存数据 */ @Override public void clearDictCache() { DictUtils.clearDictCache(); } /** * 重置字典缓存数据 */ @Override public void resetDictCache() { clearDictCache(); loadingDictCache(); } /** * 新增保存字典类型信息 * * @param dict 字典类型信息 * @return 结果 */ @Override public int insertDictType(SysDictType dict) { int row = dictTypeMapper.insertDictType(dict); if (row > 0) { DictUtils.setDictCache(dict.getDictType(), null); } return row; } /** * 修改保存字典类型信息 * * @param dict 字典类型信息 * @return 结果 */ @Override @Transactional public int updateDictType(SysDictType dict) { SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId()); dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType()); int row = dictTypeMapper.updateDictType(dict); if (row > 0) { List dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType()); DictUtils.setDictCache(dict.getDictType(), dictDatas); } return row; } /** * 校验字典类型称是否唯一 * * @param dict 字典类型 * @return 结果 */ @Override public boolean checkDictTypeUnique(SysDictType dict) { Long dictId = StringUtils.isNull(dict.getDictId()) ? -1L : dict.getDictId(); SysDictType dictType = dictTypeMapper.checkDictTypeUnique(dict.getDictType()); if (StringUtils.isNotNull(dictType) && dictType.getDictId().longValue() != dictId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 查询字典类型树 * * @param dictType 字典类型 * @return 所有字典类型 */ @Override public List selectDictTree(SysDictType dictType) { List ztrees = new ArrayList(); List dictList = dictTypeMapper.selectDictTypeList(dictType); for (SysDictType dict : dictList) { if (UserConstants.DICT_NORMAL.equals(dict.getStatus())) { Ztree ztree = new Ztree(); ztree.setId(dict.getDictId()); ztree.setName(transDictName(dict)); ztree.setTitle(dict.getDictType()); ztrees.add(ztree); } } return ztrees; } public String transDictName(SysDictType dictType) { StringBuffer sb = new StringBuffer(); sb.append("(" + dictType.getDictName() + ")"); sb.append("   " + dictType.getDictType()); return sb.toString(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysLogininforServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.core.text.Convert; import com.ruoyi.system.domain.SysLogininfor; import com.ruoyi.system.mapper.SysLogininforMapper; import com.ruoyi.system.service.ISysLogininforService; /** * 系统访问日志情况信息 服务层处理 * * @author ruoyi */ @Service public class SysLogininforServiceImpl implements ISysLogininforService { @Autowired private SysLogininforMapper logininforMapper; /** * 新增系统登录日志 * * @param logininfor 访问日志对象 */ @Override public void insertLogininfor(SysLogininfor logininfor) { logininforMapper.insertLogininfor(logininfor); } /** * 查询系统登录日志集合 * * @param logininfor 访问日志对象 * @return 登录记录集合 */ @Override public List selectLogininforList(SysLogininfor logininfor) { return logininforMapper.selectLogininforList(logininfor); } /** * 批量删除系统登录日志 * * @param ids 需要删除的数据 * @return 结果 */ @Override public int deleteLogininforByIds(String ids) { return logininforMapper.deleteLogininforByIds(Convert.toStrArray(ids)); } /** * 清空系统登录日志 */ @Override public void cleanLogininfor() { logininforMapper.cleanLogininfor(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.Ztree; import com.ruoyi.common.core.domain.entity.SysMenu; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.mapper.SysMenuMapper; import com.ruoyi.system.mapper.SysRoleMenuMapper; import com.ruoyi.system.service.ISysMenuService; /** * 菜单 业务层处理 * * @author ruoyi */ @Service public class SysMenuServiceImpl implements ISysMenuService { public static final String PREMISSION_STRING = "perms[\"{0}\"]"; @Autowired private SysMenuMapper menuMapper; @Autowired private SysRoleMenuMapper roleMenuMapper; /** * 根据用户查询菜单 * * @param user 用户信息 * @return 菜单列表 */ @Override public List selectMenusByUser(SysUser user) { List menus = new LinkedList(); // 管理员显示所有菜单信息 if (user.isAdmin()) { menus = menuMapper.selectMenuNormalAll(); } else { menus = menuMapper.selectMenusByUserId(user.getUserId()); } return getChildPerms(menus, 0); } /** * 查询菜单集合 * * @return 所有菜单信息 */ @Override public List selectMenuList(SysMenu menu, Long userId) { List menuList = null; if (ShiroUtils.isAdmin(userId)) { menuList = menuMapper.selectMenuList(menu); } else { menu.getParams().put("userId", userId); menuList = menuMapper.selectMenuListByUserId(menu); } return menuList; } /** * 查询菜单集合 * * @return 所有菜单信息 */ @Override public List selectMenuAll(Long userId) { List menuList = null; if (ShiroUtils.isAdmin(userId)) { menuList = menuMapper.selectMenuAll(); } else { menuList = menuMapper.selectMenuAllByUserId(userId); } return menuList; } /** * 根据用户ID查询权限 * * @param userId 用户ID * @return 权限列表 */ @Override public Set selectPermsByUserId(Long userId) { List perms = menuMapper.selectPermsByUserId(userId); Set permsSet = new HashSet<>(); for (String perm : perms) { if (StringUtils.isNotEmpty(perm)) { permsSet.addAll(Arrays.asList(perm.trim().split(","))); } } return permsSet; } /** * 根据角色ID查询权限 * * @param roleId 角色ID * @return 权限列表 */ @Override public Set selectPermsByRoleId(Long roleId) { List perms = menuMapper.selectPermsByRoleId(roleId); Set permsSet = new HashSet<>(); for (String perm : perms) { if (StringUtils.isNotEmpty(perm)) { permsSet.addAll(Arrays.asList(perm.trim().split(","))); } } return permsSet; } /** * 根据角色ID查询菜单 * * @param role 角色对象 * @return 菜单列表 */ @Override public List roleMenuTreeData(SysRole role, Long userId) { Long roleId = role.getRoleId(); List ztrees = new ArrayList(); List menuList = selectMenuAll(userId); if (StringUtils.isNotNull(roleId)) { List roleMenuList = menuMapper.selectMenuTree(roleId); ztrees = initZtree(menuList, roleMenuList, true); } else { ztrees = initZtree(menuList, null, true); } return ztrees; } /** * 查询所有菜单 * * @return 菜单列表 */ @Override public List menuTreeData(Long userId) { List menuList = selectMenuAll(userId); List ztrees = initZtree(menuList); return ztrees; } /** * 查询系统所有权限 * * @return 权限列表 */ @Override public LinkedHashMap selectPermsAll(Long userId) { LinkedHashMap section = new LinkedHashMap<>(); List permissions = selectMenuAll(userId); if (StringUtils.isNotEmpty(permissions)) { for (SysMenu menu : permissions) { section.put(menu.getUrl(), MessageFormat.format(PREMISSION_STRING, menu.getPerms())); } } return section; } /** * 对象转菜单树 * * @param menuList 菜单列表 * @return 树结构列表 */ public List initZtree(List menuList) { return initZtree(menuList, null, false); } /** * 对象转菜单树 * * @param menuList 菜单列表 * @param roleMenuList 角色已存在菜单列表 * @param permsFlag 是否需要显示权限标识 * @return 树结构列表 */ public List initZtree(List menuList, List roleMenuList, boolean permsFlag) { List ztrees = new ArrayList(); boolean isCheck = StringUtils.isNotNull(roleMenuList); for (SysMenu menu : menuList) { Ztree ztree = new Ztree(); ztree.setId(menu.getMenuId()); ztree.setpId(menu.getParentId()); ztree.setName(transMenuName(menu, permsFlag)); ztree.setTitle(menu.getMenuName()); if (isCheck) { ztree.setChecked(roleMenuList.contains(menu.getMenuId() + menu.getPerms())); } ztrees.add(ztree); } return ztrees; } public String transMenuName(SysMenu menu, boolean permsFlag) { StringBuffer sb = new StringBuffer(); sb.append(menu.getMenuName()); if (permsFlag) { sb.append("   " + menu.getPerms() + ""); } return sb.toString(); } /** * 删除菜单管理信息 * * @param menuId 菜单ID * @return 结果 */ @Override public int deleteMenuById(Long menuId) { return menuMapper.deleteMenuById(menuId); } /** * 根据菜单ID查询信息 * * @param menuId 菜单ID * @return 菜单信息 */ @Override public SysMenu selectMenuById(Long menuId) { return menuMapper.selectMenuById(menuId); } /** * 查询子菜单数量 * * @param parentId 父级菜单ID * @return 结果 */ @Override public int selectCountMenuByParentId(Long parentId) { return menuMapper.selectCountMenuByParentId(parentId); } /** * 查询菜单使用数量 * * @param menuId 菜单ID * @return 结果 */ @Override public int selectCountRoleMenuByMenuId(Long menuId) { return roleMenuMapper.selectCountRoleMenuByMenuId(menuId); } /** * 新增保存菜单信息 * * @param menu 菜单信息 * @return 结果 */ @Override public int insertMenu(SysMenu menu) { return menuMapper.insertMenu(menu); } /** * 修改保存菜单信息 * * @param menu 菜单信息 * @return 结果 */ @Override public int updateMenu(SysMenu menu) { return menuMapper.updateMenu(menu); } /** * 保存菜单排序 * * @param menuIds 菜单ID * @param orderNums 排序ID */ @Override @Transactional public void updateMenuSort(String[] menuIds, String[] orderNums) { try { for (int i = 0; i < menuIds.length; i++) { SysMenu menu = new SysMenu(); menu.setMenuId(Convert.toLong(menuIds[i])); menu.setOrderNum(orderNums[i]); menuMapper.updateMenuSort(menu); } } catch (Exception e) { throw new ServiceException("保存排序异常,请联系管理员"); } } /** * 校验菜单名称是否唯一 * * @param menu 菜单信息 * @return 结果 */ @Override public boolean checkMenuNameUnique(SysMenu menu) { Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId(); SysMenu info = menuMapper.checkMenuNameUnique(menu.getMenuName(), menu.getParentId()); if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 根据父节点的ID获取所有子节点 * * @param list 分类表 * @param parentId 传入的父节点ID * @return String */ public List getChildPerms(List list, int parentId) { List returnList = new ArrayList(); for (Iterator iterator = list.iterator(); iterator.hasNext();) { SysMenu t = (SysMenu) iterator.next(); // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点 if (t.getParentId() == parentId) { recursionFn(list, t); returnList.add(t); } } return returnList; } /** * 递归列表 * * @param list * @param t */ private void recursionFn(List list, SysMenu t) { // 得到子节点列表 List childList = getChildList(list, t); t.setChildren(childList); for (SysMenu tChild : childList) { if (hasChild(list, tChild)) { recursionFn(list, tChild); } } } /** * 得到子节点列表 */ private List getChildList(List list, SysMenu t) { List tlist = new ArrayList(); Iterator it = list.iterator(); while (it.hasNext()) { SysMenu n = (SysMenu) it.next(); if (n.getParentId().longValue() == t.getMenuId().longValue()) { tlist.add(n); } } return tlist; } /** * 判断是否有子节点 */ private boolean hasChild(List list, SysMenu t) { return getChildList(list, t).size() > 0; } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeReadServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.core.text.Convert; import com.ruoyi.system.domain.SysNoticeRead; import com.ruoyi.system.domain.SysNotice; import com.ruoyi.system.mapper.SysNoticeReadMapper; import com.ruoyi.system.service.ISysNoticeReadService; /** * 公告已读记录 服务层实现 * * @author ruoyi */ @Service public class SysNoticeReadServiceImpl implements ISysNoticeReadService { @Autowired private SysNoticeReadMapper noticeReadMapper; /** * 标记已读 */ @Override public void markRead(Long noticeId, Long userId) { SysNoticeRead record = new SysNoticeRead(); record.setNoticeId(noticeId); record.setUserId(userId); noticeReadMapper.insertNoticeRead(record); } /** * 查询某用户未读公告数量 */ @Override public int selectUnreadCount(Long userId) { return noticeReadMapper.selectUnreadCount(userId); } /** * 查询公告列表并标记当前用户已读状态 */ @Override public List selectNoticeListWithReadStatus(Long userId, int limit) { return noticeReadMapper.selectNoticeListWithReadStatus(userId, limit); } /** * 批量标记已读 */ @Override public void markReadBatch(Long userId, Long[] noticeIds) { if (noticeIds == null || noticeIds.length == 0) { return; } noticeReadMapper.insertNoticeReadBatch(userId, noticeIds); } /** * 删除公告时清理对应已读记录 */ @Override public void deleteByNoticeIds(String ids) { noticeReadMapper.deleteByNoticeIds(Convert.toStrArray(ids)); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysNoticeServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.core.text.Convert; import com.ruoyi.system.domain.SysNotice; import com.ruoyi.system.mapper.SysNoticeMapper; import com.ruoyi.system.service.ISysNoticeService; /** * 公告 服务层实现 * * @author ruoyi * @date 2018-06-25 */ @Service public class SysNoticeServiceImpl implements ISysNoticeService { @Autowired private SysNoticeMapper noticeMapper; /** * 查询公告信息 * * @param noticeId 公告ID * @return 公告信息 */ @Override public SysNotice selectNoticeById(Long noticeId) { return noticeMapper.selectNoticeById(noticeId); } /** * 查询公告列表 * * @param notice 公告信息 * @return 公告集合 */ @Override public List selectNoticeList(SysNotice notice) { return noticeMapper.selectNoticeList(notice); } /** * 新增公告 * * @param notice 公告信息 * @return 结果 */ @Override public int insertNotice(SysNotice notice) { return noticeMapper.insertNotice(notice); } /** * 修改公告 * * @param notice 公告信息 * @return 结果 */ @Override public int updateNotice(SysNotice notice) { return noticeMapper.updateNotice(notice); } /** * 删除公告对象 * * @param ids 需要删除的数据ID * @return 结果 */ @Override public int deleteNoticeByIds(String ids) { return noticeMapper.deleteNoticeByIds(Convert.toStrArray(ids)); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOperLogServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.core.text.Convert; import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.mapper.SysOperLogMapper; import com.ruoyi.system.service.ISysOperLogService; /** * 操作日志 服务层处理 * * @author ruoyi */ @Service public class SysOperLogServiceImpl implements ISysOperLogService { @Autowired private SysOperLogMapper operLogMapper; /** * 新增操作日志 * * @param operLog 操作日志对象 */ @Override public void insertOperlog(SysOperLog operLog) { operLogMapper.insertOperlog(operLog); } /** * 查询系统操作日志集合 * * @param operLog 操作日志对象 * @return 操作日志集合 */ @Override public List selectOperLogList(SysOperLog operLog) { return operLogMapper.selectOperLogList(operLog); } /** * 批量删除系统操作日志 * * @param ids 需要删除的数据 * @return */ @Override public int deleteOperLogByIds(String ids) { return operLogMapper.deleteOperLogByIds(Convert.toStrArray(ids)); } /** * 查询操作日志详细 * * @param operId 操作ID * @return 操作日志对象 */ @Override public SysOperLog selectOperLogById(Long operId) { return operLogMapper.selectOperLogById(operId); } /** * 清空操作日志 */ @Override public void cleanOperLog() { operLogMapper.cleanOperLog(); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysPostServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.SysPost; import com.ruoyi.system.mapper.SysPostMapper; import com.ruoyi.system.mapper.SysUserPostMapper; import com.ruoyi.system.service.ISysPostService; /** * 岗位信息 服务层处理 * * @author ruoyi */ @Service public class SysPostServiceImpl implements ISysPostService { @Autowired private SysPostMapper postMapper; @Autowired private SysUserPostMapper userPostMapper; /** * 查询岗位信息集合 * * @param post 岗位信息 * @return 岗位信息集合 */ @Override public List selectPostList(SysPost post) { return postMapper.selectPostList(post); } /** * 查询所有岗位 * * @return 岗位列表 */ @Override public List selectPostAll() { return postMapper.selectPostAll(); } /** * 根据用户ID查询岗位 * * @param userId 用户ID * @return 岗位列表 */ @Override public List selectPostsByUserId(Long userId) { List userPosts = postMapper.selectPostsByUserId(userId); List posts = postMapper.selectPostAll(); for (SysPost post : posts) { for (SysPost userRole : userPosts) { if (post.getPostId().longValue() == userRole.getPostId().longValue()) { post.setFlag(true); break; } } } return posts; } /** * 通过岗位ID查询岗位信息 * * @param postId 岗位ID * @return 角色对象信息 */ @Override public SysPost selectPostById(Long postId) { return postMapper.selectPostById(postId); } /** * 批量删除岗位信息 * * @param ids 需要删除的数据ID * @return 结果 */ @Override public int deletePostByIds(String ids) { Long[] postIds = Convert.toLongArray(ids); for (Long postId : postIds) { SysPost post = selectPostById(postId); if (countUserPostById(postId) > 0) { throw new ServiceException(String.format("%1$s已分配,不能删除", post.getPostName())); } } return postMapper.deletePostByIds(postIds); } /** * 新增保存岗位信息 * * @param post 岗位信息 * @return 结果 */ @Override public int insertPost(SysPost post) { return postMapper.insertPost(post); } /** * 修改保存岗位信息 * * @param post 岗位信息 * @return 结果 */ @Override public int updatePost(SysPost post) { return postMapper.updatePost(post); } /** * 通过岗位ID查询岗位使用数量 * * @param postId 岗位ID * @return 结果 */ @Override public int countUserPostById(Long postId) { return userPostMapper.countUserPostById(postId); } /** * 校验岗位名称是否唯一 * * @param post 岗位信息 * @return 结果 */ @Override public boolean checkPostNameUnique(SysPost post) { Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); SysPost info = postMapper.checkPostNameUnique(post.getPostName()); if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 校验岗位编码是否唯一 * * @param post 岗位信息 * @return 结果 */ @Override public boolean checkPostCodeUnique(SysPost post) { Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); SysPost info = postMapper.checkPostCodeUnique(post.getPostCode()); if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.domain.SysRoleDept; import com.ruoyi.system.domain.SysRoleMenu; import com.ruoyi.system.domain.SysUserRole; import com.ruoyi.system.mapper.SysRoleDeptMapper; import com.ruoyi.system.mapper.SysRoleMapper; import com.ruoyi.system.mapper.SysRoleMenuMapper; import com.ruoyi.system.mapper.SysUserRoleMapper; import com.ruoyi.system.service.ISysRoleService; /** * 角色 业务层处理 * * @author ruoyi */ @Service public class SysRoleServiceImpl implements ISysRoleService { @Autowired private SysRoleMapper roleMapper; @Autowired private SysRoleMenuMapper roleMenuMapper; @Autowired private SysUserRoleMapper userRoleMapper; @Autowired private SysRoleDeptMapper roleDeptMapper; /** * 根据条件分页查询角色数据 * * @param role 角色信息 * @return 角色数据集合信息 */ @Override @DataScope(deptAlias = "d") public List selectRoleList(SysRole role) { return roleMapper.selectRoleList(role); } /** * 根据用户ID查询权限 * * @param userId 用户ID * @return 权限列表 */ @Override public Set selectRoleKeys(Long userId) { List perms = roleMapper.selectRolesByUserId(userId); Set permsSet = new HashSet<>(); for (SysRole perm : perms) { if (StringUtils.isNotNull(perm)) { permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); } } return permsSet; } /** * 根据用户ID查询角色 * * @param userId 用户ID * @return 角色列表 */ @Override public List selectRolesByUserId(Long userId) { List userRoles = roleMapper.selectRolesByUserId(userId); List roles = selectRoleAll(); for (SysRole role : roles) { for (SysRole userRole : userRoles) { if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) { role.setFlag(true); break; } } } return roles; } /** * 查询所有角色 * * @return 角色列表 */ @Override public List selectRoleAll() { return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); } /** * 通过角色ID查询角色 * * @param roleId 角色ID * @return 角色对象信息 */ @Override public SysRole selectRoleById(Long roleId) { return roleMapper.selectRoleById(roleId); } /** * 通过角色ID删除角色 * * @param roleId 角色ID * @return 结果 */ @Override @Transactional public boolean deleteRoleById(Long roleId) { // 删除角色与菜单关联 roleMenuMapper.deleteRoleMenuByRoleId(roleId); // 删除角色与部门关联 roleDeptMapper.deleteRoleDeptByRoleId(roleId); return roleMapper.deleteRoleById(roleId) > 0 ? true : false; } /** * 批量删除角色信息 * * @param ids 需要删除的数据ID * @throws Exception */ @Override @Transactional public int deleteRoleByIds(String ids) { Long[] roleIds = Convert.toLongArray(ids); for (Long roleId : roleIds) { checkRoleAllowed(new SysRole(roleId)); checkRoleDataScope(roleId); SysRole role = selectRoleById(roleId); if (countUserRoleByRoleId(roleId) > 0) { throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); } } // 删除角色与菜单关联 roleMenuMapper.deleteRoleMenu(roleIds); // 删除角色与部门关联 roleDeptMapper.deleteRoleDept(roleIds); return roleMapper.deleteRoleByIds(roleIds); } /** * 新增保存角色信息 * * @param role 角色信息 * @return 结果 */ @Override @Transactional public int insertRole(SysRole role) { // 新增角色信息 roleMapper.insertRole(role); return insertRoleMenu(role); } /** * 修改保存角色信息 * * @param role 角色信息 * @return 结果 */ @Override @Transactional public int updateRole(SysRole role) { // 修改角色信息 roleMapper.updateRole(role); // 删除角色与菜单关联 roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId()); return insertRoleMenu(role); } /** * 修改数据权限信息 * * @param role 角色信息 * @return 结果 */ @Override @Transactional public int authDataScope(SysRole role) { // 修改角色信息 roleMapper.updateRole(role); // 删除角色与部门关联 roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId()); // 新增角色和部门信息(数据权限) return insertRoleDept(role); } /** * 新增角色菜单信息 * * @param role 角色对象 */ public int insertRoleMenu(SysRole role) { int rows = 1; // 新增用户与角色管理 List list = new ArrayList(); for (Long menuId : role.getMenuIds()) { SysRoleMenu rm = new SysRoleMenu(); rm.setRoleId(role.getRoleId()); rm.setMenuId(menuId); list.add(rm); } if (list.size() > 0) { rows = roleMenuMapper.batchRoleMenu(list); } return rows; } /** * 新增角色部门信息(数据权限) * * @param role 角色对象 */ public int insertRoleDept(SysRole role) { int rows = 1; // 新增角色与部门(数据权限)管理 List list = new ArrayList(); for (Long deptId : role.getDeptIds()) { SysRoleDept rd = new SysRoleDept(); rd.setRoleId(role.getRoleId()); rd.setDeptId(deptId); list.add(rd); } if (list.size() > 0) { rows = roleDeptMapper.batchRoleDept(list); } return rows; } /** * 校验角色名称是否唯一 * * @param role 角色信息 * @return 结果 */ @Override public boolean checkRoleNameUnique(SysRole role) { Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); SysRole info = roleMapper.checkRoleNameUnique(role.getRoleName()); if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 校验角色权限是否唯一 * * @param role 角色信息 * @return 结果 */ @Override public boolean checkRoleKeyUnique(SysRole role) { Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); SysRole info = roleMapper.checkRoleKeyUnique(role.getRoleKey()); if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 校验角色是否允许操作 * * @param role 角色信息 */ @Override public void checkRoleAllowed(SysRole role) { if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) { throw new ServiceException("不允许操作超级管理员角色"); } } /** * 校验角色是否有数据权限 * * @param roleIds 角色id */ @Override public void checkRoleDataScope(Long... roleIds) { if (!ShiroUtils.isAdmin()) { for (Long roleId : roleIds) { SysRole role = new SysRole(); role.setRoleId(roleId); List roles = SpringUtils.getAopProxy(this).selectRoleList(role); if (StringUtils.isEmpty(roles)) { throw new ServiceException("没有权限访问角色数据!"); } } } } /** * 通过角色ID查询角色使用数量 * * @param roleId 角色ID * @return 结果 */ @Override public int countUserRoleByRoleId(Long roleId) { return userRoleMapper.countUserRoleByRoleId(roleId); } /** * 角色状态修改 * * @param role 角色信息 * @return 结果 */ @Override public int changeStatus(SysRole role) { return roleMapper.updateRole(role); } /** * 取消授权用户角色 * * @param userRole 用户和角色关联信息 * @return 结果 */ @Override public int deleteAuthUser(SysUserRole userRole) { return userRoleMapper.deleteUserRoleInfo(userRole); } /** * 批量取消授权用户角色 * * @param roleId 角色ID * @param userIds 需要删除的用户数据ID * @return 结果 */ @Override public int deleteAuthUsers(Long roleId, String userIds) { return userRoleMapper.deleteUserRoleInfos(roleId, Convert.toLongArray(userIds)); } /** * 批量选择授权用户角色 * * @param roleId 角色ID * @param userIds 需要授权的用户数据ID * @return 结果 */ @Override public int insertAuthUsers(Long roleId, String userIds) { Long[] users = Convert.toLongArray(userIds); // 新增用户与角色管理 List list = new ArrayList(); for (Long userId : users) { SysUserRole ur = new SysUserRole(); ur.setUserId(userId); ur.setRoleId(roleId); list.add(ur); } return userRoleMapper.batchUserRole(list); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.io.Serializable; import java.util.Date; import java.util.Deque; import java.util.List; import com.ruoyi.common.utils.spring.SpringUtils; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.common.constant.ShiroConstants; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.domain.SysUserOnline; import com.ruoyi.system.mapper.SysUserOnlineMapper; import com.ruoyi.system.service.ISysUserOnlineService; /** * 在线用户 服务层处理 * * @author ruoyi */ @Service public class SysUserOnlineServiceImpl implements ISysUserOnlineService { @Autowired private SysUserOnlineMapper userOnlineDao; /** * 通过会话序号查询信息 * * @param sessionId 会话ID * @return 在线用户信息 */ @Override public SysUserOnline selectOnlineById(String sessionId) { return userOnlineDao.selectOnlineById(sessionId); } /** * 通过会话序号删除信息 * * @param sessionId 会话ID * @return 在线用户信息 */ @Override public void deleteOnlineById(String sessionId) { SysUserOnline userOnline = selectOnlineById(sessionId); if (StringUtils.isNotNull(userOnline)) { userOnlineDao.deleteOnlineById(sessionId); } } /** * 通过会话序号删除信息 * * @param sessions 会话ID集合 * @return 在线用户信息 */ @Override public void batchDeleteOnline(List sessions) { for (String sessionId : sessions) { SysUserOnline userOnline = selectOnlineById(sessionId); if (StringUtils.isNotNull(userOnline)) { userOnlineDao.deleteOnlineById(sessionId); } } } /** * 保存会话信息 * * @param online 会话信息 */ @Override public void saveOnline(SysUserOnline online) { userOnlineDao.saveOnline(online); } /** * 查询会话集合 * * @param userOnline 在线用户 */ @Override public List selectUserOnlineList(SysUserOnline userOnline) { return userOnlineDao.selectUserOnlineList(userOnline); } /** * 强退用户 * * @param sessionId 会话ID */ @Override public void forceLogout(String sessionId) { userOnlineDao.deleteOnlineById(sessionId); } /** * 清理用户缓存 * * @param loginName 登录名称 * @param sessionId 会话ID */ @Override public void removeUserCache(String loginName, String sessionId) { EhCacheManager ehCacheManager = SpringUtils.getBean(EhCacheManager.class); Cache> cache = ehCacheManager.getCache(ShiroConstants.SYS_USERCACHE); Deque deque = cache.get(loginName); if (StringUtils.isEmpty(deque) || deque.size() == 0) { return; } deque.remove(sessionId); } /** * 查询会话集合 * * @param expiredDate 失效日期 */ @Override public List selectOnlineByExpired(Date expiredDate) { String lastAccessTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, expiredDate); return userOnlineDao.selectOnlineByExpired(lastAccessTime); } } ================================================ FILE: ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java ================================================ package com.ruoyi.system.service.impl; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.stream.Collectors; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.entity.SysRole; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.ExceptionUtil; import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.bean.BeanValidators; import com.ruoyi.common.utils.html.EscapeUtil; import com.ruoyi.common.utils.security.Md5Utils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.system.domain.SysPost; import com.ruoyi.system.domain.SysUserPost; import com.ruoyi.system.domain.SysUserRole; import com.ruoyi.system.mapper.SysPostMapper; import com.ruoyi.system.mapper.SysRoleMapper; import com.ruoyi.system.mapper.SysUserMapper; import com.ruoyi.system.mapper.SysUserPostMapper; import com.ruoyi.system.mapper.SysUserRoleMapper; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.service.ISysUserService; /** * 用户 业务层处理 * * @author ruoyi */ @Service public class SysUserServiceImpl implements ISysUserService { private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class); @Autowired private SysUserMapper userMapper; @Autowired private SysRoleMapper roleMapper; @Autowired private SysPostMapper postMapper; @Autowired private SysUserPostMapper userPostMapper; @Autowired private SysUserRoleMapper userRoleMapper; @Autowired private ISysConfigService configService; @Autowired private ISysDeptService deptService; @Autowired protected Validator validator; /** * 根据条件分页查询用户列表 * * @param user 用户信息 * @return 用户信息集合信息 */ @Override @DataScope(deptAlias = "d", userAlias = "u") public List selectUserList(SysUser user) { return userMapper.selectUserList(user); } /** * 根据条件分页查询已分配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ @Override @DataScope(deptAlias = "d", userAlias = "u") public List selectAllocatedList(SysUser user) { return userMapper.selectAllocatedList(user); } /** * 根据条件分页查询未分配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ @Override @DataScope(deptAlias = "d", userAlias = "u") public List selectUnallocatedList(SysUser user) { return userMapper.selectUnallocatedList(user); } /** * 通过用户名查询用户 * * @param userName 用户名 * @return 用户对象信息 */ @Override public SysUser selectUserByLoginName(String userName) { return userMapper.selectUserByLoginName(userName); } /** * 通过手机号码查询用户 * * @param phoneNumber 手机号码 * @return 用户对象信息 */ @Override public SysUser selectUserByPhoneNumber(String phoneNumber) { return userMapper.selectUserByPhoneNumber(phoneNumber); } /** * 通过邮箱查询用户 * * @param email 邮箱 * @return 用户对象信息 */ @Override public SysUser selectUserByEmail(String email) { return userMapper.selectUserByEmail(email); } /** * 通过用户ID查询用户 * * @param userId 用户ID * @return 用户对象信息 */ @Override public SysUser selectUserById(Long userId) { return userMapper.selectUserById(userId); } /** * 通过用户ID查询用户和角色关联 * * @param userId 用户ID * @return 用户和角色关联列表 */ @Override public List selectUserRoleByUserId(Long userId) { return userRoleMapper.selectUserRoleByUserId(userId); } /** * 通过用户ID删除用户 * * @param userId 用户ID * @return 结果 */ @Override @Transactional public int deleteUserById(Long userId) { // 删除用户与角色关联 userRoleMapper.deleteUserRoleByUserId(userId); // 删除用户与岗位表 userPostMapper.deleteUserPostByUserId(userId); return userMapper.deleteUserById(userId); } /** * 批量删除用户信息 * * @param ids 需要删除的数据ID * @return 结果 */ @Override @Transactional public int deleteUserByIds(String ids) { Long[] userIds = Convert.toLongArray(ids); for (Long userId : userIds) { checkUserAllowed(new SysUser(userId)); checkUserDataScope(userId); } // 删除用户与角色关联 userRoleMapper.deleteUserRole(userIds); // 删除用户与岗位关联 userPostMapper.deleteUserPost(userIds); return userMapper.deleteUserByIds(userIds); } /** * 新增保存用户信息 * * @param user 用户信息 * @return 结果 */ @Override @Transactional public int insertUser(SysUser user) { // 新增用户信息 int rows = userMapper.insertUser(user); // 新增用户岗位关联 insertUserPost(user); // 新增用户与角色管理 insertUserRole(user.getUserId(), user.getRoleIds()); return rows; } /** * 注册用户信息 * * @param user 用户信息 * @return 结果 */ @Override public boolean registerUser(SysUser user) { user.setUserType(UserConstants.REGISTER_USER_TYPE); return userMapper.insertUser(user) > 0; } /** * 修改保存用户信息 * * @param user 用户信息 * @return 结果 */ @Override @Transactional public int updateUser(SysUser user) { Long userId = user.getUserId(); // 删除用户与角色关联 userRoleMapper.deleteUserRoleByUserId(userId); // 新增用户与角色管理 insertUserRole(user.getUserId(), user.getRoleIds()); // 删除用户与岗位关联 userPostMapper.deleteUserPostByUserId(userId); // 新增用户与岗位管理 insertUserPost(user); return userMapper.updateUser(user); } /** * 修改用户个人详细信息 * * @param user 用户信息 * @return 结果 */ @Override public int updateUserInfo(SysUser user) { return userMapper.updateUser(user); } /** * 修改用户头像 * * @param userId 用户ID * @param avatar 头像地址 * @return 结果 */ public boolean updateUserAvatar(Long userId, String avatar) { return userMapper.updateUserAvatar(userId, avatar) > 0; } /** * 更新用户登录信息(IP和登录时间) * * @param userId 用户ID * @param loginIp 登录IP地址 * @param loginDate 登录时间 * @return 结果 */ public void updateLoginInfo(Long userId, String loginIp, Date loginDate) { userMapper.updateLoginInfo(userId, loginIp, loginDate); } /** * 用户授权角色 * * @param userId 用户ID * @param roleIds 角色组 */ @Override @Transactional public void insertUserAuth(Long userId, Long[] roleIds) { userRoleMapper.deleteUserRoleByUserId(userId); insertUserRole(userId, roleIds); } /** * 修改用户密码 * * @param user 用户信息 * @return 结果 */ @Override public int resetUserPwd(SysUser user) { return userMapper.resetUserPwd(user.getUserId(), user.getPassword(), user.getSalt()); } /** * 新增用户角色信息 * * @param userId 用户ID * @param roleIds 角色组 */ public void insertUserRole(Long userId, Long[] roleIds) { if (StringUtils.isNotNull(roleIds)) { // 新增用户与角色管理 List list = new ArrayList(); for (Long roleId : roleIds) { SysUserRole ur = new SysUserRole(); ur.setUserId(userId); ur.setRoleId(roleId); list.add(ur); } if (list.size() > 0) { userRoleMapper.batchUserRole(list); } } } /** * 新增用户岗位信息 * * @param user 用户对象 */ public void insertUserPost(SysUser user) { Long[] posts = user.getPostIds(); if (StringUtils.isNotNull(posts)) { // 新增用户与岗位管理 List list = new ArrayList(); for (Long postId : posts) { SysUserPost up = new SysUserPost(); up.setUserId(user.getUserId()); up.setPostId(postId); list.add(up); } if (list.size() > 0) { userPostMapper.batchUserPost(list); } } } /** * 校验用户名称是否唯一 * * @param user 用户信息 * @return 结果 */ @Override public boolean checkLoginNameUnique(SysUser user) { Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); SysUser info = userMapper.checkLoginNameUnique(user.getLoginName()); if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 校验手机号码是否唯一 * * @param user 用户信息 * @return */ @Override public boolean checkPhoneUnique(SysUser user) { Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber()); if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 校验email是否唯一 * * @param user 用户信息 * @return */ @Override public boolean checkEmailUnique(SysUser user) { Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); SysUser info = userMapper.checkEmailUnique(user.getEmail()); if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { return UserConstants.NOT_UNIQUE; } return UserConstants.UNIQUE; } /** * 校验用户是否允许操作 * * @param user 用户信息 */ @Override public void checkUserAllowed(SysUser user) { if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) { throw new ServiceException("不允许操作超级管理员用户"); } } /** * 校验用户是否有数据权限 * * @param userId 用户id */ @Override public void checkUserDataScope(Long userId) { if (!ShiroUtils.isAdmin()) { SysUser user = new SysUser(); user.setUserId(userId); List users = SpringUtils.getAopProxy(this).selectUserList(user); if (StringUtils.isEmpty(users)) { throw new ServiceException("没有权限访问用户数据!"); } } } /** * 查询用户所属角色组 * * @param userId 用户ID * @return 结果 */ @Override public String selectUserRoleGroup(Long userId) { List list = roleMapper.selectRolesByUserId(userId); if (CollectionUtils.isEmpty(list)) { return StringUtils.EMPTY; } return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); } /** * 查询用户所属岗位组 * * @param userId 用户ID * @return 结果 */ @Override public String selectUserPostGroup(Long userId) { List list = postMapper.selectPostsByUserId(userId); if (CollectionUtils.isEmpty(list)) { return StringUtils.EMPTY; } return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); } /** * 导入用户数据 * * @param userList 用户数据列表 * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 * @param operName 操作用户 * @return 结果 */ @Override public String importUser(List userList, Boolean isUpdateSupport, String operName) { if (StringUtils.isNull(userList) || userList.size() == 0) { throw new ServiceException("导入用户数据不能为空!"); } int successNum = 0; int failureNum = 0; StringBuilder successMsg = new StringBuilder(); StringBuilder failureMsg = new StringBuilder(); for (SysUser user : userList) { try { // 验证是否存在这个用户 SysUser u = userMapper.selectUserByLoginName(user.getLoginName()); if (StringUtils.isNull(u)) { BeanValidators.validateWithException(validator, user); deptService.checkDeptDataScope(user.getDeptId()); String password = configService.selectConfigByKey("sys.user.initPassword"); user.setPassword(Md5Utils.hash(user.getLoginName() + password)); user.setCreateBy(operName); userMapper.insertUser(user); successNum++; successMsg.append("
                    " + successNum + "、账号 " + user.getLoginName() + " 导入成功"); } else if (isUpdateSupport) { BeanValidators.validateWithException(validator, user); checkUserAllowed(u); checkUserDataScope(u.getUserId()); deptService.checkDeptDataScope(user.getDeptId()); user.setUserId(u.getUserId()); user.setDeptId(u.getDeptId()); user.setUpdateBy(operName); userMapper.updateUser(user); successNum++; successMsg.append("
                    " + successNum + "、账号 " + user.getLoginName() + " 更新成功"); } else { failureNum++; failureMsg.append("
                    " + failureNum + "、账号 " + user.getLoginName() + " 已存在"); } } catch (Exception e) { failureNum++; String loginName = user.getLoginName(); if (ExceptionUtil.isCausedBy(e, ConstraintViolationException.class)) { loginName = EscapeUtil.clean(loginName); } String msg = "
                    " + failureNum + "、账号 " + loginName + " 导入失败:"; failureMsg.append(msg + e.getMessage()); log.error(msg, e); } } if (failureNum > 0) { failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); throw new ServiceException(failureMsg.toString()); } else { successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); } return successMsg.toString(); } /** * 用户状态修改 * * @param user 用户信息 * @return 结果 */ @Override public int changeStatus(SysUser user) { return userMapper.updateUserStatus(user.getUserId(), user.getStatus()); } } ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysConfigMapper.xml ================================================ select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark from sys_config and config_id = #{configId} and config_key = #{configKey} insert into sys_config ( config_name, config_key, config_value, config_type, create_by, remark, create_time )values( #{configName}, #{configKey}, #{configValue}, #{configType}, #{createBy}, #{remark}, sysdate() ) update sys_config config_name = #{configName}, config_key = #{configKey}, config_value = #{configValue}, config_type = #{configType}, update_by = #{updateBy}, remark = #{remark}, update_time = sysdate() where config_id = #{configId} delete from sys_config where config_id = #{configId} delete from sys_config where config_id in #{configId} ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml ================================================ select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time from sys_dept d insert into sys_dept( dept_id, parent_id, dept_name, ancestors, order_num, leader, phone, email, status, create_by, create_time )values( #{deptId}, #{parentId}, #{deptName}, #{ancestors}, #{orderNum}, #{leader}, #{phone}, #{email}, #{status}, #{createBy}, sysdate() ) update sys_dept parent_id = #{parentId}, dept_name = #{deptName}, ancestors = #{ancestors}, order_num = #{orderNum}, leader = #{leader}, phone = #{phone}, email = #{email}, status = #{status}, update_by = #{updateBy}, update_time = sysdate() where dept_id = #{deptId} update sys_dept set ancestors = when #{item.deptId} then #{item.ancestors} where dept_id in #{item.deptId} update sys_dept set del_flag = '2' where dept_id = #{deptId} update sys_dept set status = '0' where dept_id in #{deptId} update sys_dept set order_num = #{orderNum} where dept_id = #{deptId} ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysDictDataMapper.xml ================================================ select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark from sys_dict_data delete from sys_dict_data where dict_code = #{dictCode} delete from sys_dict_data where dict_code in #{dictCode} update sys_dict_data dict_sort = #{dictSort}, dict_label = #{dictLabel}, dict_value = #{dictValue}, dict_type = #{dictType}, css_class = #{cssClass}, list_class = #{listClass}, is_default = #{isDefault}, status = #{status}, remark = #{remark}, update_by = #{updateBy}, update_time = sysdate() where dict_code = #{dictCode} update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} insert into sys_dict_data( dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, remark, create_by, create_time )values( #{dictSort}, #{dictLabel}, #{dictValue}, #{dictType}, #{cssClass}, #{listClass}, #{isDefault}, #{status}, #{remark}, #{createBy}, sysdate() ) ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysDictTypeMapper.xml ================================================ select dict_id, dict_name, dict_type, status, create_by, create_time, remark from sys_dict_type delete from sys_dict_type where dict_id = #{dictId} delete from sys_dict_type where dict_id in #{dictId} update sys_dict_type dict_name = #{dictName}, dict_type = #{dictType}, status = #{status}, remark = #{remark}, update_by = #{updateBy}, update_time = sysdate() where dict_id = #{dictId} insert into sys_dict_type( dict_name, dict_type, status, remark, create_by, create_time )values( #{dictName}, #{dictType}, #{status}, #{remark}, #{createBy}, sysdate() ) ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysLogininforMapper.xml ================================================ insert into sys_logininfor (login_name, status, ipaddr, login_location, browser, os, msg, login_time) values (#{loginName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate()) delete from sys_logininfor where info_id in #{infoId} truncate table sys_logininfor ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml ================================================ select menu_id, menu_name, parent_id, order_num, url, target, menu_type, visible, is_refresh, ifnull(perms,'') as perms, icon, create_by, create_time from sys_menu delete from sys_menu where menu_id = #{menuId} or parent_id = #{menuId} update sys_menu menu_name = #{menuName}, parent_id = #{parentId}, order_num = #{orderNum}, url = #{url}, target = #{target}, menu_type = #{menuType}, visible = #{visible}, is_refresh = #{isRefresh}, perms = #{perms}, icon = #{icon}, remark = #{remark}, update_by = #{updateBy}, update_time = sysdate() where menu_id = #{menuId} insert into sys_menu( menu_id, parent_id, menu_name, order_num, url, target, menu_type, visible, is_refresh, perms, icon, remark, create_by, create_time )values( #{menuId}, #{parentId}, #{menuName}, #{orderNum}, #{url}, #{target}, #{menuType}, #{visible}, #{isRefresh}, #{perms}, #{icon}, #{remark}, #{createBy}, sysdate() ) update sys_menu set order_num = #{orderNum} where menu_id = #{menuId} ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysNoticeMapper.xml ================================================ select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark from sys_notice insert into sys_notice ( notice_title, notice_type, notice_content, status, remark, create_by, create_time )values( #{noticeTitle}, #{noticeType}, #{noticeContent}, #{status}, #{remark}, #{createBy}, sysdate() ) update sys_notice notice_title = #{noticeTitle}, notice_type = #{noticeType}, notice_content = #{noticeContent}, status = #{status}, update_by = #{updateBy}, update_time = sysdate() where notice_id = #{noticeId} delete from sys_notice where notice_id in #{noticeId} ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml ================================================ insert ignore into sys_notice_read (notice_id, user_id, read_time) values (#{noticeId}, #{userId}, sysdate()) insert ignore into sys_notice_read (notice_id, user_id, read_time) values (#{noticeId}, #{userId}, sysdate()) delete from sys_notice_read where notice_id in #{noticeId} ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml ================================================ select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time from sys_oper_log insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time) values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate()) delete from sys_oper_log where oper_id in #{operId} truncate table sys_oper_log ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysPostMapper.xml ================================================ select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark from sys_post delete from sys_post where post_id in #{postId} update sys_post post_code = #{postCode}, post_name = #{postName}, post_sort = #{postSort}, status = #{status}, remark = #{remark}, update_by = #{updateBy}, update_time = sysdate() where post_id = #{postId} insert into sys_post( post_id, post_code, post_name, post_sort, status, remark, create_by, create_time )values( #{postId}, #{postCode}, #{postName}, #{postSort}, #{status}, #{remark}, #{createBy}, sysdate() ) ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml ================================================ delete from sys_role_dept where role_id=#{roleId} delete from sys_role_dept where role_id in #{roleId} insert into sys_role_dept(role_id, dept_id) values (#{item.roleId},#{item.deptId}) ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysRoleMapper.xml ================================================ select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status, r.del_flag, r.create_time, r.remark from sys_role r left join sys_user_role ur on ur.role_id = r.role_id left join sys_user u on u.user_id = ur.user_id left join sys_dept d on u.dept_id = d.dept_id select r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status, r.del_flag, r.create_time, r.create_by, r.update_by, r.update_time, r.remark from sys_role r update sys_role set del_flag = '2' where role_id = #{roleId} update sys_role set del_flag = '2' where role_id in #{roleId} update sys_role role_name = #{roleName}, role_key = #{roleKey}, role_sort = #{roleSort}, data_scope = #{dataScope}, status = #{status}, remark = #{remark}, update_by = #{updateBy}, update_time = sysdate() where role_id = #{roleId} insert into sys_role( role_id, role_name, role_key, role_sort, data_scope, status, remark, create_by, create_time )values( #{roleId}, #{roleName}, #{roleKey}, #{roleSort}, #{dataScope}, #{status}, #{remark}, #{createBy}, sysdate() ) ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml ================================================ delete from sys_role_menu where role_id=#{roleId} delete from sys_role_menu where role_id in #{roleId} insert into sys_role_menu(role_id, menu_id) values (#{item.roleId},#{item.menuId}) ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml ================================================ select u.user_id, u.dept_id, u.login_name, u.user_name, u.user_type, u.email, u.avatar, u.phonenumber, u.sex, u.password, u.salt, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.update_by, u.update_time, u.remark, d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status from sys_user u left join sys_dept d on u.dept_id = d.dept_id left join sys_user_role ur on u.user_id = ur.user_id left join sys_role r on r.role_id = ur.role_id update sys_user set del_flag = '2' where user_id = #{userId} update sys_user set del_flag = '2' where user_id in #{userId} update sys_user set avatar = #{avatar} where user_id = #{userId} update sys_user SET pwd_update_date = sysdate(), password = #{password}, salt = #{salt}, update_time = sysdate() where user_id = #{userId} update sys_user SET status = #{status}, update_time = sysdate() where user_id = #{userId} update sys_user set login_ip = #{loginIp}, login_date = #{loginDate} where user_id = #{userId} update sys_user dept_id = #{deptId}, user_name = #{userName}, user_type = #{userType}, email = #{email}, phonenumber = #{phonenumber}, sex = #{sex}, avatar = #{avatar}, password = #{password}, salt = #{salt}, status = #{status}, login_ip = #{loginIp}, login_date = #{loginDate}, pwd_update_date = #{pwdUpdateDate}, update_by = #{updateBy}, remark = #{remark}, update_time = sysdate() where user_id = #{userId} insert into sys_user( user_id, dept_id, login_name, user_name, user_type, email, avatar, phonenumber, sex, password, salt, status, pwd_update_date, create_by, remark, create_time )values( #{userId}, #{deptId}, #{loginName}, #{userName}, #{userType}, #{email}, #{avatar}, #{phonenumber}, #{sex}, #{password}, #{salt}, #{status}, #{pwdUpdateDate}, #{createBy}, #{remark}, sysdate() ) ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml ================================================ select sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time, session_data from sys_user_online replace into sys_user_online(sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time, session_data) values (#{sessionId}, #{loginName}, #{deptName}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{status}, #{startTimestamp}, #{lastAccessTime}, #{expireTime}, #{sessionData,jdbcType=BLOB,typeHandler=org.apache.ibatis.type.BlobTypeHandler}) delete from sys_user_online where sessionId = #{sessionId} ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysUserPostMapper.xml ================================================ delete from sys_user_post where user_id=#{userId} delete from sys_user_post where user_id in #{userId} insert into sys_user_post(user_id, post_id) values (#{item.userId},#{item.postId}) ================================================ FILE: ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml ================================================ delete from sys_user_role where user_id = #{userId} delete from sys_user_role where user_id in #{userId} insert into sys_user_role(user_id, role_id) values (#{item.userId},#{item.roleId}) delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} delete from sys_user_role where role_id=#{roleId} and user_id in #{userId} ================================================ FILE: ry.bat ================================================ @echo off rem jarƽĿ¼ set AppName=ruoyi-admin.jar rem JVM set JVM_OPTS="-Dname=%AppName% -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" ECHO. ECHO. [1] %AppName% ECHO. [2] ر%AppName% ECHO. [3] %AppName% ECHO. [4] ״̬ %AppName% ECHO. [5] ECHO. ECHO.ѡĿ: set /p ID= IF "%id%"=="1" GOTO start IF "%id%"=="2" GOTO stop IF "%id%"=="3" GOTO restart IF "%id%"=="4" GOTO status IF "%id%"=="5" EXIT PAUSE :start for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( set pid=%%a set image_name=%%b ) if defined pid ( echo %%is running PAUSE ) start javaw %JVM_OPTS% -jar %AppName% echo starting echo Start %AppName% success... goto:eof rem stopͨjpspid :stop for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( set pid=%%a set image_name=%%b ) if not defined pid (echo process %AppName% does not exists) else ( echo prepare to kill %image_name% echo start kill %pid% ... rem ݽIDkill taskkill /f /pid %pid% ) goto:eof :restart call :stop call :start goto:eof :status for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( set pid=%%a set image_name=%%b ) if not defined pid (echo process %AppName% is dead ) else ( echo %image_name% is running ) goto:eof ================================================ FILE: ry.sh ================================================ #!/bin/sh # ./ry.sh start 启动 stop 停止 restart 重启 status 状态 AppName=ruoyi-admin.jar # JVM参数 JVM_OPTS="-Dname=$AppName -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" APP_HOME=`pwd` LOG_PATH=$APP_HOME/logs/$AppName.log if [ "$1" = "" ]; then echo -e "\033[0;31m 未输入操作名 \033[0m \033[0;34m {start|stop|restart|status} \033[0m" exit 1 fi if [ "$AppName" = "" ]; then echo -e "\033[0;31m 未输入应用名 \033[0m" exit 1 fi function start() { PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` if [ x"$PID" != x"" ]; then echo "$AppName is running..." else nohup java $JVM_OPTS -jar $AppName > /dev/null 2>&1 & echo "Start $AppName success..." fi } function stop() { echo "Stop $AppName" PID="" query(){ PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` } query if [ x"$PID" != x"" ]; then kill -TERM $PID echo "$AppName (pid:$PID) exiting..." while [ x"$PID" != x"" ] do sleep 1 query done echo "$AppName exited." else echo "$AppName already stopped." fi } function restart() { stop sleep 2 start } function status() { PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l` if [ $PID != 0 ];then echo "$AppName is running..." else echo "$AppName is not running..." fi } case $1 in start) start;; stop) stop;; restart) restart;; status) status;; *) esac ================================================ FILE: sql/quartz.sql ================================================ DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; -- ---------------------------- -- 1、存储每一个已配置的 jobDetail 的详细信息 -- ---------------------------- create table QRTZ_JOB_DETAILS ( sched_name varchar(120) not null comment '调度名称', job_name varchar(200) not null comment '任务名称', job_group varchar(200) not null comment '任务组名', description varchar(250) null comment '相关介绍', job_class_name varchar(250) not null comment '执行任务类名称', is_durable varchar(1) not null comment '是否持久化', is_nonconcurrent varchar(1) not null comment '是否并发', is_update_data varchar(1) not null comment '是否更新数据', requests_recovery varchar(1) not null comment '是否接受恢复执行', job_data blob null comment '存放持久化job对象', primary key (sched_name, job_name, job_group) ) engine=innodb comment = '任务详细信息表'; -- ---------------------------- -- 2、 存储已配置的 Trigger 的信息 -- ---------------------------- create table QRTZ_TRIGGERS ( sched_name varchar(120) not null comment '调度名称', trigger_name varchar(200) not null comment '触发器的名字', trigger_group varchar(200) not null comment '触发器所属组的名字', job_name varchar(200) not null comment 'qrtz_job_details表job_name的外键', job_group varchar(200) not null comment 'qrtz_job_details表job_group的外键', description varchar(250) null comment '相关介绍', next_fire_time bigint(13) null comment '上一次触发时间(毫秒)', prev_fire_time bigint(13) null comment '下一次触发时间(默认为-1表示不触发)', priority integer null comment '优先级', trigger_state varchar(16) not null comment '触发器状态', trigger_type varchar(8) not null comment '触发器的类型', start_time bigint(13) not null comment '开始时间', end_time bigint(13) null comment '结束时间', calendar_name varchar(200) null comment '日程表名称', misfire_instr smallint(2) null comment '补偿执行的策略', job_data blob null comment '存放持久化job对象', primary key (sched_name, trigger_name, trigger_group), foreign key (sched_name, job_name, job_group) references QRTZ_JOB_DETAILS(sched_name, job_name, job_group) ) engine=innodb comment = '触发器详细信息表'; -- ---------------------------- -- 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数 -- ---------------------------- create table QRTZ_SIMPLE_TRIGGERS ( sched_name varchar(120) not null comment '调度名称', trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', repeat_count bigint(7) not null comment '重复的次数统计', repeat_interval bigint(12) not null comment '重复的间隔时间', times_triggered bigint(10) not null comment '已经触发的次数', primary key (sched_name, trigger_name, trigger_group), foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) ) engine=innodb comment = '简单触发器的信息表'; -- ---------------------------- -- 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息 -- ---------------------------- create table QRTZ_CRON_TRIGGERS ( sched_name varchar(120) not null comment '调度名称', trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', cron_expression varchar(200) not null comment 'cron表达式', time_zone_id varchar(80) comment '时区', primary key (sched_name, trigger_name, trigger_group), foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) ) engine=innodb comment = 'Cron类型的触发器表'; -- ---------------------------- -- 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) -- ---------------------------- create table QRTZ_BLOB_TRIGGERS ( sched_name varchar(120) not null comment '调度名称', trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', blob_data blob null comment '存放持久化Trigger对象', primary key (sched_name, trigger_name, trigger_group), foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) ) engine=innodb comment = 'Blob类型的触发器表'; -- ---------------------------- -- 6、 以 Blob 类型存储存放日历信息, quartz可配置一个日历来指定一个时间范围 -- ---------------------------- create table QRTZ_CALENDARS ( sched_name varchar(120) not null comment '调度名称', calendar_name varchar(200) not null comment '日历名称', calendar blob not null comment '存放持久化calendar对象', primary key (sched_name, calendar_name) ) engine=innodb comment = '日历信息表'; -- ---------------------------- -- 7、 存储已暂停的 Trigger 组的信息 -- ---------------------------- create table QRTZ_PAUSED_TRIGGER_GRPS ( sched_name varchar(120) not null comment '调度名称', trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', primary key (sched_name, trigger_group) ) engine=innodb comment = '暂停的触发器表'; -- ---------------------------- -- 8、 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 -- ---------------------------- create table QRTZ_FIRED_TRIGGERS ( sched_name varchar(120) not null comment '调度名称', entry_id varchar(95) not null comment '调度器实例id', trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', instance_name varchar(200) not null comment '调度器实例名', fired_time bigint(13) not null comment '触发的时间', sched_time bigint(13) not null comment '定时器制定的时间', priority integer not null comment '优先级', state varchar(16) not null comment '状态', job_name varchar(200) null comment '任务名称', job_group varchar(200) null comment '任务组名', is_nonconcurrent varchar(1) null comment '是否并发', requests_recovery varchar(1) null comment '是否接受恢复执行', primary key (sched_name, entry_id) ) engine=innodb comment = '已触发的触发器表'; -- ---------------------------- -- 9、 存储少量的有关 Scheduler 的状态信息,假如是用于集群中,可以看到其他的 Scheduler 实例 -- ---------------------------- create table QRTZ_SCHEDULER_STATE ( sched_name varchar(120) not null comment '调度名称', instance_name varchar(200) not null comment '实例名称', last_checkin_time bigint(13) not null comment '上次检查时间', checkin_interval bigint(13) not null comment '检查间隔时间', primary key (sched_name, instance_name) ) engine=innodb comment = '调度器状态表'; -- ---------------------------- -- 10、 存储程序的悲观锁的信息(假如使用了悲观锁) -- ---------------------------- create table QRTZ_LOCKS ( sched_name varchar(120) not null comment '调度名称', lock_name varchar(40) not null comment '悲观锁名称', primary key (sched_name, lock_name) ) engine=innodb comment = '存储的悲观锁信息表'; -- ---------------------------- -- 11、 Quartz集群实现同步机制的行锁表 -- ---------------------------- create table QRTZ_SIMPROP_TRIGGERS ( sched_name varchar(120) not null comment '调度名称', trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', str_prop_1 varchar(512) null comment 'String类型的trigger的第一个参数', str_prop_2 varchar(512) null comment 'String类型的trigger的第二个参数', str_prop_3 varchar(512) null comment 'String类型的trigger的第三个参数', int_prop_1 int null comment 'int类型的trigger的第一个参数', int_prop_2 int null comment 'int类型的trigger的第二个参数', long_prop_1 bigint null comment 'long类型的trigger的第一个参数', long_prop_2 bigint null comment 'long类型的trigger的第二个参数', dec_prop_1 numeric(13,4) null comment 'decimal类型的trigger的第一个参数', dec_prop_2 numeric(13,4) null comment 'decimal类型的trigger的第二个参数', bool_prop_1 varchar(1) null comment 'Boolean类型的trigger的第一个参数', bool_prop_2 varchar(1) null comment 'Boolean类型的trigger的第二个参数', primary key (sched_name, trigger_name, trigger_group), foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) ) engine=innodb comment = '同步机制的行锁表'; commit; ================================================ FILE: sql/ruoyi.html ================================================ RuoYi
                    1 RuoYi Move the mouse over tables & columns to read the comments. Fk qrtz_blob_triggers_ibfk_1 qrtz_blob_triggers ref qrtz_triggers ( sched_name, trigger_name, trigger_group ) sched_name,trigger_name,trigger_group Fk qrtz_cron_triggers_ibfk_1 qrtz_cron_triggers ref qrtz_triggers ( sched_name, trigger_name, trigger_group ) sched_name,trigger_name,trigger_group Fk qrtz_simple_triggers_ibfk_1 qrtz_simple_triggers ref qrtz_triggers ( sched_name, trigger_name, trigger_group ) sched_name,trigger_name,trigger_group Fk qrtz_simprop_triggers_ibfk_1 qrtz_simprop_triggers ref qrtz_triggers ( sched_name, trigger_name, trigger_group ) sched_name,trigger_name,trigger_group Fk qrtz_triggers_ibfk_1 qrtz_triggers ref qrtz_job_details ( sched_name, job_name, job_group ) sched_name,job_name,job_group qrtz_blob_triggersTable ry.qrtz_blob_triggers Pk pk_qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) sched_namesched_name * varchar(120) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) trigger_nametrigger_name * varchar(200) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) trigger_grouptrigger_group * varchar(200) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) blob_datablob_data blob ~ qrtz_calendarsTable ry.qrtz_calendars Pk pk_qrtz_calendars ( sched_name, calendar_name ) sched_namesched_name * varchar(120) t Pk pk_qrtz_calendars ( sched_name, calendar_name ) calendar_namecalendar_name * varchar(200) t calendarcalendar * blob ~ qrtz_cron_triggersTable ry.qrtz_cron_triggers Pk pk_qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) sched_namesched_name * varchar(120) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) trigger_nametrigger_name * varchar(200) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) trigger_grouptrigger_group * varchar(200) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) cron_expressioncron_expression * varchar(200) t time_zone_idtime_zone_id varchar(80) t qrtz_job_detailsTable ry.qrtz_job_details Pk pk_qrtz_job_details ( sched_name, job_name, job_group ) sched_namesched_name * varchar(120) Referred by qrtz_triggers ( sched_name, job_name, job_group ) Pk pk_qrtz_job_details ( sched_name, job_name, job_group ) job_namejob_name * varchar(200) Referred by qrtz_triggers ( sched_name, job_name, job_group ) Pk pk_qrtz_job_details ( sched_name, job_name, job_group ) job_groupjob_group * varchar(200) Referred by qrtz_triggers ( sched_name, job_name, job_group ) descriptiondescription varchar(250) t job_class_namejob_class_name * varchar(250) t is_durableis_durable * varchar(1) t is_nonconcurrentis_nonconcurrent * varchar(1) t is_update_datais_update_data * varchar(1) t requests_recoveryrequests_recovery * varchar(1) t job_datajob_data blob ~ qrtz_locksTable ry.qrtz_locks Pk pk_qrtz_locks ( sched_name, lock_name ) sched_namesched_name * varchar(120) t Pk pk_qrtz_locks ( sched_name, lock_name ) lock_namelock_name * varchar(40) t qrtz_scheduler_stateTable ry.qrtz_scheduler_state Pk pk_qrtz_scheduler_state ( sched_name, instance_name ) sched_namesched_name * varchar(120) t Pk pk_qrtz_scheduler_state ( sched_name, instance_name ) instance_nameinstance_name * varchar(200) t last_checkin_timelast_checkin_time * bigint # checkin_intervalcheckin_interval * bigint # qrtz_simple_triggersTable ry.qrtz_simple_triggers Pk pk_qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) sched_namesched_name * varchar(120) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) trigger_nametrigger_name * varchar(200) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) trigger_grouptrigger_group * varchar(200) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) repeat_countrepeat_count * bigint # repeat_intervalrepeat_interval * bigint # times_triggeredtimes_triggered * bigint # qrtz_simprop_triggersTable ry.qrtz_simprop_triggers Pk pk_qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) sched_namesched_name * varchar(120) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) trigger_nametrigger_name * varchar(200) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) trigger_grouptrigger_group * varchar(200) References qrtz_triggers ( sched_name, trigger_name, trigger_group ) str_prop_1str_prop_1 varchar(512) t str_prop_2str_prop_2 varchar(512) t str_prop_3str_prop_3 varchar(512) t int_prop_1int_prop_1 int # int_prop_2int_prop_2 int # long_prop_1long_prop_1 bigint # long_prop_2long_prop_2 bigint # dec_prop_1dec_prop_1 decimal(13,4) # dec_prop_2dec_prop_2 decimal(13,4) # bool_prop_1bool_prop_1 varchar(1) t bool_prop_2bool_prop_2 varchar(1) t qrtz_triggersTable ry.qrtz_triggers Pk pk_qrtz_triggers ( sched_name, trigger_name, trigger_group ) sched_name ( sched_name, job_name, job_group ) sched_namesched_name * varchar(120) References qrtz_job_details ( sched_name, job_name, job_group ) Referred by qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_triggers ( sched_name, trigger_name, trigger_group ) trigger_nametrigger_name * varchar(200) Referred by qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) Pk pk_qrtz_triggers ( sched_name, trigger_name, trigger_group ) trigger_grouptrigger_group * varchar(200) Referred by qrtz_blob_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_cron_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_simple_triggers ( sched_name, trigger_name, trigger_group ) Referred by qrtz_simprop_triggers ( sched_name, trigger_name, trigger_group ) sched_name ( sched_name, job_name, job_group ) job_namejob_name * varchar(200) References qrtz_job_details ( sched_name, job_name, job_group ) sched_name ( sched_name, job_name, job_group ) job_groupjob_group * varchar(200) References qrtz_job_details ( sched_name, job_name, job_group ) descriptiondescription varchar(250) t next_fire_timenext_fire_time bigint # prev_fire_timeprev_fire_time bigint # prioritypriority int # trigger_statetrigger_state * varchar(16) t trigger_typetrigger_type * varchar(8) t start_timestart_time * bigint # end_timeend_time bigint # calendar_namecalendar_name varchar(200) t misfire_instrmisfire_instr smallint # job_datajob_data blob ~ sys_dict_dataTable ry.sys_dict_data Pk pk_sys_dict_data ( dict_code ) dict_codedict_code * int 字典编码 # dict_sortdict_sort int default 0 字典排序 # dict_labeldict_label varchar(100) default '' 字典标签 t dict_valuedict_value varchar(100) default '' 字典键值 t dict_typedict_type varchar(100) default '' 字典类型 t statusstatus int default 0 状态(0正常 1禁用) # create_bycreate_by varchar(64) default '' 创建者 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d update_byupdate_by varchar(64) default '' 更新者 t update_timeupdate_time * timestamp default '0000-00-00 00:00:00' 更新时间 d remarkremark varchar(500) default '' 备注 t sys_dict_typeTable ry.sys_dict_type Pk pk_sys_dict_type ( dict_id ) dict_iddict_id * int 字典主键 # dict_namedict_name varchar(100) default '' 字典名称 t Unq dict_type ( dict_type ) dict_typedict_type varchar(100) default '' 字典类型 t statusstatus int default 0 状态(0正常 1禁用) # create_bycreate_by varchar(64) default '' 创建者 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d update_byupdate_by varchar(64) default '' 更新者 t update_timeupdate_time * timestamp default '0000-00-00 00:00:00' 更新时间 d remarkremark varchar(500) default '' 备注 t sys_jobTable ry.sys_job Pk pk_sys_job ( job_id, job_name, job_group ) job_idjob_id * int 任务ID # Pk pk_sys_job ( job_id, job_name, job_group ) job_namejob_name * varchar(64) default '' 任务名称 t Pk pk_sys_job ( job_id, job_name, job_group ) job_groupjob_group * varchar(64) default '' 任务组名 t method_namemethod_name varchar(500) default '' 任务方法 t paramsparams varchar(200) default '' 方法参数 t cron_expressioncron_expression varchar(255) default '' cron执行表达式 t statusstatus int default 0 状态(0正常 1暂停) # create_bycreate_by varchar(64) default '' 创建者 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d update_byupdate_by varchar(64) default '' 更新者 t update_timeupdate_time * timestamp default '0000-00-00 00:00:00' 更新时间 d remarkremark varchar(500) default '' 备注信息 t sys_job_logTable ry.sys_job_log Pk pk_sys_job_log ( job_log_id ) job_log_idjob_log_id * int 任务日志ID # job_namejob_name * varchar(64) 任务名称 t job_groupjob_group * varchar(64) 任务组名 t method_namemethod_name varchar(500) 任务方法 t paramsparams varchar(200) default '' 方法参数 t job_messagejob_message varchar(500) 日志信息 t is_exceptionis_exception int default 0 是否异常 # exception_infoexception_info text 异常信息 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d sys_logininforTable ry.sys_logininfor Pk pk_sys_logininfor ( info_id ) info_idinfo_id * int 访问ID # login_namelogin_name varchar(50) default '' 登录账号 t ipaddripaddr varchar(50) default '' 登录IP地址 t browserbrowser varchar(50) default '' 浏览器类型 t osos varchar(50) default '' 操作系统 t statusstatus int default 0 登录状态 0成功 1失败 # msgmsg varchar(255) default '' 提示消息 t login_timelogin_time * timestamp default CURRENT_TIMESTAMP 访问时间 d sys_menuTable ry.sys_menu Pk pk_sys_menu ( menu_id ) menu_idmenu_id * int 菜单ID # menu_namemenu_name * varchar(50) 菜单名称 t parent_idparent_id int default 0 父菜单ID # order_numorder_num int 显示顺序 # urlurl varchar(200) default '' 请求地址 t menu_typemenu_type char(1) default '' 类型:M目录,C菜单,F按钮 c visiblevisible int default 0 菜单状态:0显示,1隐藏 # permsperms varchar(100) default '' 权限标识 t iconicon varchar(100) default '' 菜单图标 t create_bycreate_by varchar(64) default '' 创建者 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d update_byupdate_by varchar(64) default '' 更新者 t update_timeupdate_time * timestamp default '0000-00-00 00:00:00' 更新时间 d remarkremark varchar(500) default '' 备注 t sys_oper_logTable ry.sys_oper_log Pk pk_sys_oper_log ( oper_id ) oper_idoper_id * int 日志主键 # titletitle varchar(50) default '' 模块标题 t actionaction varchar(100) default '' 功能请求 t methodmethod varchar(100) default '' 方法名称 t channelchannel varchar(20) default '' 来源渠道 t login_namelogin_name varchar(50) default '' 登录账号 t dept_namedept_name varchar(50) default '' 部门名称 t oper_urloper_url varchar(255) default '' 请求URL t oper_ipoper_ip varchar(30) default '' 主机地址 t oper_paramoper_param varchar(255) default '' 请求参数 t statusstatus int default 0 操作状态 0正常 1异常 # error_msgerror_msg varchar(2000) default '' 错误消息 t oper_timeoper_time * timestamp default CURRENT_TIMESTAMP 操作时间 d sys_postTable ry.sys_post Pk pk_sys_post ( post_id ) post_idpost_id * int 岗位ID # post_codepost_code * varchar(64) 岗位编码 t post_namepost_name * varchar(100) 岗位名称 t post_sortpost_sort * int 显示顺序 # statusstatus * int 状态(0正常 1停用) # create_bycreate_by varchar(64) default '' 创建者 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d update_byupdate_by varchar(64) default '' 更新者 t update_timeupdate_time * timestamp default '0000-00-00 00:00:00' 更新时间 d remarkremark varchar(500) default '' 备注 t sys_roleTable ry.sys_role Pk pk_sys_role ( role_id ) role_idrole_id * int 角色ID # role_namerole_name * varchar(30) 角色名称 t role_keyrole_key * varchar(100) 角色权限字符串 t role_sortrole_sort * int 显示顺序 # statusstatus int default 0 角色状态:0正常,1禁用 # create_bycreate_by varchar(64) default '' 创建者 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d update_byupdate_by varchar(64) default '' 更新者 t update_timeupdate_time * timestamp default '0000-00-00 00:00:00' 更新时间 d remarkremark varchar(500) default '' 备注 t sys_role_menuTable ry.sys_role_menu Pk pk_sys_role_menu ( role_id, menu_id ) role_idrole_id * int 角色ID # Pk pk_sys_role_menu ( role_id, menu_id ) menu_idmenu_id * int 菜单ID # sys_userTable ry.sys_user Pk pk_sys_user ( user_id ) user_iduser_id * int 用户ID # dept_iddept_id int 部门ID # login_namelogin_name varchar(30) default '' 登录账号 t user_nameuser_name varchar(30) default '' 用户昵称 t emailemail varchar(100) default '' 用户邮箱 t phonenumberphonenumber varchar(20) default '' 手机号码 t passwordpassword varchar(100) default '' 密码 t saltsalt varchar(100) default '' 盐加密 t user_typeuser_type char(1) default 'N' 类型:Y默认用户,N非默认用户 c statusstatus int default 0 账号状态:0正常,1禁用 # refuse_desrefuse_des varchar(500) default '' 拒绝登录描述 t create_bycreate_by varchar(64) default '' 创建者 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d update_byupdate_by varchar(64) default '' 更新者 t update_timeupdate_time * timestamp default '0000-00-00 00:00:00' 更新时间 d sys_user_onlineTable ry.sys_user_online Pk pk_sys_user_online ( sessionId ) sessionIdsessionId * varchar(50) default '' 用户会话id t login_namelogin_name varchar(50) default '' 登录账号 t dept_namedept_name varchar(50) default '' 部门名称 t ipaddripaddr varchar(50) default '' 登录IP地址 t browserbrowser varchar(50) default '' 浏览器类型 t osos varchar(50) default '' 操作系统 t statusstatus varchar(10) default '' 在线状态on_line在线off_line离线 t start_timestampstart_timestamp * timestamp default CURRENT_TIMESTAMP session创建时间 d last_access_timelast_access_time * timestamp default '0000-00-00 00:00:00' session最后访问时间 d expire_timeexpire_time int default 0 超时时间,单位为分钟 # sys_user_postTable ry.sys_user_post Pk pk_sys_user_post ( user_id, post_id ) user_iduser_id * varchar(64) 用户ID t Pk pk_sys_user_post ( user_id, post_id ) post_idpost_id * varchar(64) 岗位ID t sys_user_roleTable ry.sys_user_role Pk pk_sys_user_role ( user_id, role_id ) user_iduser_id * int 用户ID # Pk pk_sys_user_role ( user_id, role_id ) role_idrole_id * int 角色ID # sys_deptTable ry.sys_dept Pk pk_sys_dept ( dept_id ) dept_iddept_id * int 部门id # parent_idparent_id int default 0 父部门id # dept_namedept_name varchar(30) default '' 部门名称 t order_numorder_num int default 0 显示顺序 # leaderleader varchar(20) default '' 负责人 t phonephone varchar(20) default '' 联系电话 t emailemail varchar(20) default '' 邮箱 t statusstatus int default 0 部门状态:0正常,1停用 # create_bycreate_by varchar(64) default '' 创建者 t create_timecreate_time * timestamp default CURRENT_TIMESTAMP 创建时间 d update_byupdate_by varchar(64) default '' 更新者 t update_timeupdate_time * timestamp default '0000-00-00 00:00:00' 更新时间 d qrtz_paused_trigger_grpsTable ry.qrtz_paused_trigger_grps Pk pk_qrtz_paused_trigger_grps ( sched_name, trigger_group ) sched_namesched_name * varchar(120) t Pk pk_qrtz_paused_trigger_grps ( sched_name, trigger_group ) trigger_grouptrigger_group * varchar(200) t qrtz_fired_triggersTable ry.qrtz_fired_triggers Pk pk_qrtz_fired_triggers ( sched_name, entry_id ) sched_namesched_name * varchar(120) t Pk pk_qrtz_fired_triggers ( sched_name, entry_id ) entry_identry_id * varchar(95) t trigger_nametrigger_name * varchar(200) t trigger_grouptrigger_group * varchar(200) t instance_nameinstance_name * varchar(200) t fired_timefired_time * bigint # sched_timesched_time * bigint # prioritypriority * int # statestate * varchar(16) t job_namejob_name varchar(200) t job_groupjob_group varchar(200) t is_nonconcurrentis_nonconcurrent varchar(1) t requests_recoveryrequests_recovery varchar(1) t


                    Table qrtz_blob_triggers

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * trigger_name varchar( 200 )
                    * trigger_group varchar( 200 )
                      blob_data blob
                    Indexes
                    pk_qrtz_blob_triggers ON sched_name, trigger_name, trigger_group
                    Foreign Keys
                    qrtz_blob_triggers_ibfk_1 ( sched_name, trigger_name, trigger_group ) ref qrtz_triggers (sched_name, trigger_name, trigger_group)


                    Table qrtz_calendars

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * calendar_name varchar( 200 )
                    * calendar blob
                    Indexes
                    pk_qrtz_calendars ON sched_name, calendar_name


                    Table qrtz_cron_triggers

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * trigger_name varchar( 200 )
                    * trigger_group varchar( 200 )
                    * cron_expression varchar( 200 )
                      time_zone_id varchar( 80 )
                    Indexes
                    pk_qrtz_cron_triggers ON sched_name, trigger_name, trigger_group
                    Foreign Keys
                    qrtz_cron_triggers_ibfk_1 ( sched_name, trigger_name, trigger_group ) ref qrtz_triggers (sched_name, trigger_name, trigger_group)


                    Table qrtz_fired_triggers

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * entry_id varchar( 95 )
                    * trigger_name varchar( 200 )
                    * trigger_group varchar( 200 )
                    * instance_name varchar( 200 )
                    * fired_time bigint
                    * sched_time bigint
                    * priority int
                    * state varchar( 16 )
                      job_name varchar( 200 )
                      job_group varchar( 200 )
                      is_nonconcurrent varchar( 1 )
                      requests_recovery varchar( 1 )
                    Indexes
                    pk_qrtz_fired_triggers ON sched_name, entry_id


                    Table qrtz_job_details

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * job_name varchar( 200 )
                    * job_group varchar( 200 )
                      description varchar( 250 )
                    * job_class_name varchar( 250 )
                    * is_durable varchar( 1 )
                    * is_nonconcurrent varchar( 1 )
                    * is_update_data varchar( 1 )
                    * requests_recovery varchar( 1 )
                      job_data blob
                    Indexes
                    pk_qrtz_job_details ON sched_name, job_name, job_group


                    Table qrtz_locks

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * lock_name varchar( 40 )
                    Indexes
                    pk_qrtz_locks ON sched_name, lock_name


                    Table qrtz_paused_trigger_grps

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * trigger_group varchar( 200 )
                    Indexes
                    pk_qrtz_paused_trigger_grps ON sched_name, trigger_group


                    Table qrtz_scheduler_state

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * instance_name varchar( 200 )
                    * last_checkin_time bigint
                    * checkin_interval bigint
                    Indexes
                    pk_qrtz_scheduler_state ON sched_name, instance_name


                    Table qrtz_simple_triggers

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * trigger_name varchar( 200 )
                    * trigger_group varchar( 200 )
                    * repeat_count bigint
                    * repeat_interval bigint
                    * times_triggered bigint
                    Indexes
                    pk_qrtz_simple_triggers ON sched_name, trigger_name, trigger_group
                    Foreign Keys
                    qrtz_simple_triggers_ibfk_1 ( sched_name, trigger_name, trigger_group ) ref qrtz_triggers (sched_name, trigger_name, trigger_group)


                    Table qrtz_simprop_triggers

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * trigger_name varchar( 200 )
                    * trigger_group varchar( 200 )
                      str_prop_1 varchar( 512 )
                      str_prop_2 varchar( 512 )
                      str_prop_3 varchar( 512 )
                      int_prop_1 int
                      int_prop_2 int
                      long_prop_1 bigint
                      long_prop_2 bigint
                      dec_prop_1 decimal( 13, 4 )
                      dec_prop_2 decimal( 13, 4 )
                      bool_prop_1 varchar( 1 )
                      bool_prop_2 varchar( 1 )
                    Indexes
                    pk_qrtz_simprop_triggers ON sched_name, trigger_name, trigger_group
                    Foreign Keys
                    qrtz_simprop_triggers_ibfk_1 ( sched_name, trigger_name, trigger_group ) ref qrtz_triggers (sched_name, trigger_name, trigger_group)


                    Table qrtz_triggers

                    IndexesField NameData TypeDescription
                    * sched_name varchar( 120 )
                    * trigger_name varchar( 200 )
                    * trigger_group varchar( 200 )
                    * job_name varchar( 200 )
                    * job_group varchar( 200 )
                      description varchar( 250 )
                      next_fire_time bigint
                      prev_fire_time bigint
                      priority int
                    * trigger_state varchar( 16 )
                    * trigger_type varchar( 8 )
                    * start_time bigint
                      end_time bigint
                      calendar_name varchar( 200 )
                      misfire_instr smallint
                      job_data blob
                    Indexes
                    pk_qrtz_triggers ON sched_name, trigger_name, trigger_group
                    sched_name ON sched_name, job_name, job_group
                    Foreign Keys
                    qrtz_triggers_ibfk_1 ( sched_name, job_name, job_group ) ref qrtz_job_details (sched_name, job_name, job_group)


                    Table sys_dept

                    IndexesField NameData TypeDescription
                    * dept_id int AUTOINCREMENT 部门id
                      parent_id int DEFAULT 0 父部门id
                      dept_name varchar( 30 ) DEFAULT '' 部门名称
                      order_num int DEFAULT 0 显示顺序
                      leader varchar( 20 ) DEFAULT '' 负责人
                      phone varchar( 20 ) DEFAULT '' 联系电话
                      email varchar( 20 ) DEFAULT '' 邮箱
                      status int DEFAULT 0 部门状态:0正常,1停用
                      create_by varchar( 64 ) DEFAULT '' 创建者
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                      update_by varchar( 64 ) DEFAULT '' 更新者
                    * update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
                    Indexes
                    pk_sys_dept ON dept_id


                    Table sys_dict_data

                    IndexesField NameData TypeDescription
                    * dict_code int AUTOINCREMENT 字典编码
                      dict_sort int DEFAULT 0 字典排序
                      dict_label varchar( 100 ) DEFAULT '' 字典标签
                      dict_value varchar( 100 ) DEFAULT '' 字典键值
                      dict_type varchar( 100 ) DEFAULT '' 字典类型
                      status int DEFAULT 0 状态(0正常 1禁用)
                      create_by varchar( 64 ) DEFAULT '' 创建者
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                      update_by varchar( 64 ) DEFAULT '' 更新者
                    * update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
                      remark varchar( 500 ) DEFAULT '' 备注
                    Indexes
                    pk_sys_dict_data ON dict_code


                    Table sys_dict_type

                    IndexesField NameData TypeDescription
                    * dict_id int AUTOINCREMENT 字典主键
                      dict_name varchar( 100 ) DEFAULT '' 字典名称
                    dict_type varchar( 100 ) DEFAULT '' 字典类型
                      status int DEFAULT 0 状态(0正常 1禁用)
                      create_by varchar( 64 ) DEFAULT '' 创建者
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                      update_by varchar( 64 ) DEFAULT '' 更新者
                    * update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
                      remark varchar( 500 ) DEFAULT '' 备注
                    Indexes
                    pk_sys_dict_type ON dict_id
                    dict_type ON dict_type


                    Table sys_job

                    IndexesField NameData TypeDescription
                    * job_id int AUTOINCREMENT 任务ID
                    * job_name varchar( 64 ) DEFAULT '' 任务名称
                    * job_group varchar( 64 ) DEFAULT '' 任务组名
                      method_name varchar( 500 ) DEFAULT '' 任务方法
                      params varchar( 200 ) DEFAULT '' 方法参数
                      cron_expression varchar( 255 ) DEFAULT '' cron执行表达式
                      status int DEFAULT 0 状态(0正常 1暂停)
                      create_by varchar( 64 ) DEFAULT '' 创建者
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                      update_by varchar( 64 ) DEFAULT '' 更新者
                    * update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
                      remark varchar( 500 ) DEFAULT '' 备注信息
                    Indexes
                    pk_sys_job ON job_id, job_name, job_group


                    Table sys_job_log

                    IndexesField NameData TypeDescription
                    * job_log_id int AUTOINCREMENT 任务日志ID
                    * job_name varchar( 64 ) 任务名称
                    * job_group varchar( 64 ) 任务组名
                      method_name varchar( 500 ) 任务方法
                      params varchar( 200 ) DEFAULT '' 方法参数
                      job_message varchar( 500 ) 日志信息
                      is_exception int DEFAULT 0 是否异常
                      exception_info text 异常信息
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                    Indexes
                    pk_sys_job_log ON job_log_id


                    Table sys_logininfor

                    IndexesField NameData TypeDescription
                    * info_id int AUTOINCREMENT 访问ID
                      login_name varchar( 50 ) DEFAULT '' 登录账号
                      ipaddr varchar( 50 ) DEFAULT '' 登录IP地址
                      browser varchar( 50 ) DEFAULT '' 浏览器类型
                      os varchar( 50 ) DEFAULT '' 操作系统
                      status int DEFAULT 0 登录状态 0成功 1失败
                      msg varchar( 255 ) DEFAULT '' 提示消息
                    * login_time timestamp DEFAULT CURRENT_TIMESTAMP 访问时间
                    Indexes
                    pk_sys_logininfor ON info_id


                    Table sys_menu

                    IndexesField NameData TypeDescription
                    * menu_id int AUTOINCREMENT 菜单ID
                    * menu_name varchar( 50 ) 菜单名称
                      parent_id int DEFAULT 0 父菜单ID
                      order_num int 显示顺序
                      url varchar( 200 ) DEFAULT '' 请求地址
                      menu_type char( 1 ) DEFAULT '' 类型:M目录,C菜单,F按钮
                      visible int DEFAULT 0 菜单状态:0显示,1隐藏
                      perms varchar( 100 ) DEFAULT '' 权限标识
                      icon varchar( 100 ) DEFAULT '' 菜单图标
                      create_by varchar( 64 ) DEFAULT '' 创建者
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                      update_by varchar( 64 ) DEFAULT '' 更新者
                    * update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
                      remark varchar( 500 ) DEFAULT '' 备注
                    Indexes
                    pk_sys_menu ON menu_id


                    Table sys_oper_log

                    IndexesField NameData TypeDescription
                    * oper_id int AUTOINCREMENT 日志主键
                      title varchar( 50 ) DEFAULT '' 模块标题
                      action varchar( 100 ) DEFAULT '' 功能请求
                      method varchar( 100 ) DEFAULT '' 方法名称
                      channel varchar( 20 ) DEFAULT '' 来源渠道
                      login_name varchar( 50 ) DEFAULT '' 登录账号
                      dept_name varchar( 50 ) DEFAULT '' 部门名称
                      oper_url varchar( 255 ) DEFAULT '' 请求URL
                      oper_ip varchar( 30 ) DEFAULT '' 主机地址
                      oper_param varchar( 255 ) DEFAULT '' 请求参数
                      status int DEFAULT 0 操作状态 0正常 1异常
                      error_msg varchar( 2000 ) DEFAULT '' 错误消息
                    * oper_time timestamp DEFAULT CURRENT_TIMESTAMP 操作时间
                    Indexes
                    pk_sys_oper_log ON oper_id


                    Table sys_post

                    IndexesField NameData TypeDescription
                    * post_id int AUTOINCREMENT 岗位ID
                    * post_code varchar( 64 ) 岗位编码
                    * post_name varchar( 100 ) 岗位名称
                    * post_sort int 显示顺序
                    * status int 状态(0正常 1停用)
                      create_by varchar( 64 ) DEFAULT '' 创建者
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                      update_by varchar( 64 ) DEFAULT '' 更新者
                    * update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
                      remark varchar( 500 ) DEFAULT '' 备注
                    Indexes
                    pk_sys_post ON post_id


                    Table sys_role

                    IndexesField NameData TypeDescription
                    * role_id int AUTOINCREMENT 角色ID
                    * role_name varchar( 30 ) 角色名称
                    * role_key varchar( 100 ) 角色权限字符串
                    * role_sort int 显示顺序
                      status int DEFAULT 0 角色状态:0正常,1禁用
                      create_by varchar( 64 ) DEFAULT '' 创建者
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                      update_by varchar( 64 ) DEFAULT '' 更新者
                    * update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
                      remark varchar( 500 ) DEFAULT '' 备注
                    Indexes
                    pk_sys_role ON role_id


                    Table sys_role_menu

                    IndexesField NameData TypeDescription
                    * role_id int 角色ID
                    * menu_id int 菜单ID
                    Indexes
                    pk_sys_role_menu ON role_id, menu_id


                    Table sys_user

                    IndexesField NameData TypeDescription
                    * user_id int AUTOINCREMENT 用户ID
                      dept_id int 部门ID
                      login_name varchar( 30 ) DEFAULT '' 登录账号
                      user_name varchar( 30 ) DEFAULT '' 用户昵称
                      email varchar( 100 ) DEFAULT '' 用户邮箱
                      phonenumber varchar( 20 ) DEFAULT '' 手机号码
                      password varchar( 100 ) DEFAULT '' 密码
                      salt varchar( 100 ) DEFAULT '' 盐加密
                      user_type char( 1 ) DEFAULT 'N' 类型:Y默认用户,N非默认用户
                      status int DEFAULT 0 账号状态:0正常,1禁用
                      refuse_des varchar( 500 ) DEFAULT '' 拒绝登录描述
                      create_by varchar( 64 ) DEFAULT '' 创建者
                    * create_time timestamp DEFAULT CURRENT_TIMESTAMP 创建时间
                      update_by varchar( 64 ) DEFAULT '' 更新者
                    * update_time timestamp DEFAULT '0000-00-00 00:00:00' 更新时间
                    Indexes
                    pk_sys_user ON user_id


                    Table sys_user_online

                    IndexesField NameData TypeDescription
                    * sessionId varchar( 50 ) DEFAULT '' 用户会话id
                      login_name varchar( 50 ) DEFAULT '' 登录账号
                      dept_name varchar( 50 ) DEFAULT '' 部门名称
                      ipaddr varchar( 50 ) DEFAULT '' 登录IP地址
                      browser varchar( 50 ) DEFAULT '' 浏览器类型
                      os varchar( 50 ) DEFAULT '' 操作系统
                      status varchar( 10 ) DEFAULT '' 在线状态on_line在线off_line离线
                    * start_timestsamp timestamp DEFAULT CURRENT_TIMESTAMP session创建时间
                    * last_access_time timestamp DEFAULT '0000-00-00 00:00:00' session最后访问时间
                      expire_time int DEFAULT 0 超时时间,单位为分钟
                    Indexes
                    pk_sys_user_online ON sessionId


                    Table sys_user_post

                    IndexesField NameData TypeDescription
                    * user_id varchar( 64 ) 用户ID
                    * post_id varchar( 64 ) 岗位ID
                    Indexes
                    pk_sys_user_post ON user_id, post_id


                    Table sys_user_role

                    IndexesField NameData TypeDescription
                    * user_id int 用户ID
                    * role_id int 角色ID
                    Indexes
                    pk_sys_user_role ON user_id, role_id

                    Powered by DbSchema

                    ================================================ FILE: sql/ruoyi.pdm ================================================ 21C20947-ED50-4632-B638-DC1A02BD948A ruoyi ruoyi 1524449337 Administrator 1538297587 admin [FolderOptions] [FolderOptions\Physical Objects] GenerationCheckModel=Yes GenerationPath= GenerationOptions= GenerationTasks= GenerationTargets= GenerationSelections= RevPkey=Yes RevFkey=Yes RevAkey=Yes RevCheck=Yes RevIndx=Yes RevOpts=Yes RevViewAsTabl=No RevViewOpts=Yes RevSystAsTabl=Yes RevTablPerm=No RevViewPerm=No RevProcPerm=No RevDbpkPerm=No RevSqncPerm=No RevAdtPerm=No RevUserPriv=No RevUserOpts=No RevGrpePriv=No RevRolePriv=No RevDtbsOpts=Yes RevDtbsPerm=No RevViewIndx=Yes RevJidxOpts=Yes RevStats=No RevTspcPerm=No RevCaseSensitive=No GenTrgrStdMsg=Yes GenTrgrMsgTab= GenTrgrMsgNo= GenTrgrMsgTxt= TrgrPreserve=No TrgrIns=Yes TrgrUpd=Yes TrgrDel=Yes TrgrC2Ins=Yes TrgrC2Upd=Yes TrgrC3=Yes TrgrC4=Yes TrgrC5=Yes TrgrC6=Yes TrgrC7=Yes TrgrC8=Yes TrgrC9=Yes TrgrC10=Yes TrgrC11=Yes TrgrC1=Yes TrgrC12Ins=Yes TrgrC12Upd=Yes TrgrC13=Yes UpdateTableStatistics=Yes UpdateColumnStatistics=Yes [FolderOptions\Physical Objects\Database Generation] GenScriptName=orders.sql GenScriptName0=orders.sql GenScriptName1=studentsystem.sql GenScriptName2=NetCTOSS.sql GenScriptName3=product.sql GenScriptName4=voteSystem.sql GenScriptName5=.sql GenScriptName6=enterpriseManagement.sql GenScriptName7=crebas.sql GenScriptName8= GenScriptName9= GenPathName=C:\Users\Administrator\Desktop\ GenSingleFile=Yes GenODBC=No GenCheckModel=Yes GenScriptPrev=Yes GenArchiveModel=No GenUseSync=No GenSyncChoice=0 GenSyncArch= GenSyncRmg=0 [FolderOptions\Physical Objects\Database Generation\Format] GenScriptTitle=Yes GenScriptNamLabl=No GenScriptQDtbs=No GenScriptQOwnr=Yes GenScriptCase=0 GenScriptEncoding=ANSI GenScriptNAcct=No IdentifierDelimiter=" [FolderOptions\Physical Objects\Database Generation\Database] Create=Yes Open=Yes Close=Yes Drop=Yes Permission=No [FolderOptions\Physical Objects\Database Generation\Database\Create] Physical Options=Yes Header=Yes Footer=Yes [FolderOptions\Physical Objects\Database Generation\Tablespace] Create=Yes Drop=Yes Comment=Yes Permission=No [FolderOptions\Physical Objects\Database Generation\Tablespace\Create] Header=Yes Footer=Yes [FolderOptions\Physical Objects\Database Generation\Storage] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\User] Create=Yes Grant=Yes Drop=Yes Comment=Yes Privilege=No [FolderOptions\Physical Objects\Database Generation\User\Create] Physical Options=No [FolderOptions\Physical Objects\Database Generation\Group] Create=Yes Drop=Yes Comment=Yes Privilege=No [FolderOptions\Physical Objects\Database Generation\Role] Create=Yes Drop=Yes Privilege=No [FolderOptions\Physical Objects\Database Generation\UserDefinedDataType] Create=Yes Comment=Yes Drop=Yes [FolderOptions\Physical Objects\Database Generation\UserDefinedDataType\Create] Default value=Yes Check=Yes [FolderOptions\Physical Objects\Database Generation\AbstractDataType] Create=Yes Header=Yes Footer=Yes Drop=Yes Comment=Yes Install JAVA class=Yes Remove JAVA class=Yes Permission=No [FolderOptions\Physical Objects\Database Generation\Rule] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Default] Create=Yes Comment=Yes Drop=Yes [FolderOptions\Physical Objects\Database Generation\Sequence] Create=Yes Drop=Yes Comment=Yes Permission=No [FolderOptions\Physical Objects\Database Generation\Table&&Column] [FolderOptions\Physical Objects\Database Generation\Table&&Column\Table] Create=Yes Drop=Yes Comment=Yes Permission=No [FolderOptions\Physical Objects\Database Generation\Table&&Column\Table\Create] Check=Yes Physical Options=Yes Header=Yes Footer=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Table\Create\Check] Constraint declaration=No [FolderOptions\Physical Objects\Database Generation\Table&&Column\Column] User datatype=No Default value=Yes Check=Yes Physical Options=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Column\Check] Constraint declaration=No [FolderOptions\Physical Objects\Database Generation\Table&&Column\Key] [FolderOptions\Physical Objects\Database Generation\Table&&Column\Key\Primary key] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Key\Primary key\Create] Constraint declaration=No Physical Options=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Key\Alternate key] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Key\Alternate key\Create] Constraint declaration=No Physical Options=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Foreign key] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Foreign key\Create] Constraint declaration=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Index] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Index\Create] Constraint declaration=Yes Physical Options=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Index\Filter] Primary key=No Foreign key=No Alternate key=No Cluster=Yes Other=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Trigger] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Table&&Column\Trigger\Filter] For insert=Yes For update=Yes For delete=Yes For other=Yes [FolderOptions\Physical Objects\Database Generation\View] Create=Yes Drop=Yes Comment=Yes Permission=No [FolderOptions\Physical Objects\Database Generation\View\Create] Force Column list=No Physical Options=Yes Header=Yes Footer=Yes [FolderOptions\Physical Objects\Database Generation\View\ViewColumn] Comment=Yes [FolderOptions\Physical Objects\Database Generation\View\ViewIndex] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\View\ViewIndex\Create] Physical Options=Yes [FolderOptions\Physical Objects\Database Generation\View\ViewIndex\Filter] Cluster=Yes Other=Yes [FolderOptions\Physical Objects\Database Generation\View\Trigger] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\View\Trigger\Filter] For insert=Yes For update=Yes For delete=Yes For other=Yes [FolderOptions\Physical Objects\Database Generation\DBMSTrigger] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Synonym] Create=Yes Drop=Yes [FolderOptions\Physical Objects\Database Generation\Synonym\Filter] Table=Yes View=Yes Proc=Yes Synonym=Yes Database Package=Yes Sequence=Yes [FolderOptions\Physical Objects\Database Generation\JoinIndex] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\JoinIndex\Create] Physical Options=Yes Header=Yes Footer=Yes [FolderOptions\Physical Objects\Database Generation\Procedure] Create=Yes Drop=Yes Comment=Yes Permission=No [FolderOptions\Physical Objects\Database Generation\Procedure\Create] Header=Yes Footer=Yes [FolderOptions\Physical Objects\Database Generation\DatabasePackage] Create=Yes Drop=Yes Permission=No [FolderOptions\Physical Objects\Database Generation\WebService] Create=Yes Drop=Yes Comment=Yes [FolderOptions\Physical Objects\Database Generation\Dimension] Create=Yes Drop=Yes [FolderOptions\Physical Objects\Database Generation\Synchronization] GenBackupTabl=1 GenKeepBackTabl=1 GenTmpTablDrop=No GenKeepTablOpts=No [FolderOptions\Physical Objects\Test Data] GenDataPathName= GenDataSinglefile=Yes GenDataScriptName=testdata GenDataScriptName0= GenDataScriptName1= GenDataScriptName2= GenDataScriptName3= GenDataScriptName4= GenDataScriptName5= GenDataScriptName6= GenDataScriptName7= GenDataScriptName8= GenDataScriptName9= GenDataOdbc=0 GenDataDelOld=No GenDataTitle=No GenDataDefNumRows=20 GenDataCommit=0 GenDataPacket=0 GenDataOwner=No GenDataProfNumb= GenDataProfChar= GenDataProfDate= GenDataCSVSeparator=, GenDataFileFormat=CSV GenDataUseWizard=No [FolderOptions\Pdm] IndxIQName=%COLUMN%_%INDEXTYPE% IndxPK=Yes IndxFK=Yes IndxAK=Yes IndxPKName=%TABLE%_PK IndxFKName=%REFR%_FK IndxAKName=%AKEY%_AK IndxPreserve=No IndxThreshold=0 IndxStats=No RefrPreserve=No JidxPreserve=No RbldMultiFact=Yes RbldMultiDim=Yes RbldMultiJidx=Yes CubePreserve=No TablStProcPreserve=No ProcDepPreserve=Yes TrgrDepPreserve=Yes CubeScriptPath= CubeScriptCase=0 CubeScriptEncoding=ANSI CubeScriptNacct=No CubeScriptHeader=No CubeScriptExt=csv CubeScriptExt0=txt CubeScriptExt1= CubeScriptExt2= CubeScriptSep=, CubeScriptDeli=" DfltDomnName=D_%.U:VALUE% DfltColnName=D_%.U:VALUE% DfltReuse=Yes DfltDrop=Yes [ModelOptions] [ModelOptions\Physical Objects] CaseSensitive=No DisplayName=Yes EnableTrans=No EnableRequirements=No DefaultDttp= IgnoreOwner=No RebuildTrigger=Yes RefrUnique=No RefrAutoMigrate=Yes RefrMigrateReuse=Yes RefrMigrateDomain=Yes RefrMigrateCheck=Yes RefrMigrateRule=Yes RefrMigrateExtd=No RefrMigrDefaultLink=No RefrDfltImpl=D RefrPrgtColn=No RefrMigrateToEnd=No RebuildTriggerDep=No ColnFKName=%.3:PARENT%_%COLUMN% ColnFKNameUse=No DomnCopyDttp=Yes DomnCopyChck=No DomnCopyRule=No DomnCopyMand=No DomnCopyExtd=No DomnCopyProf=No Notation=0 DomnDefaultMandatory=No ColnDefaultMandatory=No TablDefaultOwner= ViewDefaultOwner= TrgrDefaultOwnerTabl= TrgrDefaultOwnerView= IdxDefaultOwnerTabl= IdxDefaultOwnerView= JdxDefaultOwner= DBPackDefaultOwner= SeqDefaultOwner= ProcDefaultOwner= DBMSTrgrDefaultOwner= Currency=USD RefrDeleteConstraint=1 RefrUpdateConstraint=1 RefrParentMandatory=No RefrParentChangeAllow=Yes RefrCheckOnCommit=No [ModelOptions\Physical Objects\NamingOptionsTemplates] [ModelOptions\Physical Objects\ClssNamingOptions] [ModelOptions\Physical Objects\ClssNamingOptions\PDMPCKG] [ModelOptions\Physical Objects\ClssNamingOptions\PDMPCKG\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\PDMPCKG\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\PDMDOMN] [ModelOptions\Physical Objects\ClssNamingOptions\PDMDOMN\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\PDMDOMN\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\TABL] [ModelOptions\Physical Objects\ClssNamingOptions\TABL\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\TABL\Code] Template= MaxLen=64 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\COLN] [ModelOptions\Physical Objects\ClssNamingOptions\COLN\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\COLN\Code] Template= MaxLen=64 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\INDX] [ModelOptions\Physical Objects\ClssNamingOptions\INDX\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\INDX\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\REFR] [ModelOptions\Physical Objects\ClssNamingOptions\REFR\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\REFR\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\VREF] [ModelOptions\Physical Objects\ClssNamingOptions\VREF\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\VREF\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\VIEW] [ModelOptions\Physical Objects\ClssNamingOptions\VIEW\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\VIEW\Code] Template= MaxLen=64 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\VIEWC] [ModelOptions\Physical Objects\ClssNamingOptions\VIEWC\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\VIEWC\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\WEBSERV] [ModelOptions\Physical Objects\ClssNamingOptions\WEBSERV\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\WEBSERV\Code] Template= MaxLen=254 Case=M ValidChar='a'-'z','A'-'Z','0'-'9',"/-_.!~*'()" InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\WEBOP] [ModelOptions\Physical Objects\ClssNamingOptions\WEBOP\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\WEBOP\Code] Template= MaxLen=254 Case=M ValidChar='a'-'z','A'-'Z','0'-'9',"/-_.!~*'()" InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\WPARAM] [ModelOptions\Physical Objects\ClssNamingOptions\WPARAM\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\WPARAM\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\FACT] [ModelOptions\Physical Objects\ClssNamingOptions\FACT\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\FACT\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\DIMN] [ModelOptions\Physical Objects\ClssNamingOptions\DIMN\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\DIMN\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\CUBE] [ModelOptions\Physical Objects\ClssNamingOptions\CUBE\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\CUBE\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\MEAS] [ModelOptions\Physical Objects\ClssNamingOptions\MEAS\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\MEAS\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\DATTR] [ModelOptions\Physical Objects\ClssNamingOptions\DATTR\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\DATTR\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\FILO] [ModelOptions\Physical Objects\ClssNamingOptions\FILO\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\FILO\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\FRMEOBJ] [ModelOptions\Physical Objects\ClssNamingOptions\FRMEOBJ\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\FRMEOBJ\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\FRMELNK] [ModelOptions\Physical Objects\ClssNamingOptions\FRMELNK\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\FRMELNK\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\DefaultClass] [ModelOptions\Physical Objects\ClssNamingOptions\DefaultClass\Name] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Physical Objects\ClssNamingOptions\DefaultClass\Code] Template= MaxLen=254 Case=M ValidChar= InvldChar= AllValid=Yes NoAccent=No DefaultChar= Script= ConvTable= ConvTablePath=%_HOME%\Resource Files\Conversion Tables [ModelOptions\Connection] [ModelOptions\Pdm] [ModelOptions\Generate] [ModelOptions\Generate\Pdm] RRMapping=No [ModelOptions\Generate\Cdm] CheckModel=Yes SaveLinks=Yes NameToCode=No Notation=2 [ModelOptions\Generate\Oom] CheckModel=Yes SaveLinks=Yes ORMapping=No NameToCode=Yes ClassPrefix= [ModelOptions\Generate\Xsm] CheckModel=Yes SaveLinks=Yes ORMapping=No NameToCode=No [ModelOptions\Generate\Ldm] CheckModel=Yes SaveLinks=Yes NameToCode=No [ModelOptions\Default Opts] [ModelOptions\Default Opts\TABL] PhysOpts= [ModelOptions\Default Opts\COLN] PhysOpts= [ModelOptions\Default Opts\INDX] PhysOpts= [ModelOptions\Default Opts\AKEY] PhysOpts= [ModelOptions\Default Opts\PKEY] PhysOpts= [ModelOptions\Default Opts\STOR] PhysOpts= [ModelOptions\Default Opts\TSPC] PhysOpts= [ModelOptions\Default Opts\SQNC] PhysOpts= [ModelOptions\Default Opts\DTBS] PhysOpts= [ModelOptions\Default Opts\USER] PhysOpts= [ModelOptions\Default Opts\JIDX] PhysOpts= AFAD9ECF-F417-4FCE-BEA4-884857D4C1A9 MySQL 5.0 MYSQL50 1524449337 Administrator 1524449337 Administrator F4F16ECD-F2F1-4006-AF6F-638D5C65F35E 4BA9F647-DAB1-11D1-9944-006097355D9B B6C2C4A4-6A8A-41F3-909D-C7B514E1EAE2 PhysicalDiagram_1 PhysicalDiagram_1 1524449325 Administrator 1538297386 admin [DisplayPreferences] [DisplayPreferences\PDM] [DisplayPreferences\General] Adjust to text=Yes Snap Grid=No Constrain Labels=Yes Display Grid=No Show Page Delimiter=Yes Grid size=0 Graphic unit=2 Window color=255, 255, 255 Background image= Background mode=8 Watermark image= Watermark mode=8 Show watermark on screen=No Gradient mode=0 Gradient end color=255, 255, 255 Show Swimlane=No SwimlaneVert=Yes TreeVert=No CompDark=0 [DisplayPreferences\Object] Mode=0 Trunc Length=80 Word Length=80 Word Text=!""#$%&'()*+,-./:;<=>?@[\]^_`{|}~ Shortcut IntIcon=Yes Shortcut IntLoct=Yes Shortcut IntFullPath=No Shortcut IntLastPackage=Yes Shortcut ExtIcon=Yes Shortcut ExtLoct=No Shortcut ExtFullPath=No Shortcut ExtLastPackage=Yes Shortcut ExtIncludeModl=Yes EObjShowStrn=Yes ExtendedObject.Comment=No ExtendedObject.IconPicture=No ExtendedObject_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Object Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF] <Separator Name="Separator" />[CRLF] <StandardAttribute Name="Comment" Attribute="Comment" Prefix="" Suffix="" Alignment="LEFT" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> ELnkShowStrn=Yes ELnkShowName=Yes ExtendedLink_SymbolLayout=<Form>[CRLF] <Form Name="Center" >[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF] <Form Name="Source" >[CRLF] </Form>[CRLF] <Form Name="Destination" >[CRLF] </Form>[CRLF]</Form> FileObject.Stereotype=No FileObject.DisplayName=Yes FileObject.LocationOrName=No FileObject.IconPicture=No FileObject.IconMode=Yes FileObject_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="Yes" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Location" Attribute="LocationOrName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> PckgShowStrn=Yes Package.Comment=No Package.IconPicture=No Package_SymbolLayout= Display Model Version=Yes Table.Stereotype=Yes Table.DisplayName=Yes Table.OwnerDisplayName=No Table.Columns=Yes Table.Columns._Filter=""PDMCOLNALL Table.Columns._Columns=Stereotype DataType KeyIndicator Table.Columns._Limit=-5 Table.Keys=No Table.Keys._Columns=Stereotype Indicator Table.Indexes=No Table.Indexes._Columns=Stereotype Table.Triggers=No Table.Triggers._Columns=Stereotype Table.Comment=No Table.IconPicture=No Table_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="Yes" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Owner and Name" Attribute="OwnerDisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <Separator Name="Separator" />[CRLF] <StandardCollection Name="Columns" Collection="Columns" Columns="Stereotype No\r\nDisplayName Yes\r\nDataType No\r\nSymbolDataType No &quot;Domain or Data type&quot;\r\nDomain No\r\nKeyIndicator No\r\nIndexIndicator No\r\nNullStatus No" Filters="&quot;All Columns&quot; PDMCOLNALL &quot;&quot;\r\n&quot;PK Columns&quot; PDMCOLNPK &quot;PRIM \&quot;TRUE\&quot; TRUE&quot;\r\n&quot;Key Columns&quot; PDMCOLNKEY &quot;KEYS \&quot;TRUE\&quot; TRUE&quot;" HasLimit="Yes" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Keys" Collection="Keys" Columns="Stereotype No\r\nDisplayName Yes\r\nIndicator No" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Indexes" Collection="Indexes" Columns="Stereotype No\r\nDisplayName Yes\r\nIndicator No" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Triggers" Collection="Triggers" Columns="Stereotype No\r\nDisplayName Yes" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Comment" Attribute="Comment" Prefix="" Suffix="" Alignment="LEFT" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> View.Stereotype=Yes View.DisplayName=Yes View.OwnerDisplayName=No View.Columns=Yes View.Columns._Columns=DisplayName View.Columns._Limit=-5 View.TemporaryVTables=Yes View.Indexes=No View.Comment=No View.IconPicture=No View_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="Yes" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Owner and Name" Attribute="OwnerDisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <Separator Name="Separator" />[CRLF] <StandardCollection Name="Columns" Collection="Columns" Columns="DisplayName No\r\nExpression No\r\nDataType No\r\nSymbolDataType No &quot;Domain or Data type&quot;\r\nIndexIndicator No" HasLimit="Yes" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Tables" Collection="TemporaryVTables" Columns="Name Yes" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardCollection Name="Indexes" Collection="Indexes" Columns="DisplayName Yes" HasLimit="No" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Comment" Attribute="Comment" Prefix="" Suffix="" Alignment="LEFT" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> Procedure.Stereotype=No Procedure.DisplayName=Yes Procedure.OwnerDisplayName=No Procedure.Comment=No Procedure.IconPicture=No Procedure_SymbolLayout=<Form>[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="Yes" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Owner and Name" Attribute="OwnerDisplayName" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <Separator Name="Separator" />[CRLF] <StandardAttribute Name="Comment" Attribute="Comment" Prefix="" Suffix="" Alignment="LEFT" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Icon" Attribute="IconPicture" Prefix="" Suffix="" Alignment="CNTR" Caption="" Mandatory="Yes" />[CRLF]</Form> Reference.Cardinality=No Reference.ImplementationType=No Reference.ChildRole=Yes Reference.Stereotype=Yes Reference.DisplayName=No Reference.ForeignKeyConstraintName=Yes Reference.JoinExpression=No Reference.Integrity=No Reference.ParentRole=Yes Reference_SymbolLayout=<Form>[CRLF] <Form Name="Source" >[CRLF] <StandardAttribute Name="Cardinality" Attribute="Cardinality" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Implementation" Attribute="ImplementationType" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Child Role" Attribute="ChildRole" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF] <Form Name="Center" >[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="No" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Cons&amp;traint Name" Attribute="ForeignKeyConstraintName" Prefix="" Suffix="" Caption="Cons&amp;traint Name" Mandatory="No" />[CRLF] <StandardAttribute Name="Join" Attribute="JoinExpression" Prefix="" Suffix="" Caption="Join" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] <StandardAttribute Name="Referential integrity" Attribute="Integrity" Prefix="" Suffix="" Caption="Referential integrity" Mandatory="No" />[CRLF] </Form>[CRLF] <Form Name="Destination" >[CRLF] <StandardAttribute Name="Parent Role" Attribute="ParentRole" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF]</Form> ViewReference.ChildRole=Yes ViewReference.Stereotype=Yes ViewReference.DisplayName=No ViewReference.JoinExpression=No ViewReference.ParentRole=Yes ViewReference_SymbolLayout=<Form>[CRLF] <Form Name="Source" >[CRLF] <StandardAttribute Name="Child Role" Attribute="ChildRole" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF] <Form Name="Center" >[CRLF] <StandardAttribute Name="Stereotype" Attribute="Stereotype" Prefix="&lt;&lt;" Suffix="&gt;&gt;" Caption="" Mandatory="No" />[CRLF] <ExclusiveChoice Name="Exclusive Choice" Mandatory="No" Display="HorizontalRadios" >[CRLF] <StandardAttribute Name="Name" Attribute="DisplayName" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] <StandardAttribute Name="Join Expression" Attribute="JoinExpression" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </ExclusiveChoice>[CRLF] </Form>[CRLF] <Form Name="Destination" >[CRLF] <StandardAttribute Name="Parent Role" Attribute="ParentRole" Prefix="" Suffix="" Caption="" Mandatory="No" />[CRLF] </Form>[CRLF]</Form> File Location=No PckgStrn=Yes ColnMode=0 ColnMax=5 TablOwnr=No ColnDttp=Yes ColnDomn=No ColnShowDomn=No ColnKey=Yes ColnIndx=No ColnMand=No ColnStrn=Yes VColName=Yes VColExpr=No VColDttp=No VColIndx=No VColCMod=0 VColCMax=5 ProcOwnr=No KeyStrn=Yes IndxStrn=Yes TrgrStrn=Yes [DisplayPreferences\Symbol] [DisplayPreferences\Symbol\FRMEOBJ] STRNFont=新宋体,8,N STRNFont color=0, 0, 0 DISPNAMEFont=新宋体,8,N DISPNAMEFont color=0, 0, 0 LABLFont=新宋体,8,N LABLFont color=0, 0, 0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Width=6000 Height=2000 Brush color=255 255 255 Fill Color=Yes Brush style=6 Brush bitmap mode=12 Brush gradient mode=64 Brush gradient color=192 192 192 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 255 128 128 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\FRMELNK] CENTERFont=新宋体,8,N CENTERFont color=0, 0, 0 Line style=0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Brush color=255 255 255 Fill Color=Yes Brush style=1 Brush bitmap mode=12 Brush gradient mode=0 Brush gradient color=118 118 118 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 128 128 255 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\FILO] OBJSTRNFont=新宋体,8,N OBJSTRNFont color=0, 0, 0 DISPNAMEFont=新宋体,8,N DISPNAMEFont color=0, 0, 0 LCNMFont=新宋体,8,N LCNMFont color=0, 0, 0 AutoAdjustToText=Yes Keep aspect=Yes Keep center=Yes Keep size=No Width=2400 Height=2400 Brush color=255 255 255 Fill Color=No Brush style=1 Brush bitmap mode=12 Brush gradient mode=0 Brush gradient color=118 118 118 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 0 0 255 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\PDMPCKG] STRNFont=新宋体,8,N STRNFont color=0, 0, 0 DISPNAMEFont=新宋体,8,N DISPNAMEFont color=0, 0, 0 LABLFont=新宋体,8,N LABLFont color=0, 0, 0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Width=4800 Height=3600 Brush color=255 255 192 Fill Color=Yes Brush style=6 Brush bitmap mode=12 Brush gradient mode=65 Brush gradient color=255 255 255 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 178 178 178 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\TABL] STRNFont=新宋体,8,N STRNFont color=0, 0, 0 DISPNAMEFont=新宋体,8,N DISPNAMEFont color=0, 0, 0 OWNRDISPNAMEFont=新宋体,8,N OWNRDISPNAMEFont color=0, 0, 0 ColumnsFont=新宋体,8,N ColumnsFont color=0, 0, 0 TablePkColumnsFont=新宋体,8,U TablePkColumnsFont color=0, 0, 0 TableFkColumnsFont=新宋体,8,N TableFkColumnsFont color=0, 0, 0 KeysFont=新宋体,8,N KeysFont color=0, 0, 0 IndexesFont=新宋体,8,N IndexesFont color=0, 0, 0 TriggersFont=新宋体,8,N TriggersFont color=0, 0, 0 LABLFont=新宋体,8,N LABLFont color=0, 0, 0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Width=4800 Height=4000 Brush color=178 214 252 Fill Color=Yes Brush style=6 Brush bitmap mode=12 Brush gradient mode=65 Brush gradient color=255 255 255 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 0 128 192 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\VIEW] STRNFont=新宋体,8,N STRNFont color=0, 0, 0 DISPNAMEFont=新宋体,8,N DISPNAMEFont color=0, 0, 0 OWNRDISPNAMEFont=新宋体,8,N OWNRDISPNAMEFont color=0, 0, 0 ColumnsFont=新宋体,8,N ColumnsFont color=0, 0, 0 TablePkColumnsFont=新宋体,8,U TablePkColumnsFont color=0, 0, 0 TableFkColumnsFont=新宋体,8,N TableFkColumnsFont color=0, 0, 0 TemporaryVTablesFont=新宋体,8,N TemporaryVTablesFont color=0, 0, 0 IndexesFont=新宋体,8,N IndexesFont color=0, 0, 0 LABLFont=新宋体,8,N LABLFont color=0, 0, 0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Width=4800 Height=4000 Brush color=208 208 255 Fill Color=Yes Brush style=6 Brush bitmap mode=12 Brush gradient mode=65 Brush gradient color=255 255 255 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 128 128 192 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\PROC] STRNFont=新宋体,8,N STRNFont color=0, 0, 0 DISPNAMEFont=新宋体,8,N DISPNAMEFont color=0, 0, 0 OWNRDISPNAMEFont=新宋体,8,N OWNRDISPNAMEFont color=0, 0, 0 LABLFont=新宋体,8,N LABLFont color=0, 0, 0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Width=4000 Height=1000 Brush color=255 255 192 Fill Color=Yes Brush style=6 Brush bitmap mode=12 Brush gradient mode=65 Brush gradient color=255 255 255 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 128 108 0 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\REFR] SOURCEFont=新宋体,8,N SOURCEFont color=0, 0, 0 CENTERFont=新宋体,8,N CENTERFont color=0, 0, 0 DESTINATIONFont=新宋体,8,N DESTINATIONFont color=0, 0, 0 Line style=0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Brush color=255 255 255 Fill Color=Yes Brush style=1 Brush bitmap mode=12 Brush gradient mode=0 Brush gradient color=118 118 118 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 0 128 192 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\VREF] SOURCEFont=新宋体,8,N SOURCEFont color=0, 0, 0 CENTERFont=新宋体,8,N CENTERFont color=0, 0, 0 DESTINATIONFont=新宋体,8,N DESTINATIONFont color=0, 0, 0 Line style=0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Brush color=255 255 255 Fill Color=Yes Brush style=1 Brush bitmap mode=12 Brush gradient mode=0 Brush gradient color=118 118 118 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 128 128 192 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\USRDEPD] OBJXSTRFont=新宋体,8,N OBJXSTRFont color=0, 0, 0 Line style=0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Brush color=255 255 255 Fill Color=Yes Brush style=1 Brush bitmap mode=12 Brush gradient mode=0 Brush gradient color=118 118 118 Brush background image= Custom shape= Custom text mode=0 Pen=2 0 128 128 255 Shadow color=192 192 192 Shadow=0 [DisplayPreferences\Symbol\Free Symbol] Free TextFont=新宋体,8,N Free TextFont color=0, 0, 0 Line style=0 AutoAdjustToText=Yes Keep aspect=No Keep center=No Keep size=No Brush color=255 255 255 Fill Color=Yes Brush style=1 Brush bitmap mode=12 Brush gradient mode=0 Brush gradient color=118 118 118 Brush background image= Custom shape= Custom text mode=0 Pen=1 0 0 0 255 Shadow color=192 192 192 Shadow=0 (8268, 11693) ((315,354), (433,354)) 1 15 1524449375 1538296407 -1 ((-38123,15297), (-26435,28269)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1524449886 -1 ((-23935,12010), (-11861,28282)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538296409 -1 ((-9361,18172), (2713,27845)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1524449886 -1 ((5214,16547), (17288,27869)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538296412 -1 ((19788,14872), (31862,27845)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538296204 -1 ((-37598,8498), (-29000,12497)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538296205 -1 ((-37674,3548), (-29076,7547)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538296308 -1 ((-37528,-6452), (-28929,-2453)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538296401 -1 ((-24280,-1775), (-11048,10373)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538296599 -1 ((-9182,625), (2892,10298)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538296612 -1 ((5017,-2538), (17091,10434)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538297772 -1 ((-39520,-17440), (-26288,-8592)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538297770 -1 ((-24744,-19106), (-10738,-8608)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538297380 -1 ((-9749,-20696), (3870,-8548)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1524449375 1538297383 -1 ((5261,-17623), (18494,-8774)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1538296083 1538296211 -1 ((-37675,-1349), (-29076,2650)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 1538296587 1538296608 -1 ((19570,-987), (32030,8687)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 config_id 1538296632 1538297253 ((-13950,-17175), (73200,19575)) 4130 1 0 7 16777215 16777215 新宋体,8,N 1538297386 1538297498 -1 ((19859,-18262), (33092,-8589)) 12615680 16570034 12632256 STRN 0 新宋体,8,N DISPNAME 0 新宋体,8,N OWNRDISPNAME 0 新宋体,8,N Columns 0 新宋体,8,N TablePkColumns 0 新宋体,8,U TableFkColumns 0 新宋体,8,N Keys 0 新宋体,8,N Indexes 0 新宋体,8,N Triggers 0 新宋体,8,N LABL 0 新宋体,8,N 6 65 16777215 BB11FFF9-9DBB-4648-87AA-9A50E1214549 sys_dept sys_dept 1524449375 Administrator 1538297518 admin 部门表 00C66282-419A-4915-8509-DFFFE6352DE8 dept_id dept_id 1524449375 Administrator 1524449375 Administrator 部门id int(11) 11 1 1 5B6FB0B1-5B1E-4E86-AF2A-72C49EBB315E parent_id parent_id 1524449375 Administrator 1524449375 Administrator 父部门id 0 int(11) 11 065E33A5-6AB5-44F1-8FEC-A72311EECD66 ancestors ancestors 1538295690 admin 1538295792 admin varchar(50) 50 EBB59EC8-AFD4-40E3-B811-DD5040728D91 dept_name dept_name 1524449375 Administrator 1524449375 Administrator 部门名称 '' varchar(30) 30 2F26C025-82B0-4AC5-AEE0-32BA07B7B529 order_num order_num 1524449375 Administrator 1524449375 Administrator 显示顺序 0 int(4) 4 CA504E09-528C-482E-A0C7-F86C559AA3A6 leader leader 1524449375 Administrator 1524449375 Administrator 负责人 '' varchar(20) 20 9CFC55C4-DF2B-4A90-A789-C3839FAA43A8 phone phone 1524449375 Administrator 1524449375 Administrator 联系电话 '' varchar(20) 20 1A9407E5-D74E-4CE9-9078-C4EC25393F7B email email 1524449375 Administrator 1524449375 Administrator 邮箱 '' varchar(20) 20 B6772812-4B69-4248-871D-FA1B4BA0E5F7 status status 1524449375 Administrator 1538295792 admin 部门状态:0正常,1停用 0 char(1) 1 6EBD2BFF-861E-4247-BAAB-B37CCBAF6F8D del_flag del_flag 1538295690 admin 1538295792 admin char(1) 1 2504A090-F6D6-493F-855E-5154E01AF0CA create_by create_by 1524449375 Administrator 1524449375 Administrator 创建者 '' varchar(64) 64 D866AE9E-E7FF-47B2-BF3D-9BC1605A2F39 create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp 7C6C9836-FC23-4492-8CF1-A4439E01B57C update_by update_by 1524449375 Administrator 1524449375 Administrator 更新者 '' varchar(64) 64 FCED770D-005C-4531-A9D7-D1FD0A054719 update_time update_time 1524449375 Administrator 1524449375 Administrator 更新时间 timestamp 15C1774B-9F17-48B6-A61F-728A25220B30 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator AA56FD91-4450-4282-8F31-AE302DF6AFEC sys_user sys_user 1524449375 Administrator 1538297540 admin 用户信息表 4A920BCE-4040-4F12-89D2-7DF345B90321 user_id user_id 1524449375 Administrator 1524449375 Administrator 用户ID int(11) 11 1 1 174E10B2-4A4D-40FF-80B8-B4D285561E42 dept_id dept_id 1524449375 Administrator 1538297552 admin 部门ID NULL int(11) 11 1D4908A9-5416-4252-BA09-FA122D0194C3 login_name login_name 1524449375 Administrator 1524449375 Administrator 登录账号 '' varchar(30) 30 2EF63346-9E82-4746-81B7-AB67D727446D user_name user_name 1524449375 Administrator 1524449375 Administrator 用户昵称 '' varchar(30) 30 477EA57C-0E0B-4596-9A85-EC91E72F5160 user_type user_type 1524449375 Administrator 1524449375 Administrator 类型:Y默认用户,N非默认用户 N char(1) 1 CD16FFF4-F214-473B-A9A8-FA30A3E357D1 email email 1524449375 Administrator 1524449375 Administrator 用户邮箱 '' varchar(100) 100 61603FA5-3EBC-4389-AED7-1B54D238A563 phonenumber phonenumber 1524449375 Administrator 1524449375 Administrator 手机号码 '' varchar(20) 20 65E9DE55-ED58-4BD9-B96C-7C081D1119B2 sex sex 1538295815 admin 1538295948 admin char(1) 1 E5E35061-221A-4BB9-AA22-3CF20F1FCCF6 avatar avatar 1538295815 admin 1538295948 admin varchar(100) 100 4ED1C2BF-B826-4A82-9464-EEBF271F4054 password password 1524449375 Administrator 1524449375 Administrator 密码 '' varchar(100) 100 53E6BB49-3435-46E0-832F-BCAFE1A021CB salt salt 1524449375 Administrator 1524449375 Administrator 盐加密 '' varchar(100) 100 245CAD53-B33B-4EED-8CFA-7AA10ED943B8 status status 1524449375 Administrator 1538297540 admin 账号状态:0正常,1禁用 0 char(1) 1 7F851464-6CC5-445B-9413-2A89B9CE90CB del_flag del_flag 1524449375 Administrator 1538295948 admin 拒绝登录描述 '' char(1) 1 3DC8EC79-D75A-4BF8-8FBC-152E938AC14F create_by create_by 1524449375 Administrator 1524449375 Administrator 创建者 '' varchar(64) 64 48C8C936-7A34-4A97-AACA-A6F07751FFAD create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp 6050B4F3-9B26-4B40-AB4C-BA483F179958 update_by update_by 1524449375 Administrator 1524449375 Administrator 更新者 '' varchar(64) 64 CD1E7E11-8EB6-4C9C-A69C-39CBCF10573E update_time update_time 1524449375 Administrator 1524449375 Administrator 更新时间 timestamp F9F55D4C-13E6-49A0-BFDB-E0AFE0FA5501 remark remark 1538295815 admin 1538295948 admin varchar(500) 500 2E35FD67-A7A7-4B10-85E4-85115AD0E143 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator 2711A520-532C-4F14-A034-BFF047C9CD6B sys_post sys_post 1524449375 Administrator 1538297571 admin 岗位信息表 FB04D29E-41F0-49A3-BFDB-58E222843F21 post_id post_id 1524449375 Administrator 1524449375 Administrator 岗位ID int(11) 11 1 1 50010C4E-4F59-47B9-8F08-05E8E071E8B1 post_code post_code 1524449375 Administrator 1524449375 Administrator 岗位编码 varchar(64) 64 1 0F929250-051E-4344-B22A-C30E071A543B post_name post_name 1524449375 Administrator 1524449375 Administrator 岗位名称 varchar(100) 100 1 2BC9005E-350F-46BE-98D6-9B13060F1B20 post_sort post_sort 1524449375 Administrator 1524449375 Administrator 显示顺序 int(4) 4 1 F6D7AD3E-5EA0-4759-B6BF-6334B7105B78 status status 1524449375 Administrator 1538297565 admin 状态(0正常 1停用) char(1) 1 1 CED01369-5063-479D-A444-32936369A486 create_by create_by 1524449375 Administrator 1524449375 Administrator 创建者 '' varchar(64) 64 A29528FF-A2B9-4149-B997-1B0204D42E40 create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp 6026A05D-0C1E-497E-8EAF-FDB704BE6A52 update_by update_by 1524449375 Administrator 1524449375 Administrator 更新者 '' varchar(64) 64 DF516F5F-CD82-4347-AC57-BDCB4E5DD75E update_time update_time 1524449375 Administrator 1524449375 Administrator 更新时间 timestamp 539CEC34-49F0-49A0-9B7C-B84655FD2233 remark remark 1524449375 Administrator 1524449375 Administrator 备注 '' varchar(500) 500 14E893B1-D0BA-46A7-A905-F0FFA089B65A Key_1 Key_1 1524449375 Administrator 1524449375 Administrator 11337551-BA45-43CD-9148-92BE60E2F8F5 sys_role sys_role 1524449375 Administrator 1538297608 admin 角色信息表 A420E2C9-8FE3-452A-9047-C7BEACE8490C role_id role_id 1524449375 Administrator 1524449375 Administrator 角色ID int(10) 10 1 1 9342763D-5B89-4440-965B-2B55DB4ACD86 role_name role_name 1524449375 Administrator 1524449375 Administrator 角色名称 varchar(30) 30 1 54480009-0C7E-40F2-AA76-CD914A6D66C5 role_key role_key 1524449375 Administrator 1524449375 Administrator 角色权限字符串 varchar(100) 100 1 E73F4D0E-12A0-42B5-B3CE-B573D499DD6C role_sort role_sort 1524449375 Administrator 1538296031 admin 显示顺序 int(10) 10 5F836F54-9EBD-4768-AA3C-F268F5FAFE8D data_scope data_scope 1538295973 admin 1538296031 admin char(1) 1 424ED799-E4C1-44AD-A172-C2B3C405E9C5 status status 1524449375 Administrator 1538297608 admin 角色状态:0正常,1禁用 0 char(1) 1 8E034C76-5966-4246-B81B-7B12F37D96A7 del_flag del_flag 1538295973 admin 1538296031 admin char(1) 1 214F6E1F-28B1-454B-ABF0-D1C43220129D create_by create_by 1524449375 Administrator 1524449375 Administrator 创建者 '' varchar(64) 64 1A6D5791-0353-4ABC-8BC2-921BB87A2E5A create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp D6394880-A49C-4B83-B43A-5FDBAA918AA3 update_by update_by 1524449375 Administrator 1524449375 Administrator 更新者 '' varchar(64) 64 34285DF5-8E36-452B-A3AA-9F4290C20F7E update_time update_time 1524449375 Administrator 1524449375 Administrator 更新时间 timestamp 2FAB98F7-68A2-460B-8A20-5D5DA73F5103 remark remark 1524449375 Administrator 1524449375 Administrator 备注 '' varchar(500) 500 4342E67F-D33C-435F-9865-973E053B6075 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator FBC2A590-443B-43C9-82D5-687B850C8B3D sys_menu sys_menu 1524449375 Administrator 1538297627 admin 菜单权限表 BB061292-3B99-432E-9B96-5362AAD918B9 menu_id menu_id 1524449375 Administrator 1524449375 Administrator 菜单ID int(11) 11 1 1 EA8422AB-37B1-4D60-A3C9-A4BF9039A9D4 menu_name menu_name 1524449375 Administrator 1524449375 Administrator 菜单名称 varchar(50) 50 1 E56E04A8-63F6-4271-92E3-974DC84DD536 parent_id parent_id 1524449375 Administrator 1524449375 Administrator 父菜单ID 0 int(11) 11 1809914E-6B09-4CD2-8916-E603D6717557 order_num order_num 1524449375 Administrator 1524449375 Administrator 显示顺序 NULL int(4) 4 FCB44D46-3C21-40CB-B942-57823E52E5B1 url url 1524449375 Administrator 1524449375 Administrator 请求地址 '' varchar(200) 200 667EE044-6805-4668-BAF4-E78B3052051F menu_type menu_type 1524449375 Administrator 1524449375 Administrator 类型:M目录,C菜单,F按钮 '' char(1) 1 F7658083-BCAB-46F7-AF31-8A4B1D8749EF visible visible 1524449375 Administrator 1538297627 admin 菜单状态:0显示,1隐藏 0 char(1) 1 528611C8-C319-430F-8F00-68FBA60F310B perms perms 1524449375 Administrator 1524449375 Administrator 权限标识 '' varchar(100) 100 38004CD7-8DD0-43F1-9E59-B50132CB6F1A icon icon 1524449375 Administrator 1524449375 Administrator 菜单图标 '' varchar(100) 100 6927665F-EC42-4E1F-A275-4B27F442B6B8 create_by create_by 1524449375 Administrator 1524449375 Administrator 创建者 '' varchar(64) 64 1A6A4D0F-0B0B-4522-B4DA-3F1D592CB889 create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp 605D7776-4820-4BA9-91E8-AD837B73AEFB update_by update_by 1524449375 Administrator 1524449375 Administrator 更新者 '' varchar(64) 64 4CFF26BB-8736-4864-855E-C7C1B133370B update_time update_time 1524449375 Administrator 1524449375 Administrator 更新时间 timestamp 67C6E46C-DF06-480A-BC74-E927406E5D26 remark remark 1524449375 Administrator 1524449375 Administrator 备注 '' varchar(500) 500 08EBE713-9E4D-4312-AA7D-2E4E439734E5 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator F8CB66D1-3632-4509-97C4-17016BE261FC sys_user_role sys_user_role 1524449375 Administrator 1538297676 admin 用户和角色关联表 73701F72-C45B-4CA0-8A62-632890E3DEF0 user_id user_id 1524449375 Administrator 1524449375 Administrator 用户ID int(11) 11 1 CABD458B-DA59-46A8-99C3-088AD8D34097 role_id role_id 1524449375 Administrator 1524449375 Administrator 角色ID int(11) 11 1 37C3213B-EF22-4CD4-A91F-9A9A2503FB2A Key_1 Key_1 1524449375 Administrator 1524449375 Administrator 9F8C6A9F-3221-410E-AEA4-D1A80026397E sys_role_menu sys_role_menu 1524449375 Administrator 1538297683 admin 角色和菜单关联表 D2E151A5-6156-46EF-844E-0ADC3070293B role_id role_id 1524449375 Administrator 1524449375 Administrator 角色ID int(11) 11 1 6B8C1E62-FD8B-4504-8FA0-F69917722FBD menu_id menu_id 1524449375 Administrator 1524449375 Administrator 菜单ID int(11) 11 1 2E72304F-91F0-4392-BAE8-BBF7A4346B7D Key_1 Key_1 1524449375 Administrator 1524449375 Administrator 726CB18E-7D5B-4E2E-9CF8-047AD5AF89E3 sys_user_post sys_user_post 1524449375 Administrator 1538297694 admin 用户与岗位关联表 E4A1CAB6-0F63-4917-ACEF-418DE7F894BA user_id user_id 1524449375 Administrator 1538296306 admin 用户ID int(11) 11 1 8E7188D5-B3A5-4F1D-B6CB-D77D652414DE post_id post_id 1524449375 Administrator 1538296306 admin 岗位ID int(11) 11 1 4091B7D3-2404-4C20-BBCD-B63E22A5E960 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator FE347A45-D8EC-423B-9B38-4D315A3ABE42 sys_oper_log sys_oper_log 1524449375 Administrator 1538297699 admin 操作日志记录 F5FC8AC1-7415-4A57-BA2C-EE2E7B9E1EFC oper_id oper_id 1524449375 Administrator 1524449375 Administrator 日志主键 int(11) 11 1 1 2103BC5C-E28D-4369-8369-E898B218587A title title 1524449375 Administrator 1524449375 Administrator 模块标题 '' varchar(50) 50 6816377B-3DB6-424A-99ED-1D20FEB30ED4 business_type business_type 1524449375 Administrator 1538296397 admin 功能请求 '' int(2) 2 9CA3B7C3-F52C-4E2E-893F-8E6EBA7B2667 method method 1524449375 Administrator 1524449375 Administrator 方法名称 '' varchar(100) 100 A5744803-C050-4108-9D15-7A0B95F03642 operator_type operator_type 1524449375 Administrator 1538296397 admin 来源渠道 '' int(1) 1 B0DF8235-6BC1-452C-8B30-A56F0430E4F5 oper_name oper_name 1524449375 Administrator 1538296397 admin 登录账号 '' varchar(50) 50 25315A12-4EB9-4B67-9E2C-9F40F8EF7FAB dept_name dept_name 1524449375 Administrator 1524449375 Administrator 部门名称 '' varchar(50) 50 7AF8602B-A1DA-4EA3-BFB2-7638F96A86C0 oper_url oper_url 1524449375 Administrator 1524449375 Administrator 请求URL '' varchar(255) 255 F2A56B63-7A56-43FA-8099-411F3578B30D oper_ip oper_ip 1524449375 Administrator 1524449375 Administrator 主机地址 '' varchar(30) 30 1EF1BAF6-F5C1-496C-98E0-8B10C37279A1 oper_param oper_param 1524449375 Administrator 1524449375 Administrator 请求参数 '' varchar(255) 255 AA3F3A4E-D375-4232-B152-01DCFB8F6B6D status status 1524449375 Administrator 1524449375 Administrator 操作状态 0正常 1异常 0 int(1) 1 29E44D4A-6AC7-4220-A502-4BFC8746397A error_msg error_msg 1524449375 Administrator 1524449375 Administrator 错误消息 '' varchar(2000) 2000 22343C35-D913-485B-862E-2CEF579AAF22 oper_time oper_time 1524449375 Administrator 1524449375 Administrator 操作时间 timestamp C0561C20-CC22-471B-A764-414C0D378FD6 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator AA2CFBA5-FA97-4AF1-92FE-645370B5848D sys_dict_type sys_dict_type 1524449375 Administrator 1538297703 admin 字典类型表 79CB7D43-B999-4D92-9477-D3AFEBD94248 dict_id dict_id 1524449375 Administrator 1524449375 Administrator 字典主键 int(11) 11 1 1 2490B755-3E0A-4935-97F0-2EFDF9A72D05 dict_name dict_name 1524449375 Administrator 1524449375 Administrator 字典名称 '' varchar(100) 100 7421238A-82DB-4992-AA28-41726AB6A5D6 dict_type dict_type 1524449375 Administrator 1524449375 Administrator 字典类型 '' varchar(100) 100 971D2FBD-1A24-4EE4-B943-9367609C7472 status status 1524449375 Administrator 1538296458 admin 状态(0正常 1禁用) 0 char(1) 1 B8876246-5BBA-4A03-86D7-98CA4EBEE342 create_by create_by 1524449375 Administrator 1524449375 Administrator 创建者 '' varchar(64) 64 5237CED2-0853-41DE-ACF4-BE442BC9E112 create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp 2CACFBC0-8349-4B3A-9183-208B18C9F56F update_by update_by 1524449375 Administrator 1524449375 Administrator 更新者 '' varchar(64) 64 ABEE7806-4F61-4B97-980C-CA081F61CA7C update_time update_time 1524449375 Administrator 1524449375 Administrator 更新时间 timestamp 3966B558-B911-45DE-86C6-57F3DB9267BA remark remark 1524449375 Administrator 1524449375 Administrator 备注 '' varchar(500) 500 AFC0A0ED-A469-40B2-A6C4-4616444830AA unique unique 1524449375 Administrator 1524449375 Administrator (dict_type) BAD40D8E-BC11-44F5-918E-B27CABBCB051 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator 493D6B25-21D0-45B1-BBA0-764B9C09B57D sys_dict_data sys_dict_data 1524449375 Administrator 1538297709 admin 字典数据表 CFDB23A8-AE38-4051-973A-2DABAC8283F9 dict_code dict_code 1524449375 Administrator 1524449375 Administrator 字典编码 int(11) 11 1 1 EAA405BD-12A8-472F-A42D-CDA6A82E291A dict_sort dict_sort 1524449375 Administrator 1524449375 Administrator 字典排序 0 int(4) 4 F13017F5-2AA0-4DE9-9DC2-A9A3D73A98E6 dict_label dict_label 1524449375 Administrator 1524449375 Administrator 字典标签 '' varchar(100) 100 EEEC4136-823D-4892-9BB9-BB0B4ADD83E3 dict_value dict_value 1524449375 Administrator 1524449375 Administrator 字典键值 '' varchar(100) 100 ADF5A383-D055-40BE-BBFC-06E2B93D4E6A dict_type dict_type 1524449375 Administrator 1524449375 Administrator 字典类型 '' varchar(100) 100 A0B2DDF2-251D-4701-9B00-6893C74CC449 css_class css_class 1538296497 admin 1538296556 admin varchar(100) 100 3CBFBA8E-7609-458D-9E53-A825C3F307A2 list_class list_class 1538296497 admin 1538296556 admin varchar(100) 100 BA974839-DEE0-4684-BBEF-6D7776C34354 is_default is_default 1538296497 admin 1538296556 admin char(1) 1 1676CDF5-01CA-4749-BA1D-6E5399257BD0 status status 1524449375 Administrator 1524449375 Administrator 状态(0正常 1禁用) 0 int(1) 1 8798B094-1AAF-4A23-B2F1-4C19DACF1AA3 create_by create_by 1524449375 Administrator 1524449375 Administrator 创建者 '' varchar(64) 64 D1CB9293-D762-403C-85CB-4B974ACF7328 create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp 5A34AF87-B25E-4349-9713-69DC50F6F5F2 update_by update_by 1524449375 Administrator 1524449375 Administrator 更新者 '' varchar(64) 64 3204FBAC-1F61-4571-ADC4-BF1BE9CED85A update_time update_time 1524449375 Administrator 1524449375 Administrator 更新时间 timestamp B7DE1842-809C-4401-9C80-C9A37DF9B053 remark remark 1524449375 Administrator 1524449375 Administrator 备注 '' varchar(500) 500 2809F417-7FA5-48DA-B613-662C7C28061E Key_1 Key_1 1524449375 Administrator 1524449375 Administrator 0A7C2F56-6E3B-4E70-A549-0EC60779D180 sys_logininfor sys_logininfor 1524449375 Administrator 1538297756 admin 系统访问记录 5CB5D942-D52B-487D-BC86-476481B0FB8D info_id info_id 1524449375 Administrator 1524449375 Administrator 访问ID int(11) 11 1 1 A1C66DBC-9DB7-428B-9275-3D014B6CE388 login_name login_name 1524449375 Administrator 1524449375 Administrator 登录账号 '' varchar(50) 50 8E0F50A6-F98D-48B0-8D9D-78F3A76ED171 ipaddr ipaddr 1524449375 Administrator 1524449375 Administrator 登录IP地址 '' varchar(50) 50 91B70723-1A7E-4277-A100-63B775A504B3 login_location login_location 1538297350 admin 1538297369 admin varchar(255) 255 AA04F533-A044-428B-80F8-515B6BB1A302 browser browser 1524449375 Administrator 1524449375 Administrator 浏览器类型 '' varchar(50) 50 D37570E9-9EEE-4349-B875-494A5415C736 os os 1524449375 Administrator 1524449375 Administrator 操作系统 '' varchar(50) 50 CF10A80C-123E-42F3-A2DD-1B770E5F9D86 status status 1524449375 Administrator 1524449375 Administrator 登录状态 0成功 1失败 0 int(1) 1 9113784E-932A-4FAF-82CB-A75B8C827309 msg msg 1524449375 Administrator 1524449375 Administrator 提示消息 '' varchar(255) 255 BCA519C6-19C9-45DF-A0B5-F88E9E6D3557 login_time login_time 1524449375 Administrator 1524449375 Administrator 访问时间 timestamp C14E656C-0645-49EB-8B42-AD82232E0416 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator 4DCA223F-E98B-4D8B-A71C-CFB438C15488 sys_user_online sys_user_online 1524449375 Administrator 1538297754 admin 在线用户记录 7FCC57CE-47DD-4948-B949-10401B2FC7B1 sessionId sessionId 1524449375 Administrator 1524449375 Administrator 用户会话id '' varchar(50) 50 1 FDE5B59D-8CF7-4AAE-987F-3FF2AEBE22CB login_name login_name 1524449375 Administrator 1524449375 Administrator 登录账号 '' varchar(50) 50 AB65FF92-33A0-42C8-8B3F-454A1FAD5615 dept_name dept_name 1524449375 Administrator 1524449375 Administrator 部门名称 '' varchar(50) 50 C4DAF2D0-9CDC-476B-A011-FF5D302371EB ipaddr ipaddr 1524449375 Administrator 1524449375 Administrator 登录IP地址 '' varchar(50) 50 C8243FB0-425B-4A74-9ADA-C93B15E713EA login_location login_location 1538297178 admin 1538297216 admin varchar(255) 255 89EC40B0-0C22-4811-90BB-BEA385ACDF20 browser browser 1524449375 Administrator 1524449375 Administrator 浏览器类型 '' varchar(50) 50 AC455631-CFE0-45BB-A0C5-788D695E4B6C os os 1524449375 Administrator 1524449375 Administrator 操作系统 '' varchar(50) 50 5C56E3C9-4591-4762-89E1-C9BBFECB5F11 status status 1524449375 Administrator 1524449375 Administrator 在线状态on_line在线off_line离线 '' varchar(10) 10 0CAF2F1F-459F-4F78-9075-D95F924A4FF7 start_timestamp start_timestamp 1524449375 Administrator 1524449375 Administrator session创建时间 timestamp 6AE6BDED-823E-4455-9A9F-338EC6F7BDB9 last_access_time last_access_time 1524449375 Administrator 1524449375 Administrator session最后访问时间 timestamp CE390924-4628-421C-979F-002C2952E99E expire_time expire_time 1524449375 Administrator 1524449375 Administrator 超时时间,单位为分钟 0 int(5) 5 365CC94D-6124-42C7-96BD-376B84B709F7 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator AFCBF4DB-07EC-42D1-ACA7-56B5038F5AC5 sys_job sys_job 1524449375 Administrator 1538297732 admin 定时任务调度表 1658CED4-3885-4094-AB70-F35408EBCD5E job_id job_id 1524449375 Administrator 1524449375 Administrator 任务ID int(11) 11 1 1 731E7147-E3A4-4D93-8C7C-BB1C6D94DB9E job_name job_name 1524449375 Administrator 1524449375 Administrator 任务名称 '' varchar(64) 64 1 C64B3655-C240-44F0-83B4-F42FB76C8BEA job_group job_group 1524449375 Administrator 1524449375 Administrator 任务组名 '' varchar(64) 64 1 9F7E735D-B823-4ADA-BA3D-8FFFFEC92F5C method_name method_name 1524449375 Administrator 1524449375 Administrator 任务方法 '' varchar(500) 500 28EEE4F4-E8E7-4052-8F10-88D6C74C595D method_params method_params 1524449375 Administrator 1538297298 admin 方法参数 '' varchar(200) 200 C8986FAD-E2E7-4364-9E8B-B75366B9A4ED cron_expression cron_expression 1524449375 Administrator 1524449375 Administrator cron执行表达式 '' varchar(255) 255 FD188167-AC02-4161-BE89-D63E61412313 misfire_policy misfire_policy 1538297273 admin 1538297298 admin varchar(20) 20 2D4B6C8F-EEE8-4474-9D20-8206A7E80362 status status 1524449375 Administrator 1524449375 Administrator 状态(0正常 1暂停) 0 int(1) 1 CA78AC7F-19E7-47BC-BF7B-9F31EFB02702 create_by create_by 1524449375 Administrator 1524449375 Administrator 创建者 '' varchar(64) 64 B8F807AE-9F19-4FCA-BA98-7BF71DD0CA02 create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp 3FBB42FA-ED0F-4D7C-99D0-5F7AF7B0F1DD update_by update_by 1524449375 Administrator 1524449375 Administrator 更新者 '' varchar(64) 64 1C5863D2-A8B9-43DB-AA06-F8BE3E01093B update_time update_time 1524449375 Administrator 1524449375 Administrator 更新时间 timestamp 889C3FF9-BB1E-4EB1-AFE9-1D1155984915 remark remark 1524449375 Administrator 1524449375 Administrator 备注信息 '' varchar(500) 500 38106F1A-4FFB-4EC0-B979-55BD6C6C6FF7 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator CF7C8958-5494-48C6-BE05-83F2CF8C7513 sys_job_log sys_job_log 1524449375 Administrator 1538297742 admin 定时任务调度日志表 308F32A1-A8EC-4002-9993-DF9234A303B7 job_log_id job_log_id 1524449375 Administrator 1524449375 Administrator 任务日志ID int(11) 11 1 1 F4D55B65-BB6B-4182-A6D6-F9CAABC19110 job_name job_name 1524449375 Administrator 1524449375 Administrator 任务名称 varchar(64) 64 1 8AF383A0-01C0-4947-8384-FF0F13AC00AE job_group job_group 1524449375 Administrator 1524449375 Administrator 任务组名 varchar(64) 64 1 96582B76-F1E9-4473-BA51-01B87B5F459E method_name method_name 1524449375 Administrator 1524449375 Administrator 任务方法 varchar(500) 500 2AB02ABA-02E3-4F72-95BA-4261A7F5729A method_params method_params 1524449375 Administrator 1538297325 admin 方法参数 '' varchar(200) 200 8EB39444-CBFF-43AA-AA37-49217EF545B6 job_message job_message 1524449375 Administrator 1524449375 Administrator 日志信息 varchar(500) 500 18CD263C-0F57-4EDF-999E-1B5A7EE2BFF9 is_exception is_exception 1524449375 Administrator 1538297325 admin 是否异常 0 char(1) 1 634ECD78-2251-43EB-B6CF-DF7FA9DA4354 exception_info exception_info 1524449375 Administrator 1524449375 Administrator 异常信息 text 4EC075CC-507B-43D7-860F-34DAAEB1DBBF create_time create_time 1524449375 Administrator 1524449375 Administrator 创建时间 timestamp A87DCE10-894A-4CF7-B39C-AF18202C7F86 Key_1 Key_1 1524449375 Administrator 1524449375 Administrator FD6284E8-B6D4-43AF-A038-9C97DCD403DC sys_role_dept sys_role_dept 1538296083 admin 1538297689 admin 角色和部门关联表 2BC66204-4193-42E6-BB7B-7AD57C9E5BEF role_id role_id 1538296083 admin 1538296150 admin 用户ID int(11) 11 1 A32BC025-6437-41AB-BAA4-3A150E406781 dept_id dept_id 1538296083 admin 1538296150 admin 岗位ID int(11) 11 1 315FFED5-B0A0-4649-8255-2283896340C9 Key_1 Key_1 1538296083 admin 1538296083 admin 45EB995C-F5F6-4818-AEB1-2038DEBA9CEE sys_config sys_config 1538296587 admin 1538297714 admin 参数配置表 667C4616-146B-475C-8111-4720375D762C config_id config_id 1538296587 admin 1538296691 admin 字典编码 int(5) 5 1 1 EA798E0B-0CBE-4897-B0AF-1F2D3CD6DEF4 config_name config_name 1538296587 admin 1538296691 admin 字典排序 0 varchar(100) 100 A9A2A6E0-C914-4516-AE4C-F33CE71B92E8 config_key config_key 1538296587 admin 1538296691 admin 字典标签 '' varchar(100) 100 24CCA897-8671-402E-8229-9ED0C80C176A config_value config_value 1538296587 admin 1538296691 admin 字典键值 '' varchar(100) 100 B4E76B1D-BFAF-42F3-8CCA-8B5A8CC7CBFF config_type config_type 1538296587 admin 1538296691 admin 字典类型 '' char(1) 1 A6AC1891-F5C4-45B3-8CAB-8F4CE8B8BF08 create_by create_by 1538296587 admin 1538296587 admin 创建者 '' varchar(64) 64 CC1E0367-A079-49A0-8F0A-FE5F7B3EB6EA create_time create_time 1538296587 admin 1538296587 admin 创建时间 timestamp 081CD54E-AE38-4696-A326-F829B8EA5737 update_by update_by 1538296587 admin 1538296587 admin 更新者 '' varchar(64) 64 E2118ECE-8F52-4FBA-B18A-F30FFB2BDD20 update_time update_time 1538296587 admin 1538296587 admin 更新时间 timestamp 55A16121-8932-465E-8427-EBDA39B2B900 remark remark 1538296587 admin 1538296587 admin 备注 '' varchar(500) 500 0F331278-2804-496A-A87B-B0944C80FB82 Key_1 Key_1 1538296587 admin 1538296587 admin F33DE1D6-C12D-43DB-A502-83BD1615F081 sys_notice sys_notice 1538297386 admin 1538297746 admin 通知公告表 FF4A9744-D7CA-450E-8AD7-B3E7E90075CE notice_id notice_id 1538297386 admin 1538297496 admin 任务日志ID int(4) 4 1 1 E2B08825-4C94-4209-80B2-21A7AD8CBF2D notice_title notice_title 1538297386 admin 1538297496 admin 任务名称 varchar(50) 50 1 04414862-9ABC-4431-B1B7-B44ECC08CB6E notice_type notice_type 1538297386 admin 1538297496 admin 任务组名 char(2) 2 1 E829DAD1-E3F9-4AED-A3DE-59CE4340333E notice_content notice_content 1538297386 admin 1538297496 admin 任务方法 varchar(500) 500 2EABC8DB-6700-4717-89A3-31461C4CB2D5 status status 1538297386 admin 1538297496 admin 方法参数 '' char(1) 1 448D3EB6-DE24-4BE3-9C29-1FC3C71B0E8D create_by create_by 1538297386 admin 1538297496 admin 日志信息 varchar(64) 64 770ED87D-D4D7-499C-A266-7A54051B1A84 create_time1 create_time1 1538297386 admin 1538297496 admin 是否异常 0 datetime 12DDF399-7CCB-4117-8B05-6AA9BEE845E5 update_by update_by 1538297386 admin 1538297496 admin 异常信息 varchar(64) 64 FE101CE4-9B66-4097-944D-36B01A9E2219 update_time1 update_time1 1538297400 admin 1538297496 admin datetime D5F1728C-01D0-4C00-9AD6-AAA14228104B remark remark 1538297386 admin 1538297496 admin 创建时间 varchar(255) 255 43C7AC1D-CE7A-4B55-A474-8CB2376D446F Key_1 Key_1 1538297386 admin 1538297386 admin F2EBEA5B-F352-45CB-B349-39158064CEE8 PUBLIC PUBLIC 1524449325 Administrator 1524449325 Administrator 41740AEF-D7FB-4738-ABDF-47C3287A6AF6 MySQL 5.0 MYSQL50 1524449337 Administrator 1538295558 admin file:///%_DBMS%/mysql50.xdb F4F16ECD-F2F1-4006-AF6F-638D5C65F35E 4BA9F647-DAB1-11D1-9944-006097355D9B ================================================ FILE: sql/ry_20260319.sql ================================================ -- ---------------------------- -- 1、部门表 -- ---------------------------- drop table if exists sys_dept; create table sys_dept ( dept_id bigint(20) not null auto_increment comment '部门id', parent_id bigint(20) default 0 comment '父部门id', ancestors varchar(50) default '' comment '祖级列表', dept_name varchar(30) default '' comment '部门名称', order_num int(4) default 0 comment '显示顺序', leader varchar(20) default null comment '负责人', phone varchar(11) default null comment '联系电话', email varchar(50) default null comment '邮箱', status char(1) default '0' comment '部门状态(0正常 1停用)', del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', primary key (dept_id) ) engine=innodb auto_increment=200 comment = '部门表'; -- ---------------------------- -- 初始化-部门表数据 -- ---------------------------- insert into sys_dept values(100, 0, '0', '若依科技', 0, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null); -- ---------------------------- -- 2、用户信息表 -- ---------------------------- drop table if exists sys_user; create table sys_user ( user_id bigint(20) not null auto_increment comment '用户ID', dept_id bigint(20) default null comment '部门ID', login_name varchar(30) not null comment '登录账号', user_name varchar(30) default '' comment '用户昵称', user_type varchar(2) default '00' comment '用户类型(00系统用户 01注册用户)', email varchar(50) default '' comment '用户邮箱', phonenumber varchar(11) default '' comment '手机号码', sex char(1) default '0' comment '用户性别(0男 1女 2未知)', avatar varchar(100) default '' comment '头像路径', password varchar(50) default '' comment '密码', salt varchar(20) default '' comment '盐加密', status char(1) default '0' comment '账号状态(0正常 1停用)', del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', login_ip varchar(128) default '' comment '最后登录IP', login_date datetime comment '最后登录时间', pwd_update_date datetime comment '密码最后更新时间', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (user_id) ) engine=innodb auto_increment=100 comment = '用户信息表'; -- ---------------------------- -- 初始化-用户信息表数据 -- ---------------------------- insert into sys_user values(1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '29c67a30398638269fe600f73a054934', '111111', '0', '0', '127.0.0.1', null, null, 'admin', sysdate(), '', null, '管理员'); insert into sys_user values(2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '8e6d98b90472783cc73c17047ddccf36', '222222', '0', '0', '127.0.0.1', null, null, 'admin', sysdate(), '', null, '测试员'); -- ---------------------------- -- 3、岗位信息表 -- ---------------------------- drop table if exists sys_post; create table sys_post ( post_id bigint(20) not null auto_increment comment '岗位ID', post_code varchar(64) not null comment '岗位编码', post_name varchar(50) not null comment '岗位名称', post_sort int(4) not null comment '显示顺序', status char(1) not null comment '状态(0正常 1停用)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (post_id) ) engine=innodb comment = '岗位信息表'; -- ---------------------------- -- 初始化-岗位信息表数据 -- ---------------------------- insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, ''); insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, ''); insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, ''); insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, ''); -- ---------------------------- -- 4、角色信息表 -- ---------------------------- drop table if exists sys_role; create table sys_role ( role_id bigint(20) not null auto_increment comment '角色ID', role_name varchar(30) not null comment '角色名称', role_key varchar(100) not null comment '角色权限字符串', role_sort int(4) not null comment '显示顺序', data_scope char(1) default '1' comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', status char(1) not null comment '角色状态(0正常 1停用)', del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (role_id) ) engine=innodb auto_increment=100 comment = '角色信息表'; -- ---------------------------- -- 初始化-角色信息表数据 -- ---------------------------- insert into sys_role values('1', '超级管理员', 'admin', 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员'); insert into sys_role values('2', '普通角色', 'common', 2, 2, '0', '0', 'admin', sysdate(), '', null, '普通角色'); -- ---------------------------- -- 5、菜单权限表 -- ---------------------------- drop table if exists sys_menu; create table sys_menu ( menu_id bigint(20) not null auto_increment comment '菜单ID', menu_name varchar(50) not null comment '菜单名称', parent_id bigint(20) default 0 comment '父菜单ID', order_num int(4) default 0 comment '显示顺序', url varchar(200) default '#' comment '请求地址', target varchar(20) default '' comment '打开方式(menuItem页签 menuBlank新窗口)', menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)', visible char(1) default 0 comment '菜单状态(0显示 1隐藏)', is_refresh char(1) default 1 comment '是否刷新(0刷新 1不刷新)', perms varchar(100) default null comment '权限标识', icon varchar(100) default '#' comment '菜单图标', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default '' comment '备注', primary key (menu_id) ) engine=innodb auto_increment=2000 comment = '菜单权限表'; -- ---------------------------- -- 初始化-菜单信息表数据 -- ---------------------------- -- 一级菜单 insert into sys_menu values('1', '系统管理', '0', '1', '#', '', 'M', '0', '1', '', 'fa fa-gear', 'admin', sysdate(), '', null, '系统管理目录'); insert into sys_menu values('2', '系统监控', '0', '2', '#', '', 'M', '0', '1', '', 'fa fa-video-camera', 'admin', sysdate(), '', null, '系统监控目录'); insert into sys_menu values('3', '系统工具', '0', '3', '#', '', 'M', '0', '1', '', 'fa fa-bars', 'admin', sysdate(), '', null, '系统工具目录'); insert into sys_menu values('4', '若依官网', '0', '4', 'http://ruoyi.vip', 'menuBlank', 'C', '0', '1', '', 'fa fa-location-arrow', 'admin', sysdate(), '', null, '若依官网地址'); -- 二级菜单 insert into sys_menu values('100', '用户管理', '1', '1', '/system/user', '', 'C', '0', '1', 'system:user:view', 'fa fa-user-o', 'admin', sysdate(), '', null, '用户管理菜单'); insert into sys_menu values('101', '角色管理', '1', '2', '/system/role', '', 'C', '0', '1', 'system:role:view', 'fa fa-user-secret', 'admin', sysdate(), '', null, '角色管理菜单'); insert into sys_menu values('102', '菜单管理', '1', '3', '/system/menu', '', 'C', '0', '1', 'system:menu:view', 'fa fa-th-list', 'admin', sysdate(), '', null, '菜单管理菜单'); insert into sys_menu values('103', '部门管理', '1', '4', '/system/dept', '', 'C', '0', '1', 'system:dept:view', 'fa fa-outdent', 'admin', sysdate(), '', null, '部门管理菜单'); insert into sys_menu values('104', '岗位管理', '1', '5', '/system/post', '', 'C', '0', '1', 'system:post:view', 'fa fa-address-card-o', 'admin', sysdate(), '', null, '岗位管理菜单'); insert into sys_menu values('105', '字典管理', '1', '6', '/system/dict', '', 'C', '0', '1', 'system:dict:view', 'fa fa-bookmark-o', 'admin', sysdate(), '', null, '字典管理菜单'); insert into sys_menu values('106', '参数设置', '1', '7', '/system/config', '', 'C', '0', '1', 'system:config:view', 'fa fa-sun-o', 'admin', sysdate(), '', null, '参数设置菜单'); insert into sys_menu values('107', '通知公告', '1', '8', '/system/notice', '', 'C', '0', '1', 'system:notice:view', 'fa fa-bullhorn', 'admin', sysdate(), '', null, '通知公告菜单'); insert into sys_menu values('108', '日志管理', '1', '9', '#', '', 'M', '0', '1', '', 'fa fa-pencil-square-o', 'admin', sysdate(), '', null, '日志管理菜单'); insert into sys_menu values('109', '在线用户', '2', '1', '/monitor/online', '', 'C', '0', '1', 'monitor:online:view', 'fa fa-user-circle', 'admin', sysdate(), '', null, '在线用户菜单'); insert into sys_menu values('110', '定时任务', '2', '2', '/monitor/job', '', 'C', '0', '1', 'monitor:job:view', 'fa fa-tasks', 'admin', sysdate(), '', null, '定时任务菜单'); insert into sys_menu values('111', '数据监控', '2', '3', '/monitor/data', '', 'C', '0', '1', 'monitor:data:view', 'fa fa-bug', 'admin', sysdate(), '', null, '数据监控菜单'); insert into sys_menu values('112', '服务监控', '2', '4', '/monitor/server', '', 'C', '0', '1', 'monitor:server:view', 'fa fa-server', 'admin', sysdate(), '', null, '服务监控菜单'); insert into sys_menu values('113', '缓存监控', '2', '5', '/monitor/cache', '', 'C', '0', '1', 'monitor:cache:view', 'fa fa-cube', 'admin', sysdate(), '', null, '缓存监控菜单'); insert into sys_menu values('114', '表单构建', '3', '1', '/tool/build', '', 'C', '0', '1', 'tool:build:view', 'fa fa-wpforms', 'admin', sysdate(), '', null, '表单构建菜单'); insert into sys_menu values('115', '代码生成', '3', '2', '/tool/gen', '', 'C', '0', '1', 'tool:gen:view', 'fa fa-code', 'admin', sysdate(), '', null, '代码生成菜单'); insert into sys_menu values('116', '系统接口', '3', '3', '/tool/swagger', '', 'C', '0', '1', 'tool:swagger:view', 'fa fa-gg', 'admin', sysdate(), '', null, '系统接口菜单'); -- 三级菜单 insert into sys_menu values('500', '操作日志', '108', '1', '/monitor/operlog', '', 'C', '0', '1', 'monitor:operlog:view', 'fa fa-address-book', 'admin', sysdate(), '', null, '操作日志菜单'); insert into sys_menu values('501', '登录日志', '108', '2', '/monitor/logininfor', '', 'C', '0', '1', 'monitor:logininfor:view', 'fa fa-file-image-o', 'admin', sysdate(), '', null, '登录日志菜单'); -- 用户管理按钮 insert into sys_menu values('1000', '用户查询', '100', '1', '#', '', 'F', '0', '1', 'system:user:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1001', '用户新增', '100', '2', '#', '', 'F', '0', '1', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1002', '用户修改', '100', '3', '#', '', 'F', '0', '1', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1003', '用户删除', '100', '4', '#', '', 'F', '0', '1', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1004', '用户导出', '100', '5', '#', '', 'F', '0', '1', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1005', '用户导入', '100', '6', '#', '', 'F', '0', '1', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1006', '重置密码', '100', '7', '#', '', 'F', '0', '1', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, ''); -- 角色管理按钮 insert into sys_menu values('1007', '角色查询', '101', '1', '#', '', 'F', '0', '1', 'system:role:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1008', '角色新增', '101', '2', '#', '', 'F', '0', '1', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1009', '角色修改', '101', '3', '#', '', 'F', '0', '1', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1010', '角色删除', '101', '4', '#', '', 'F', '0', '1', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1011', '角色导出', '101', '5', '#', '', 'F', '0', '1', 'system:role:export', '#', 'admin', sysdate(), '', null, ''); -- 菜单管理按钮 insert into sys_menu values('1012', '菜单查询', '102', '1', '#', '', 'F', '0', '1', 'system:menu:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1013', '菜单新增', '102', '2', '#', '', 'F', '0', '1', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1014', '菜单修改', '102', '3', '#', '', 'F', '0', '1', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1015', '菜单删除', '102', '4', '#', '', 'F', '0', '1', 'system:menu:remove', '#', 'admin', sysdate(), '', null, ''); -- 部门管理按钮 insert into sys_menu values('1016', '部门查询', '103', '1', '#', '', 'F', '0', '1', 'system:dept:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1017', '部门新增', '103', '2', '#', '', 'F', '0', '1', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1018', '部门修改', '103', '3', '#', '', 'F', '0', '1', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1019', '部门删除', '103', '4', '#', '', 'F', '0', '1', 'system:dept:remove', '#', 'admin', sysdate(), '', null, ''); -- 岗位管理按钮 insert into sys_menu values('1020', '岗位查询', '104', '1', '#', '', 'F', '0', '1', 'system:post:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1021', '岗位新增', '104', '2', '#', '', 'F', '0', '1', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1022', '岗位修改', '104', '3', '#', '', 'F', '0', '1', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1023', '岗位删除', '104', '4', '#', '', 'F', '0', '1', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1024', '岗位导出', '104', '5', '#', '', 'F', '0', '1', 'system:post:export', '#', 'admin', sysdate(), '', null, ''); -- 字典管理按钮 insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', 'F', '0', '1', 'system:dict:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', 'F', '0', '1', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', 'F', '0', '1', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', 'F', '0', '1', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', 'F', '0', '1', 'system:dict:export', '#', 'admin', sysdate(), '', null, ''); -- 参数设置按钮 insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', 'F', '0', '1', 'system:config:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', 'F', '0', '1', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', 'F', '0', '1', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', 'F', '0', '1', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', 'F', '0', '1', 'system:config:export', '#', 'admin', sysdate(), '', null, ''); -- 通知公告按钮 insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', 'F', '0', '1', 'system:notice:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', 'F', '0', '1', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', 'F', '0', '1', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', 'F', '0', '1', 'system:notice:remove', '#', 'admin', sysdate(), '', null, ''); -- 操作日志按钮 insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', 'F', '0', '1', 'monitor:operlog:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', 'F', '0', '1', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1041', '详细信息', '500', '3', '#', '', 'F', '0', '1', 'monitor:operlog:detail', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1042', '日志导出', '500', '4', '#', '', 'F', '0', '1', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, ''); -- 登录日志按钮 insert into sys_menu values('1043', '登录查询', '501', '1', '#', '', 'F', '0', '1', 'monitor:logininfor:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1044', '登录删除', '501', '2', '#', '', 'F', '0', '1', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1045', '日志导出', '501', '3', '#', '', 'F', '0', '1', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1046', '账户解锁', '501', '4', '#', '', 'F', '0', '1', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, ''); -- 在线用户按钮 insert into sys_menu values('1047', '在线查询', '109', '1', '#', '', 'F', '0', '1', 'monitor:online:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1048', '批量强退', '109', '2', '#', '', 'F', '0', '1', 'monitor:online:batchForceLogout', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1049', '单条强退', '109', '3', '#', '', 'F', '0', '1', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, ''); -- 定时任务按钮 insert into sys_menu values('1050', '任务查询', '110', '1', '#', '', 'F', '0', '1', 'monitor:job:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1051', '任务新增', '110', '2', '#', '', 'F', '0', '1', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1052', '任务修改', '110', '3', '#', '', 'F', '0', '1', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1053', '任务删除', '110', '4', '#', '', 'F', '0', '1', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1054', '状态修改', '110', '5', '#', '', 'F', '0', '1', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1055', '任务详细', '110', '6', '#', '', 'F', '0', '1', 'monitor:job:detail', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1056', '任务导出', '110', '7', '#', '', 'F', '0', '1', 'monitor:job:export', '#', 'admin', sysdate(), '', null, ''); -- 代码生成按钮 insert into sys_menu values('1057', '生成查询', '115', '1', '#', '', 'F', '0', '1', 'tool:gen:list', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1058', '生成修改', '115', '2', '#', '', 'F', '0', '1', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1059', '生成删除', '115', '3', '#', '', 'F', '0', '1', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1060', '预览代码', '115', '4', '#', '', 'F', '0', '1', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); insert into sys_menu values('1061', '生成代码', '115', '5', '#', '', 'F', '0', '1', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); -- ---------------------------- -- 6、用户和角色关联表 用户N-1角色 -- ---------------------------- drop table if exists sys_user_role; create table sys_user_role ( user_id bigint(20) not null comment '用户ID', role_id bigint(20) not null comment '角色ID', primary key(user_id, role_id) ) engine=innodb comment = '用户和角色关联表'; -- ---------------------------- -- 初始化-用户和角色关联表数据 -- ---------------------------- insert into sys_user_role values ('1', '1'); insert into sys_user_role values ('2', '2'); -- ---------------------------- -- 7、角色和菜单关联表 角色1-N菜单 -- ---------------------------- drop table if exists sys_role_menu; create table sys_role_menu ( role_id bigint(20) not null comment '角色ID', menu_id bigint(20) not null comment '菜单ID', primary key(role_id, menu_id) ) engine=innodb comment = '角色和菜单关联表'; -- ---------------------------- -- 初始化-角色和菜单关联表数据 -- ---------------------------- insert into sys_role_menu values ('2', '1'); insert into sys_role_menu values ('2', '2'); insert into sys_role_menu values ('2', '3'); insert into sys_role_menu values ('2', '4'); insert into sys_role_menu values ('2', '100'); insert into sys_role_menu values ('2', '101'); insert into sys_role_menu values ('2', '102'); insert into sys_role_menu values ('2', '103'); insert into sys_role_menu values ('2', '104'); insert into sys_role_menu values ('2', '105'); insert into sys_role_menu values ('2', '106'); insert into sys_role_menu values ('2', '107'); insert into sys_role_menu values ('2', '108'); insert into sys_role_menu values ('2', '109'); insert into sys_role_menu values ('2', '110'); insert into sys_role_menu values ('2', '111'); insert into sys_role_menu values ('2', '112'); insert into sys_role_menu values ('2', '113'); insert into sys_role_menu values ('2', '114'); insert into sys_role_menu values ('2', '115'); insert into sys_role_menu values ('2', '116'); insert into sys_role_menu values ('2', '500'); insert into sys_role_menu values ('2', '501'); insert into sys_role_menu values ('2', '1000'); insert into sys_role_menu values ('2', '1001'); insert into sys_role_menu values ('2', '1002'); insert into sys_role_menu values ('2', '1003'); insert into sys_role_menu values ('2', '1004'); insert into sys_role_menu values ('2', '1005'); insert into sys_role_menu values ('2', '1006'); insert into sys_role_menu values ('2', '1007'); insert into sys_role_menu values ('2', '1008'); insert into sys_role_menu values ('2', '1009'); insert into sys_role_menu values ('2', '1010'); insert into sys_role_menu values ('2', '1011'); insert into sys_role_menu values ('2', '1012'); insert into sys_role_menu values ('2', '1013'); insert into sys_role_menu values ('2', '1014'); insert into sys_role_menu values ('2', '1015'); insert into sys_role_menu values ('2', '1016'); insert into sys_role_menu values ('2', '1017'); insert into sys_role_menu values ('2', '1018'); insert into sys_role_menu values ('2', '1019'); insert into sys_role_menu values ('2', '1020'); insert into sys_role_menu values ('2', '1021'); insert into sys_role_menu values ('2', '1022'); insert into sys_role_menu values ('2', '1023'); insert into sys_role_menu values ('2', '1024'); insert into sys_role_menu values ('2', '1025'); insert into sys_role_menu values ('2', '1026'); insert into sys_role_menu values ('2', '1027'); insert into sys_role_menu values ('2', '1028'); insert into sys_role_menu values ('2', '1029'); insert into sys_role_menu values ('2', '1030'); insert into sys_role_menu values ('2', '1031'); insert into sys_role_menu values ('2', '1032'); insert into sys_role_menu values ('2', '1033'); insert into sys_role_menu values ('2', '1034'); insert into sys_role_menu values ('2', '1035'); insert into sys_role_menu values ('2', '1036'); insert into sys_role_menu values ('2', '1037'); insert into sys_role_menu values ('2', '1038'); insert into sys_role_menu values ('2', '1039'); insert into sys_role_menu values ('2', '1040'); insert into sys_role_menu values ('2', '1041'); insert into sys_role_menu values ('2', '1042'); insert into sys_role_menu values ('2', '1043'); insert into sys_role_menu values ('2', '1044'); insert into sys_role_menu values ('2', '1045'); insert into sys_role_menu values ('2', '1046'); insert into sys_role_menu values ('2', '1047'); insert into sys_role_menu values ('2', '1048'); insert into sys_role_menu values ('2', '1049'); insert into sys_role_menu values ('2', '1050'); insert into sys_role_menu values ('2', '1051'); insert into sys_role_menu values ('2', '1052'); insert into sys_role_menu values ('2', '1053'); insert into sys_role_menu values ('2', '1054'); insert into sys_role_menu values ('2', '1055'); insert into sys_role_menu values ('2', '1056'); insert into sys_role_menu values ('2', '1057'); insert into sys_role_menu values ('2', '1058'); insert into sys_role_menu values ('2', '1059'); insert into sys_role_menu values ('2', '1060'); insert into sys_role_menu values ('2', '1061'); -- ---------------------------- -- 8、角色和部门关联表 角色1-N部门 -- ---------------------------- drop table if exists sys_role_dept; create table sys_role_dept ( role_id bigint(20) not null comment '角色ID', dept_id bigint(20) not null comment '部门ID', primary key(role_id, dept_id) ) engine=innodb comment = '角色和部门关联表'; -- ---------------------------- -- 初始化-角色和部门关联表数据 -- ---------------------------- insert into sys_role_dept values ('2', '100'); insert into sys_role_dept values ('2', '101'); insert into sys_role_dept values ('2', '105'); -- ---------------------------- -- 9、用户与岗位关联表 用户1-N岗位 -- ---------------------------- drop table if exists sys_user_post; create table sys_user_post ( user_id bigint(20) not null comment '用户ID', post_id bigint(20) not null comment '岗位ID', primary key (user_id, post_id) ) engine=innodb comment = '用户与岗位关联表'; -- ---------------------------- -- 初始化-用户与岗位关联表数据 -- ---------------------------- insert into sys_user_post values ('1', '1'); insert into sys_user_post values ('2', '2'); -- ---------------------------- -- 10、操作日志记录 -- ---------------------------- drop table if exists sys_oper_log; create table sys_oper_log ( oper_id bigint(20) not null auto_increment comment '日志主键', title varchar(50) default '' comment '模块标题', business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)', method varchar(200) default '' comment '方法名称', request_method varchar(10) default '' comment '请求方式', operator_type int(1) default 0 comment '操作类别(0其它 1后台用户 2手机端用户)', oper_name varchar(50) default '' comment '操作人员', dept_name varchar(50) default '' comment '部门名称', oper_url varchar(255) default '' comment '请求URL', oper_ip varchar(128) default '' comment '主机地址', oper_location varchar(255) default '' comment '操作地点', oper_param varchar(2000) default '' comment '请求参数', json_result varchar(2000) default '' comment '返回参数', status int(1) default 0 comment '操作状态(0正常 1异常)', error_msg varchar(2000) default '' comment '错误消息', oper_time datetime comment '操作时间', cost_time bigint(20) default 0 comment '消耗时间', primary key (oper_id), key idx_sys_oper_log_bt (business_type), key idx_sys_oper_log_s (status), key idx_sys_oper_log_ot (oper_time) ) engine=innodb auto_increment=100 comment = '操作日志记录'; -- ---------------------------- -- 11、字典类型表 -- ---------------------------- drop table if exists sys_dict_type; create table sys_dict_type ( dict_id bigint(20) not null auto_increment comment '字典主键', dict_name varchar(100) default '' comment '字典名称', dict_type varchar(100) default '' comment '字典类型', status char(1) default '0' comment '状态(0正常 1停用)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (dict_id), unique (dict_type) ) engine=innodb auto_increment=100 comment = '字典类型表'; insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表'); insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表'); insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表'); insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表'); insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表'); insert into sys_dict_type values(6, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表'); insert into sys_dict_type values(7, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表'); insert into sys_dict_type values(8, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表'); insert into sys_dict_type values(9, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表'); insert into sys_dict_type values(10, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表'); -- ---------------------------- -- 12、字典数据表 -- ---------------------------- drop table if exists sys_dict_data; create table sys_dict_data ( dict_code bigint(20) not null auto_increment comment '字典编码', dict_sort int(4) default 0 comment '字典排序', dict_label varchar(100) default '' comment '字典标签', dict_value varchar(100) default '' comment '字典键值', dict_type varchar(100) default '' comment '字典类型', css_class varchar(100) default null comment '样式属性(其他样式扩展)', list_class varchar(100) default null comment '表格回显样式', is_default char(1) default 'N' comment '是否默认(Y是 N否)', status char(1) default '0' comment '状态(0正常 1停用)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (dict_code) ) engine=innodb auto_increment=100 comment = '字典数据表'; insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男'); insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女'); insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知'); insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单'); insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单'); insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); insert into sys_dict_data values(10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', sysdate(), '', null, '默认分组'); insert into sys_dict_data values(11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, '系统分组'); insert into sys_dict_data values(12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是'); insert into sys_dict_data values(13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否'); insert into sys_dict_data values(14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知'); insert into sys_dict_data values(15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告'); insert into sys_dict_data values(16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); insert into sys_dict_data values(17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态'); insert into sys_dict_data values(18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作'); insert into sys_dict_data values(19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作'); insert into sys_dict_data values(20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作'); insert into sys_dict_data values(21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作'); insert into sys_dict_data values(22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作'); insert into sys_dict_data values(23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作'); insert into sys_dict_data values(24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作'); insert into sys_dict_data values(25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作'); insert into sys_dict_data values(26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作'); insert into sys_dict_data values(27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作'); insert into sys_dict_data values(28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态'); insert into sys_dict_data values(29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); -- ---------------------------- -- 13、参数配置表 -- ---------------------------- drop table if exists sys_config; create table sys_config ( config_id int(5) not null auto_increment comment '参数主键', config_name varchar(100) default '' comment '参数名称', config_key varchar(100) default '' comment '参数键名', config_value varchar(500) default '' comment '参数键值', config_type char(1) default 'N' comment '系统内置(Y是 N否)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (config_id) ) engine=innodb auto_increment=100 comment = '参数配置表'; insert into sys_config values(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456'); insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深黑主题theme-dark,浅色主题theme-light,深蓝主题theme-blue'); insert into sys_config values(4, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)'); insert into sys_config values(5, '用户管理-密码字符范围', 'sys.account.chrtype', '0', 'Y', 'admin', sysdate(), '', null, '默认任意字符范围,0任意(密码可以输入任意字符),1数字(密码只能为0-9数字),2英文字母(密码只能为a-z和A-Z字母),3字母和数字(密码必须包含字母,数字),4字母数字和特殊字符(目前支持的特殊字符包括:~!@#$%^&*()-=_+)'); insert into sys_config values(6, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', sysdate(), '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框'); insert into sys_config values(7, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', sysdate(), '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框'); insert into sys_config values(8, '主框架页-菜单导航显示风格', 'sys.index.menuStyle', 'default', 'Y', 'admin', sysdate(), '', null, '菜单导航显示风格(default为左侧导航菜单,topnav为顶部导航菜单)'); insert into sys_config values(9, '主框架页-是否开启页脚', 'sys.index.footer', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启底部页脚显示(true显示,false隐藏)'); insert into sys_config values(10, '主框架页-是否开启页签', 'sys.index.tagsView', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启菜单多页签显示(true显示,false隐藏)'); insert into sys_config values(11, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); -- ---------------------------- -- 14、系统访问记录 -- ---------------------------- drop table if exists sys_logininfor; create table sys_logininfor ( info_id bigint(20) not null auto_increment comment '访问ID', login_name varchar(50) default '' comment '登录账号', ipaddr varchar(128) default '' comment '登录IP地址', login_location varchar(255) default '' comment '登录地点', browser varchar(50) default '' comment '浏览器类型', os varchar(50) default '' comment '操作系统', status char(1) default '0' comment '登录状态(0成功 1失败)', msg varchar(255) default '' comment '提示消息', login_time datetime comment '访问时间', primary key (info_id), key idx_sys_logininfor_s (status), key idx_sys_logininfor_lt (login_time) ) engine=innodb auto_increment=100 comment = '系统访问记录'; -- ---------------------------- -- 15、在线用户记录 -- ---------------------------- drop table if exists sys_user_online; create table sys_user_online ( sessionId varchar(50) default '' comment '用户会话id', login_name varchar(50) default '' comment '登录账号', dept_name varchar(50) default '' comment '部门名称', ipaddr varchar(128) default '' comment '登录IP地址', login_location varchar(255) default '' comment '登录地点', browser varchar(50) default '' comment '浏览器类型', os varchar(50) default '' comment '操作系统', status varchar(10) default '' comment '在线状态on_line在线off_line离线', start_timestamp datetime comment 'session创建时间', last_access_time datetime comment 'session最后访问时间', expire_time int(5) default 0 comment '超时时间,单位为分钟', session_data blob default null comment '序列化的Session数据,用于服务重启后恢复会话', primary key (sessionId) ) engine=innodb comment = '在线用户记录'; -- ---------------------------- -- 16、定时任务调度表 -- ---------------------------- drop table if exists sys_job; create table sys_job ( job_id bigint(20) not null auto_increment comment '任务ID', job_name varchar(64) default '' comment '任务名称', job_group varchar(64) default 'DEFAULT' comment '任务组名', invoke_target varchar(500) not null comment '调用目标字符串', cron_expression varchar(255) default '' comment 'cron执行表达式', misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)', status char(1) default '0' comment '状态(0正常 1暂停)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default '' comment '备注信息', primary key (job_id, job_name, job_group) ) engine=innodb auto_increment=100 comment = '定时任务调度表'; insert into sys_job values(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); insert into sys_job values(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); -- ---------------------------- -- 17、定时任务调度日志表 -- ---------------------------- drop table if exists sys_job_log; create table sys_job_log ( job_log_id bigint(20) not null auto_increment comment '任务日志ID', job_name varchar(64) not null comment '任务名称', job_group varchar(64) not null comment '任务组名', invoke_target varchar(500) not null comment '调用目标字符串', job_message varchar(500) comment '日志信息', status char(1) default '0' comment '执行状态(0正常 1失败)', exception_info varchar(2000) default '' comment '异常信息', start_time datetime comment '执行开始时间', end_time datetime comment '执行结束时间', create_time datetime comment '创建时间', primary key (job_log_id) ) engine=innodb comment = '定时任务调度日志表'; -- ---------------------------- -- 18、通知公告表 -- ---------------------------- drop table if exists sys_notice; create table sys_notice ( notice_id int(4) not null auto_increment comment '公告ID', notice_title varchar(50) not null comment '公告标题', notice_type char(1) not null comment '公告类型(1通知 2公告)', notice_content longblob default null comment '公告内容', status char(1) default '0' comment '公告状态(0正常 1关闭)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(255) default null comment '备注', primary key (notice_id) ) engine=innodb auto_increment=10 comment = '通知公告表'; -- ---------------------------- -- 初始化-公告信息表数据 -- ---------------------------- insert into sys_notice values('1', '温馨提醒:2018-07-01 若依新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); insert into sys_notice values('2', '维护通知:2018-07-01 若依系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); insert into sys_notice values('3', '若依开源框架介绍', '1', '

                    项目介绍

                    RuoYi开源项目是为企业用户定制的后台脚手架框架,为企业打造的一站式解决方案,降低企业开发成本,提升开发效率。主要包括用户管理、角色管理、部门管理、菜单管理、参数管理、字典管理、岗位管理、定时任务服务监控、登录日志、操作日志、代码生成等功能。其中,还支持多数据源、数据权限、国际化、Redis缓存、Docker部署、滑动验证码、第三方认证登录、分布式事务、分布式文件存储、分库分表处理等技术特点。


                    官网及演示

                    若依官网地址: http://ruoyi.vip

                    若依文档地址: http://doc.ruoyi.vip

                    演示地址【不分离版】: http://demo.ruoyi.vip

                    演示地址【分离版本】: http://vue.ruoyi.vip

                    演示地址【微服务版】: http://cloud.ruoyi.vip

                    演示地址【移动端版】: http://h5.ruoyi.vip


                    ', '0', 'admin', sysdate(), '', null, '管理员'); -- ---------------------------- -- 19、公告已读记录表 -- ---------------------------- drop table if exists sys_notice_read; create table sys_notice_read ( read_id bigint(20) not null auto_increment comment '已读主键', notice_id int(4) not null comment '公告id', user_id bigint(20) not null comment '用户id', read_time datetime not null comment '阅读时间', primary key (read_id), unique key uk_user_notice (user_id, notice_id) comment '同一用户同一公告只记录一次' ) engine=innodb auto_increment=1 comment='公告已读记录表'; -- ---------------------------- -- 20、代码生成业务表 -- ---------------------------- drop table if exists gen_table; create table gen_table ( table_id bigint(20) not null auto_increment comment '编号', table_name varchar(200) default '' comment '表名称', table_comment varchar(500) default '' comment '表描述', sub_table_name varchar(64) default null comment '关联子表的表名', sub_table_fk_name varchar(64) default null comment '子表关联的外键名', class_name varchar(100) default '' comment '实体类名称', tpl_category varchar(200) default 'crud' comment '使用的模板(crud单表操作 tree树表操作 sub主子表操作)', package_name varchar(100) comment '生成包路径', module_name varchar(30) comment '生成模块名', business_name varchar(30) comment '生成业务名', function_name varchar(50) comment '生成功能名', function_author varchar(50) comment '生成功能作者', form_col_num int(1) default 1 comment '表单布局(单列 双列 三列)', gen_type char(1) default '0' comment '生成代码方式(0zip压缩包 1自定义路径)', gen_path varchar(200) default '/' comment '生成路径(不填默认项目路径)', options varchar(1000) comment '其它生成选项', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (table_id) ) engine=innodb auto_increment=1 comment = '代码生成业务表'; -- ---------------------------- -- 21、代码生成业务表字段 -- ---------------------------- drop table if exists gen_table_column; create table gen_table_column ( column_id bigint(20) not null auto_increment comment '编号', table_id bigint(20) comment '归属表编号', column_name varchar(200) comment '列名称', column_comment varchar(500) comment '列描述', column_type varchar(100) comment '列类型', java_type varchar(500) comment 'JAVA类型', java_field varchar(200) comment 'JAVA字段名', is_pk char(1) comment '是否主键(1是)', is_increment char(1) comment '是否自增(1是)', is_required char(1) comment '是否必填(1是)', is_insert char(1) comment '是否为插入字段(1是)', is_edit char(1) comment '是否编辑字段(1是)', is_list char(1) comment '是否列表字段(1是)', is_query char(1) comment '是否查询字段(1是)', query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)', html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', dict_type varchar(200) default '' comment '字典类型', sort int comment '排序', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', primary key (column_id) ) engine=innodb auto_increment=1 comment = '代码生成业务表字段';